diff --git a/MIGRATING.md b/MIGRATING.md index bfc8ef1d6..12dadf45f 100644 --- a/MIGRATING.md +++ b/MIGRATING.md @@ -95,62 +95,6 @@ The `MPListenerController` class has been removed. The SDK no longer invokes any --- -### Source-Based Distribution Changes - -As part of the transition from binary (XCFramework) to source-based distribution, several public Swift classes have been converted to Objective-C. This improves compatibility with Swift Package Manager and reduces integration complexity, but may require code changes for Swift users who were referencing Swift-specific type hierarchies. - -#### MPRoktEvent Classes Moved to Top-Level - -The `MPRoktEvent` subclasses have been converted from Swift nested classes to Objective-C top-level classes. This changes how Swift users reference these types. - -**Before (Swift):** - -```swift -let initEvent = MPRoktEvent.MPRoktInitComplete(success: true) -let readyEvent = MPRoktEvent.MPRoktPlacementReady(placementId: "abc") -let purchaseEvent = MPRoktEvent.MPRoktCartItemInstantPurchase(...) -``` - -**After (Swift):** - -```swift -let initEvent = MPRoktInitComplete(success: true) -let readyEvent = MPRoktPlacementReady(placementId: "abc") -let purchaseEvent = MPRoktCartItemInstantPurchase(...) -``` - -**Affected Classes:** - -| Before (Swift) | After (Swift) | -| ------------------------------------------- | ------------------------------- | -| `MPRoktEvent.MPRoktInitComplete` | `MPRoktInitComplete` | -| `MPRoktEvent.MPRoktShowLoadingIndicator` | `MPRoktShowLoadingIndicator` | -| `MPRoktEvent.MPRoktHideLoadingIndicator` | `MPRoktHideLoadingIndicator` | -| `MPRoktEvent.MPRoktPlacementInteractive` | `MPRoktPlacementInteractive` | -| `MPRoktEvent.MPRoktPlacementReady` | `MPRoktPlacementReady` | -| `MPRoktEvent.MPRoktOfferEngagement` | `MPRoktOfferEngagement` | -| `MPRoktEvent.MPRoktOpenUrl` | `MPRoktOpenUrl` | -| `MPRoktEvent.MPRoktPositiveEngagement` | `MPRoktPositiveEngagement` | -| `MPRoktEvent.MPRoktPlacementClosed` | `MPRoktPlacementClosed` | -| `MPRoktEvent.MPRoktPlacementCompleted` | `MPRoktPlacementCompleted` | -| `MPRoktEvent.MPRoktPlacementFailure` | `MPRoktPlacementFailure` | -| `MPRoktEvent.MPRoktFirstPositiveEngagement` | `MPRoktFirstPositiveEngagement` | -| `MPRoktEvent.MPRoktCartItemInstantPurchase` | `MPRoktCartItemInstantPurchase` | - -**Migration Steps:** - -1. Find all usages of `MPRoktEvent.MPRokt*` in your Swift code -2. Remove the `MPRoktEvent.` prefix -3. Type checking with `is` remains unchanged since they still inherit from `MPRoktEvent` - -**Notes:** - -- Objective-C users are not affected by this change -- The `MPRoktEvent` base class still exists and can be used for type checking -- All initializers and properties remain the same - ---- - ### Direct Routing Enabled by Default API requests now route directly to regional endpoints based on your API key prefix: @@ -384,6 +328,181 @@ targets: [ --- +### MPRokt API Changes + +The `MPRokt` interface has been updated to align with the Rokt SDK 5.0.x API. These changes consolidate multiple callback parameters into a unified event-based callback pattern and standardize parameter naming. + +#### What Has Changed + +- The `MPRoktEventCallback` class has been removed and replaced with MPRoktEvent +- The `selectPlacements:` method's `callbacks:` parameter has been replaced with `onEvent:` +- The `purchaseFinalized:` method's `placementId:` parameter has been renamed to `identifier:` +- A new `globalEvents:` method has been added for subscribing to global Rokt events +- A new `MPRoktEmbeddedSizeChanged` event class has been added + +#### Migration Steps + +##### selectPlacements Method + +**Before (Objective-C):** + +```objective-c +MPRoktEventCallback *callbacks = [[MPRoktEventCallback alloc] init]; +callbacks.onLoad = ^{ + // Handle load +}; +callbacks.onUnLoad = ^{ + // Handle unload +}; +callbacks.onShouldShowLoadingIndicator = ^{ + // Show loading indicator +}; +callbacks.onShouldHideLoadingIndicator = ^{ + // Hide loading indicator +}; +callbacks.onEmbeddedSizeChange = ^(NSString *placementId, CGFloat height) { + // Handle size change +}; + +[[MParticle sharedInstance].rokt selectPlacements:@"checkout" + attributes:attributes + embeddedViews:embeddedViews + config:config + callbacks:callbacks]; +``` + +**After (Objective-C):** + +```objective-c +[[MParticle sharedInstance].rokt selectPlacements:@"checkout" + attributes:attributes + embeddedViews:embeddedViews + config:config + onEvent:^(MPRoktEvent * _Nonnull event) { + if ([event isKindOfClass:[MPRoktShowLoadingIndicator class]]) { + // Show loading indicator + } else if ([event isKindOfClass:[MPRoktHideLoadingIndicator class]]) { + // Hide loading indicator + } else if ([event isKindOfClass:[MPRoktPlacementReady class]]) { + // Handle load/ready + } else if ([event isKindOfClass:[MPRoktPlacementClosed class]]) { + // Handle unload/closed + } else if ([event isKindOfClass:[MPRoktEmbeddedSizeChanged class]]) { + MPRoktEmbeddedSizeChanged *sizeEvent = (MPRoktEmbeddedSizeChanged *)event; + // Handle size change with sizeEvent.placementId and sizeEvent.updatedHeight + } +}]; +``` + +**Before (Swift):** + +```swift +let callbacks = MPRoktEventCallback() +callbacks.onLoad = { + // Handle load +} +callbacks.onUnLoad = { + // Handle unload +} +callbacks.onShouldShowLoadingIndicator = { + // Show loading indicator +} +callbacks.onShouldHideLoadingIndicator = { + // Hide loading indicator +} +callbacks.onEmbeddedSizeChange = { placementId, height in + // Handle size change +} + +MParticle.sharedInstance().rokt.selectPlacements("checkout", + attributes: attributes, + embeddedViews: embeddedViews, + config: config, + callbacks: callbacks) +``` + +**After (Swift):** + +```swift +MParticle.sharedInstance().rokt.selectPlacements("checkout", + attributes: attributes, + embeddedViews: embeddedViews, + config: config) { event in + switch event { + case is MPRoktEvent.MPRoktShowLoadingIndicator: + // Show loading indicator + case is MPRoktEvent.MPRoktHideLoadingIndicator: + // Hide loading indicator + case is MPRoktEvent.MPRoktPlacementReady: + // Handle load/ready + case is MPRoktEvent.MPRoktPlacementClosed: + // Handle unload/closed + case let sizeEvent as MPRoktEvent.MPRoktEmbeddedSizeChanged: + // Handle size change with sizeEvent.placementId and sizeEvent.updatedHeight + default: + break + } +} +``` + +##### purchaseFinalized Method + +**(Objective-C):** + +```objective-c +[[MParticle sharedInstance].rokt purchaseFinalized:@"checkout" + catalogItemId:@"item123" + success:YES]; +``` + +Note: The method signature remains the same, but the parameter name has changed from `placementId:` to `identifier:`. If you're using named parameters, update accordingly. + +##### New globalEvents Method + +The new `globalEvents:` method allows you to subscribe to global Rokt events from all sources, including events not associated with a specific view (such as `InitComplete`). + +**Objective-C:** + +```objective-c +[[MParticle sharedInstance].rokt globalEvents:^(MPRoktEvent * _Nonnull event) { + if ([event isKindOfClass:[MPRoktInitComplete class]]) { + MPRoktInitComplete *initEvent = (MPRoktInitComplete *)event; + if (initEvent.success) { + // Rokt SDK initialized successfully + } + } +}]; +``` + +**Swift:** + +```swift +MParticle.sharedInstance().rokt.globalEvents { event in + if let initEvent = event as? MPRoktEvent.MPRoktInitComplete { + if initEvent.success { + // Rokt SDK initialized successfully + } + } +} +``` + +#### Event Mapping Reference + +| Old Callback | New Event Class | +| ------------------------------ | ---------------------------- | +| `onLoad` | `MPRoktPlacementReady` | +| `onUnLoad` | `MPRoktPlacementClosed` | +| `onShouldShowLoadingIndicator` | `MPRoktShowLoadingIndicator` | +| `onShouldHideLoadingIndicator` | `MPRoktHideLoadingIndicator` | +| `onEmbeddedSizeChange` | `MPRoktEmbeddedSizeChanged` | + +#### Notes + +- All `MPRoktEvent` subclasses are nested classes within `MPRoktEvent` +- The `onEvent` callback receives all event types, so use type checking to handle specific events +- The `MPRoktEmbeddedSizeChanged` event provides both `placementId` and `updatedHeight` properties +- Remove any references to `MPRoktEventCallback` from your code + ## Migrating from versions < 8.0.0 For migration guidance from SDK 7.x to SDK 8.x, please see [migration-guide-v8.md](migration-guide-v8.md). diff --git a/UnitTests/ObjCTests/MPRoktEventTests.m b/UnitTests/ObjCTests/MPRoktEventTests.m index 9729848df..8a7b2867a 100644 --- a/UnitTests/ObjCTests/MPRoktEventTests.m +++ b/UnitTests/ObjCTests/MPRoktEventTests.m @@ -247,6 +247,24 @@ - (void)testCartItemInstantPurchaseDescriptionOverride { XCTAssertEqualObjects(event.description, customDescription); } +#pragma mark - MPRoktEmbeddedSizeChanged Tests + +- (void)testEmbeddedSizeChangedWithPlacementIdAndHeight { + NSString *placementId = @"embed-placement-123"; + CGFloat updatedHeight = 250.5; + MPRoktEmbeddedSizeChanged *event = [[MPRoktEmbeddedSizeChanged alloc] initWithPlacementId:placementId updatedHeight:updatedHeight]; + XCTAssertNotNil(event); + XCTAssertEqualObjects(event.placementId, placementId); + XCTAssertEqualWithAccuracy(event.updatedHeight, updatedHeight, 0.001); + XCTAssertTrue([event isKindOfClass:[MPRoktEvent class]]); +} + +- (void)testEmbeddedSizeChangedWithZeroHeight { + MPRoktEmbeddedSizeChanged *event = [[MPRoktEmbeddedSizeChanged alloc] initWithPlacementId:@"placement" updatedHeight:0]; + XCTAssertNotNil(event); + XCTAssertEqualWithAccuracy(event.updatedHeight, 0, 0.001); +} + #pragma mark - Inheritance Tests - (void)testAllEventTypesInheritFromMPRoktEvent { @@ -264,6 +282,7 @@ - (void)testAllEventTypesInheritFromMPRoktEvent { XCTAssertTrue([[[MPRoktPlacementFailure alloc] initWithPlacementId:@"test"] isKindOfClass:[MPRoktEvent class]]); XCTAssertTrue([[[MPRoktFirstPositiveEngagement alloc] initWithPlacementId:@"test"] isKindOfClass:[MPRoktEvent class]]); XCTAssertTrue([[[MPRoktCartItemInstantPurchase alloc] initWithPlacementId:@"p" name:nil cartItemId:@"c" catalogItemId:@"cat" currency:@"USD" description:@"d" linkedProductId:nil providerData:@"prov" quantity:nil totalPrice:nil unitPrice:nil] isKindOfClass:[MPRoktEvent class]]); + XCTAssertTrue([[[MPRoktEmbeddedSizeChanged alloc] initWithPlacementId:@"p" updatedHeight:100] isKindOfClass:[MPRoktEvent class]]); } @end diff --git a/UnitTests/ObjCTests/MPRoktTests.m b/UnitTests/ObjCTests/MPRoktTests.m index a71e100a9..d166bf594 100644 --- a/UnitTests/ObjCTests/MPRoktTests.m +++ b/UnitTests/ObjCTests/MPRoktTests.m @@ -88,7 +88,7 @@ - (void)testSelectPlacementsSimpleWithValidParameters { // Set up expectations for kit container XCTestExpectation *expectation = [self expectationWithDescription:@"Wait for async operation"]; - SEL roktSelector = @selector(executeWithIdentifier:attributes:embeddedViews:config:callbacks:filteredUser:); + SEL roktSelector = @selector(executeWithIdentifier:attributes:embeddedViews:config:onEvent:filteredUser:); OCMExpect([self.mockContainer forwardSDKCall:roktSelector event:nil parameters:[OCMArg checkWithBlock:^BOOL(MPForwardQueueParameters *params) { @@ -96,6 +96,7 @@ - (void)testSelectPlacementsSimpleWithValidParameters { XCTAssertEqualObjects(params[1], attributes); XCTAssertNil(params[2]); XCTAssertNil(params[3]); + XCTAssertNil(params[4]); return true; }] messageType:MPMessageTypeEvent @@ -128,12 +129,11 @@ - (void)testSelectPlacementsExpandedWithValidParameters { NSDictionary *finalAttributes = @{@"key": @"value", @"sandbox": @"true"}; MPRoktEmbeddedView *exampleView = [[MPRoktEmbeddedView alloc] initWithFrame:CGRectZero]; NSDictionary *embeddedViews = @{@"placement": exampleView}; - MPRoktEventCallback *exampleCallbacks = [[MPRoktEventCallback alloc] init]; - exampleCallbacks.onLoad = ^{}; - exampleCallbacks.onUnLoad = ^{}; - exampleCallbacks.onShouldShowLoadingIndicator = ^{}; - exampleCallbacks.onShouldHideLoadingIndicator = ^{}; - exampleCallbacks.onEmbeddedSizeChange = ^(NSString *p, CGFloat s){}; + + // Create onEvent callback block + void (^exampleOnEvent)(MPRoktEvent * _Nonnull) = ^(MPRoktEvent * _Nonnull event) { + // Handle event + }; MPRoktConfig *roktConfig = [[MPRoktConfig alloc] init]; roktConfig.colorMode = MPColorModeDark; @@ -142,7 +142,7 @@ - (void)testSelectPlacementsExpandedWithValidParameters { // Set up expectations for kit container XCTestExpectation *expectation = [self expectationWithDescription:@"Wait for async operation"]; - SEL roktSelector = @selector(executeWithIdentifier:attributes:embeddedViews:config:callbacks:filteredUser:); + SEL roktSelector = @selector(executeWithIdentifier:attributes:embeddedViews:config:onEvent:filteredUser:); OCMExpect([self.mockContainer forwardSDKCall:roktSelector event:nil parameters:[OCMArg checkWithBlock:^BOOL(MPForwardQueueParameters *params) { @@ -150,12 +150,7 @@ - (void)testSelectPlacementsExpandedWithValidParameters { XCTAssertEqualObjects(params[1], finalAttributes); XCTAssertEqualObjects(params[2], embeddedViews); XCTAssertEqualObjects(params[3], roktConfig); - MPRoktEventCallback *resultCallbacks = params[4]; - XCTAssertEqualObjects(resultCallbacks.onLoad, exampleCallbacks.onLoad); - XCTAssertEqualObjects(resultCallbacks.onUnLoad, exampleCallbacks.onUnLoad); - XCTAssertEqualObjects(resultCallbacks.onShouldShowLoadingIndicator, exampleCallbacks.onShouldShowLoadingIndicator); - XCTAssertEqualObjects(resultCallbacks.onShouldHideLoadingIndicator, exampleCallbacks.onShouldHideLoadingIndicator); - XCTAssertEqualObjects(resultCallbacks.onEmbeddedSizeChange, exampleCallbacks.onEmbeddedSizeChange); + XCTAssertNotNil(params[4]); // onEvent callback should be set return true; }] messageType:MPMessageTypeEvent @@ -168,7 +163,7 @@ - (void)testSelectPlacementsExpandedWithValidParameters { attributes:attributes embeddedViews:embeddedViews config:roktConfig - callbacks:exampleCallbacks]; + onEvent:exampleOnEvent]; // Wait for async operation [self waitForExpectationsWithTimeout:0.2 handler:nil]; @@ -193,12 +188,12 @@ - (void)testSelectPlacementsExpandedWithNilParameters { attributes:nil embeddedViews:nil config:nil - callbacks:nil]; + onEvent:nil]; // Wait for async operation XCTestExpectation *expectation = [self expectationWithDescription:@"Wait for async operation"]; - SEL roktSelector = @selector(executeWithIdentifier:attributes:embeddedViews:config:callbacks:filteredUser:); + SEL roktSelector = @selector(executeWithIdentifier:attributes:embeddedViews:config:onEvent:filteredUser:); NSDictionary *finalAttributes = @{@"sandbox": @"true"}; OCMExpect([self.mockContainer forwardSDKCall:roktSelector @@ -208,6 +203,7 @@ - (void)testSelectPlacementsExpandedWithNilParameters { XCTAssertEqualObjects(params[1], finalAttributes); XCTAssertNil(params[2]); XCTAssertNil(params[3]); + XCTAssertNil(params[4]); return true; }] messageType:MPMessageTypeEvent @@ -237,7 +233,7 @@ - (void)testSelectPlacementsSimpleWithMapping { // Set up expectations for kit container XCTestExpectation *expectation = [self expectationWithDescription:@"Wait for async operation"]; - SEL roktSelector = @selector(executeWithIdentifier:attributes:embeddedViews:config:callbacks:filteredUser:); + SEL roktSelector = @selector(executeWithIdentifier:attributes:embeddedViews:config:onEvent:filteredUser:); OCMExpect([self.mockContainer forwardSDKCall:roktSelector event:nil parameters:[OCMArg checkWithBlock:^BOOL(MPForwardQueueParameters *params) { @@ -245,6 +241,7 @@ - (void)testSelectPlacementsSimpleWithMapping { XCTAssertEqualObjects(params[1], mappedAttributes); XCTAssertNil(params[2]); XCTAssertNil(params[3]); + XCTAssertNil(params[4]); return true; }] messageType:MPMessageTypeEvent @@ -271,7 +268,7 @@ - (void)testSelectPlacementsSimpleWithNilMapping { [[[self.mockInstance stub] andReturn:self.mockContainer] kitContainer_PRIVATE]; [[[self.mockInstance stub] andReturn:self.mockInstance] sharedInstance]; - SEL roktSelector = @selector(executeWithIdentifier:attributes:embeddedViews:config:callbacks:filteredUser:); + SEL roktSelector = @selector(executeWithIdentifier:attributes:embeddedViews:config:onEvent:filteredUser:); OCMReject([self.mockContainer forwardSDKCall:roktSelector event:[OCMArg any] parameters:[OCMArg any] @@ -588,7 +585,7 @@ - (void)testPurchaseFinalized { [[[self.mockInstance stub] andReturn:self.mockInstance] sharedInstance]; // Set up test parameters - NSString *placementId = @"testonversion"; + NSString *identifier = @"testonversion"; NSString *catalogItemId = @"testcatalogItemId"; BOOL success = YES; @@ -598,7 +595,7 @@ - (void)testPurchaseFinalized { OCMExpect([self.mockContainer forwardSDKCall:roktSelector event:nil parameters:[OCMArg checkWithBlock:^BOOL(MPForwardQueueParameters *params) { - XCTAssertEqualObjects(params[0], placementId); + XCTAssertEqualObjects(params[0], identifier); XCTAssertEqualObjects(params[1], catalogItemId); XCTAssertEqualObjects(params[2], @(success)); return true; @@ -609,7 +606,7 @@ - (void)testPurchaseFinalized { }); // Execute method - [[MParticle sharedInstance].rokt purchaseFinalized:placementId catalogItemId:catalogItemId success:success]; + [[MParticle sharedInstance].rokt purchaseFinalized:identifier catalogItemId:catalogItemId success:success]; // Wait for async operation [self waitForExpectationsWithTimeout:0.2 handler:nil]; @@ -747,4 +744,91 @@ - (void)testEventsWithIdentifierCallbackInvocation { OCMVerifyAll(self.mockContainer); } +- (void)testGlobalEvents { + MParticle *instance = [MParticle sharedInstance]; + self.mockInstance = OCMPartialMock(instance); + self.mockContainer = OCMClassMock([MPKitContainer_PRIVATE class]); + [[[self.mockInstance stub] andReturn:self.mockContainer] kitContainer_PRIVATE]; + [[[self.mockInstance stub] andReturn:self.mockInstance] sharedInstance]; + + // Set up test parameters + void (^onEventCallback)(MPRoktEvent *) = ^(MPRoktEvent *event) { + // Handle global event + }; + + // Set up expectations for kit container + XCTestExpectation *expectation = [self expectationWithDescription:@"Wait for async operation"]; + SEL roktSelector = @selector(globalEvents:); + OCMExpect([self.mockContainer forwardSDKCall:roktSelector + event:nil + parameters:[OCMArg checkWithBlock:^BOOL(MPForwardQueueParameters *params) { + XCTAssertNotNil(params[0]); + return true; + }] + messageType:MPMessageTypeEvent + userInfo:nil]).andDo(^(NSInvocation *invocation) { + [expectation fulfill]; + }); + + // Execute method + [self.rokt globalEvents:onEventCallback]; + + // Wait for async operation + [self waitForExpectationsWithTimeout:0.2 handler:nil]; + + // Verify + OCMVerifyAll(self.mockContainer); +} + +- (void)testGlobalEventsCallbackInvocation { + MParticle *instance = [MParticle sharedInstance]; + self.mockInstance = OCMPartialMock(instance); + self.mockContainer = OCMClassMock([MPKitContainer_PRIVATE class]); + [[[self.mockInstance stub] andReturn:self.mockContainer] kitContainer_PRIVATE]; + [[[self.mockInstance stub] andReturn:self.mockInstance] sharedInstance]; + + // Set up test parameters + __block BOOL callbackInvoked = NO; + __block MPRoktEvent *receivedEvent = nil; + + void (^onEventCallback)(MPRoktEvent *) = ^(MPRoktEvent *event) { + callbackInvoked = YES; + receivedEvent = event; + }; + + // Set up expectations for kit container to simulate callback invocation + XCTestExpectation *expectation = [self expectationWithDescription:@"Wait for async operation"]; + SEL roktSelector = @selector(globalEvents:); + OCMExpect([self.mockContainer forwardSDKCall:roktSelector + event:nil + parameters:[OCMArg checkWithBlock:^BOOL(MPForwardQueueParameters *params) { + // Simulate the kit calling the callback with InitComplete event + void (^capturedCallback)(MPRoktEvent *) = params[0]; + if (capturedCallback) { + MPRoktInitComplete *testEvent = [[MPRoktInitComplete alloc] initWithSuccess:YES]; + capturedCallback(testEvent); + } + return true; + }] + messageType:MPMessageTypeEvent + userInfo:nil]).andDo(^(NSInvocation *invocation) { + [expectation fulfill]; + }); + + // Execute method + [self.rokt globalEvents:onEventCallback]; + + // Wait for async operation + [self waitForExpectationsWithTimeout:0.2 handler:nil]; + + // Verify callback was invoked + XCTAssertTrue(callbackInvoked, @"Callback should have been invoked"); + XCTAssertNotNil(receivedEvent, @"Should have received an event"); + XCTAssertTrue([receivedEvent isKindOfClass:[MPRoktInitComplete class]], @"Should receive the correct event type"); + XCTAssertTrue(((MPRoktInitComplete *)receivedEvent).success, @"InitComplete event should indicate success"); + + // Verify + OCMVerifyAll(self.mockContainer); +} + @end diff --git a/UnitTests/SwiftTests/MPRoktEventTests.swift b/UnitTests/SwiftTests/MPRoktEventTests.swift index e5b5ab9e9..bbf702b5b 100644 --- a/UnitTests/SwiftTests/MPRoktEventTests.swift +++ b/UnitTests/SwiftTests/MPRoktEventTests.swift @@ -1,137 +1,159 @@ import XCTest @testable import mParticle_Apple_SDK -/// Tests to verify Rokt event classes are now top-level (not nested) in SDK 9.0 +/// Tests to verify Rokt event classes use nested access via NS_SWIFT_NAME final class MPRoktEventTests: XCTestCase { - // MARK: - Top-Level Class Instantiation Tests + // MARK: - Nested Class Instantiation Tests - func test_MPRoktInitComplete_isTopLevelClass() { - let event = MPRoktInitComplete(success: true) + func test_MPRoktInitComplete_nestedAccess() { + let event = MPRoktEvent.MPRoktInitComplete(success: true) XCTAssertNotNil(event) XCTAssertTrue(event is MPRoktEvent, "MPRoktInitComplete should inherit from MPRoktEvent") XCTAssertTrue(event.success) } func test_MPRoktInitComplete_withFailure() { - let event = MPRoktInitComplete(success: false) + let event = MPRoktEvent.MPRoktInitComplete(success: false) XCTAssertNotNil(event) XCTAssertFalse(event.success) } - func test_MPRoktPlacementReady_isTopLevelClass() { - let event = MPRoktPlacementReady(placementId: "test-placement") + func test_MPRoktPlacementReady_nestedAccess() { + let event = MPRoktEvent.MPRoktPlacementReady(placementId: "test-placement") XCTAssertNotNil(event) XCTAssertTrue(event is MPRoktEvent) XCTAssertEqual(event.placementId, "test-placement") } - func test_MPRoktPlacementClosed_isTopLevelClass() { - let event = MPRoktPlacementClosed(placementId: "test-placement") + func test_MPRoktPlacementClosed_nestedAccess() { + let event = MPRoktEvent.MPRoktPlacementClosed(placementId: "test-placement") XCTAssertNotNil(event) XCTAssertTrue(event is MPRoktEvent) XCTAssertEqual(event.placementId, "test-placement") } - func test_MPRoktPlacementCompleted_isTopLevelClass() { - let event = MPRoktPlacementCompleted(placementId: "test-placement") + func test_MPRoktPlacementCompleted_nestedAccess() { + let event = MPRoktEvent.MPRoktPlacementCompleted(placementId: "test-placement") XCTAssertNotNil(event) XCTAssertTrue(event is MPRoktEvent) XCTAssertEqual(event.placementId, "test-placement") } - func test_MPRoktPlacementInteractive_isTopLevelClass() { - let event = MPRoktPlacementInteractive(placementId: "test-placement") + func test_MPRoktPlacementInteractive_nestedAccess() { + let event = MPRoktEvent.MPRoktPlacementInteractive(placementId: "test-placement") XCTAssertNotNil(event) XCTAssertTrue(event is MPRoktEvent) XCTAssertEqual(event.placementId, "test-placement") } - func test_MPRoktPlacementFailure_isTopLevelClass() { - let event = MPRoktPlacementFailure(placementId: "test-placement") + func test_MPRoktPlacementFailure_nestedAccess() { + let event = MPRoktEvent.MPRoktPlacementFailure(placementId: "test-placement") XCTAssertNotNil(event) XCTAssertTrue(event is MPRoktEvent) XCTAssertEqual(event.placementId, "test-placement") } - func test_MPRoktOfferEngagement_isTopLevelClass() { - let event = MPRoktOfferEngagement(placementId: "test-placement") + func test_MPRoktOfferEngagement_nestedAccess() { + let event = MPRoktEvent.MPRoktOfferEngagement(placementId: "test-placement") XCTAssertNotNil(event) XCTAssertTrue(event is MPRoktEvent) XCTAssertEqual(event.placementId, "test-placement") } - func test_MPRoktPositiveEngagement_isTopLevelClass() { - let event = MPRoktPositiveEngagement(placementId: "test-placement") + func test_MPRoktPositiveEngagement_nestedAccess() { + let event = MPRoktEvent.MPRoktPositiveEngagement(placementId: "test-placement") XCTAssertNotNil(event) XCTAssertTrue(event is MPRoktEvent) XCTAssertEqual(event.placementId, "test-placement") } - func test_MPRoktFirstPositiveEngagement_isTopLevelClass() { - let event = MPRoktFirstPositiveEngagement(placementId: "test-placement") + func test_MPRoktFirstPositiveEngagement_nestedAccess() { + let event = MPRoktEvent.MPRoktFirstPositiveEngagement(placementId: "test-placement") XCTAssertNotNil(event) XCTAssertTrue(event is MPRoktEvent) XCTAssertEqual(event.placementId, "test-placement") } - func test_MPRoktOpenUrl_isTopLevelClass() { - let event = MPRoktOpenUrl(placementId: "test-placement", url: "https://example.com") + func test_MPRoktOpenUrl_nestedAccess() { + let event = MPRoktEvent.MPRoktOpenUrl(placementId: "test-placement", url: "https://example.com") XCTAssertNotNil(event) XCTAssertTrue(event is MPRoktEvent) XCTAssertEqual(event.placementId, "test-placement") XCTAssertEqual(event.url, "https://example.com") } - func test_MPRoktShowLoadingIndicator_isTopLevelClass() { - // Verify class exists and is a top-level class (not nested) - let eventClass: AnyClass? = NSClassFromString("MPRoktShowLoadingIndicator") - XCTAssertNotNil(eventClass, "MPRoktShowLoadingIndicator should be accessible as top-level class") + func test_MPRoktShowLoadingIndicator_nestedAccess() { + let event = MPRoktEvent.MPRoktShowLoadingIndicator() + XCTAssertNotNil(event) + XCTAssertTrue(event is MPRoktEvent) } - func test_MPRoktHideLoadingIndicator_isTopLevelClass() { - // Verify class exists and is a top-level class (not nested) - let eventClass: AnyClass? = NSClassFromString("MPRoktHideLoadingIndicator") - XCTAssertNotNil(eventClass, "MPRoktHideLoadingIndicator should be accessible as top-level class") + func test_MPRoktHideLoadingIndicator_nestedAccess() { + let event = MPRoktEvent.MPRoktHideLoadingIndicator() + XCTAssertNotNil(event) + XCTAssertTrue(event is MPRoktEvent) } - func test_MPRoktCartItemInstantPurchase_isTopLevelClass() { - // Verify class exists and is a top-level class (not nested) + func test_MPRoktCartItemInstantPurchase_classExists() { let eventClass: AnyClass? = NSClassFromString("MPRoktCartItemInstantPurchase") - XCTAssertNotNil(eventClass, "MPRoktCartItemInstantPurchase should be accessible as top-level class") + XCTAssertNotNil(eventClass, "MPRoktCartItemInstantPurchase should exist") + } + + // MARK: - MPRoktEmbeddedSizeChanged Tests + + func test_MPRoktEmbeddedSizeChanged_nestedAccess() { + let event = MPRoktEvent.MPRoktEmbeddedSizeChanged(placementId: "embed-placement", updatedHeight: 250.5) + XCTAssertNotNil(event) + XCTAssertTrue(event is MPRoktEvent) + XCTAssertEqual(event.placementId, "embed-placement") + XCTAssertEqual(event.updatedHeight, 250.5, accuracy: 0.001) + } + + func test_MPRoktEmbeddedSizeChanged_zeroHeight() { + let event = MPRoktEvent.MPRoktEmbeddedSizeChanged(placementId: "embed-placement", updatedHeight: 0) + XCTAssertNotNil(event) + XCTAssertEqual(event.updatedHeight, 0, accuracy: 0.001) + } + + func test_MPRoktEmbeddedSizeChanged_classExists() { + let eventClass: AnyClass? = NSClassFromString("MPRoktEmbeddedSizeChanged") + XCTAssertNotNil(eventClass, "MPRoktEmbeddedSizeChanged should exist") } // MARK: - Type Checking Tests func test_roktEvent_typeChecking_withIsOperator() { let events: [MPRoktEvent] = [ - MPRoktInitComplete(success: true), - MPRoktPlacementReady(placementId: "test"), - MPRoktPlacementClosed(placementId: "test"), - MPRoktPlacementCompleted(placementId: "test"), - MPRoktPlacementInteractive(placementId: "test"), - MPRoktPlacementFailure(placementId: "test") + MPRoktEvent.MPRoktInitComplete(success: true), + MPRoktEvent.MPRoktPlacementReady(placementId: "test"), + MPRoktEvent.MPRoktPlacementClosed(placementId: "test"), + MPRoktEvent.MPRoktPlacementCompleted(placementId: "test"), + MPRoktEvent.MPRoktPlacementInteractive(placementId: "test"), + MPRoktEvent.MPRoktPlacementFailure(placementId: "test"), + MPRoktEvent.MPRoktEmbeddedSizeChanged(placementId: "test", updatedHeight: 100) ] - XCTAssertEqual(events.count, 6) + XCTAssertEqual(events.count, 7) for event in events { XCTAssertTrue(event is MPRoktEvent, "All events should be MPRoktEvent instances") } - XCTAssertTrue(events[0] is MPRoktInitComplete) - XCTAssertTrue(events[1] is MPRoktPlacementReady) - XCTAssertTrue(events[2] is MPRoktPlacementClosed) - XCTAssertTrue(events[3] is MPRoktPlacementCompleted) - XCTAssertTrue(events[4] is MPRoktPlacementInteractive) - XCTAssertTrue(events[5] is MPRoktPlacementFailure) + XCTAssertTrue(events[0] is MPRoktEvent.MPRoktInitComplete) + XCTAssertTrue(events[1] is MPRoktEvent.MPRoktPlacementReady) + XCTAssertTrue(events[2] is MPRoktEvent.MPRoktPlacementClosed) + XCTAssertTrue(events[3] is MPRoktEvent.MPRoktPlacementCompleted) + XCTAssertTrue(events[4] is MPRoktEvent.MPRoktPlacementInteractive) + XCTAssertTrue(events[5] is MPRoktEvent.MPRoktPlacementFailure) + XCTAssertTrue(events[6] is MPRoktEvent.MPRoktEmbeddedSizeChanged) } func test_roktEvent_switchStatement_works() { - let event: MPRoktEvent = MPRoktPlacementReady(placementId: "test") + let event: MPRoktEvent = MPRoktEvent.MPRoktPlacementReady(placementId: "test") var matched = false - if event is MPRoktPlacementReady { + if event is MPRoktEvent.MPRoktPlacementReady { matched = true } @@ -139,15 +161,26 @@ final class MPRoktEventTests: XCTestCase { } func test_roktEvent_casting_works() { - let event: MPRoktEvent = MPRoktOpenUrl(placementId: "test", url: "https://example.com") + let event: MPRoktEvent = MPRoktEvent.MPRoktOpenUrl(placementId: "test", url: "https://example.com") - if let openUrlEvent = event as? MPRoktOpenUrl { + if let openUrlEvent = event as? MPRoktEvent.MPRoktOpenUrl { XCTAssertEqual(openUrlEvent.url, "https://example.com") } else { XCTFail("Casting to MPRoktOpenUrl should succeed") } } + func test_roktEvent_casting_embeddedSizeChanged() { + let event: MPRoktEvent = MPRoktEvent.MPRoktEmbeddedSizeChanged(placementId: "test", updatedHeight: 300) + + if let sizeEvent = event as? MPRoktEvent.MPRoktEmbeddedSizeChanged { + XCTAssertEqual(sizeEvent.placementId, "test") + XCTAssertEqual(sizeEvent.updatedHeight, 300, accuracy: 0.001) + } else { + XCTFail("Casting to MPRoktEmbeddedSizeChanged should succeed") + } + } + // MARK: - Class Existence Tests func test_MPRoktEvent_classExists() { @@ -157,21 +190,26 @@ final class MPRoktEventTests: XCTestCase { func test_MPRoktInitComplete_classExists() { let eventClass: AnyClass? = NSClassFromString("MPRoktInitComplete") - XCTAssertNotNil(eventClass, "MPRoktInitComplete should exist as top-level class") + XCTAssertNotNil(eventClass, "MPRoktInitComplete should exist") } func test_MPRoktPlacementReady_classExists() { let eventClass: AnyClass? = NSClassFromString("MPRoktPlacementReady") - XCTAssertNotNil(eventClass, "MPRoktPlacementReady should exist as top-level class") + XCTAssertNotNil(eventClass, "MPRoktPlacementReady should exist") } func test_MPRoktPlacementClosed_classExists() { let eventClass: AnyClass? = NSClassFromString("MPRoktPlacementClosed") - XCTAssertNotNil(eventClass, "MPRoktPlacementClosed should exist as top-level class") + XCTAssertNotNil(eventClass, "MPRoktPlacementClosed should exist") } - func test_MPRoktCartItemInstantPurchase_classExists() { + func test_MPRoktCartItemInstantPurchase_classExists_runtime() { let eventClass: AnyClass? = NSClassFromString("MPRoktCartItemInstantPurchase") - XCTAssertNotNil(eventClass, "MPRoktCartItemInstantPurchase should exist as top-level class") + XCTAssertNotNil(eventClass, "MPRoktCartItemInstantPurchase should exist") + } + + func test_MPRoktEmbeddedSizeChanged_classExists_runtime() { + let eventClass: AnyClass? = NSClassFromString("MPRoktEmbeddedSizeChanged") + XCTAssertNotNil(eventClass, "MPRoktEmbeddedSizeChanged should exist") } } diff --git a/mParticle-Apple-SDK/Include/MPKitProtocol.h b/mParticle-Apple-SDK/Include/MPKitProtocol.h index 02e5d4208..07adac984 100644 --- a/mParticle-Apple-SDK/Include/MPKitProtocol.h +++ b/mParticle-Apple-SDK/Include/MPKitProtocol.h @@ -17,7 +17,6 @@ @class FilteredMPIdentityApiRequest; @class MPRoktEmbeddedView; @class MPRoktConfig; -@class MPRoktEventCallback; @class MPRoktEvent; #if TARGET_OS_IOS == 1 && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0 @@ -132,17 +131,18 @@ #pragma mark First Party Kits - (nonnull MPKitExecStatus *)executeWithIdentifier:(NSString * _Nullable)identifier - attributes:(NSDictionary * _Nonnull)attributes - embeddedViews:(NSDictionary * _Nullable)embeddedViews - config:(MPRoktConfig * _Nullable)config - callbacks:(MPRoktEventCallback * _Nullable)callbacks - filteredUser:(FilteredMParticleUser * _Nonnull)filteredUser; + attributes:(NSDictionary * _Nonnull)attributes + embeddedViews:(NSDictionary * _Nullable)embeddedViews + config:(MPRoktConfig * _Nullable)config + onEvent:(void (^ _Nullable)(MPRoktEvent * _Nonnull))onEvent + filteredUser:(FilteredMParticleUser * _Nonnull)filteredUser; - (nonnull MPKitExecStatus *)setWrapperSdk:(MPWrapperSdk)wrapperSdk version:(nonnull NSString *)wrapperSdkVersion; -- (nonnull MPKitExecStatus *)purchaseFinalized:(nonnull NSString *)placementId +- (nonnull MPKitExecStatus *)purchaseFinalized:(nonnull NSString *)identifier catalogItemId:(nonnull NSString *)catalogItemId success:(nonnull NSNumber *)success; - (nonnull MPKitExecStatus *)events:(NSString * _Nonnull)identifier onEvent:(void (^ _Nullable)(MPRoktEvent * _Nonnull))onEvent; +- (nonnull MPKitExecStatus *)globalEvents:(void (^ _Nonnull)(MPRoktEvent * _Nonnull))onEvent; @end diff --git a/mParticle-Apple-SDK/Include/MPRokt.h b/mParticle-Apple-SDK/Include/MPRokt.h index 78f6ff087..b70c1d351 100644 --- a/mParticle-Apple-SDK/Include/MPRokt.h +++ b/mParticle-Apple-SDK/Include/MPRokt.h @@ -8,25 +8,6 @@ #import #import -/** - * Callback container for Rokt callbacks. - * Used to handle various lifecycle and UI events from Rokt. - */ -@interface MPRoktEventCallback : NSObject - -/** Called when the Rokt placement has finished loading */ -@property (nonatomic, copy, nullable) void (^onLoad)(void); -/** Called when the Rokt placement is being unloaded/removed */ -@property (nonatomic, copy, nullable) void (^onUnLoad)(void); -/** Called when Rokt reccomends the UI shows a loading indicator */ -@property (nonatomic, copy, nullable) void (^onShouldShowLoadingIndicator)(void); -/** Called when Rokt reccomends the UI hides its loading indicator */ -@property (nonatomic, copy, nullable) void (^onShouldHideLoadingIndicator)(void); -/** Called when the embedded view's size changes */ -@property (nonatomic, copy, nullable) void (^onEmbeddedSizeChange)(NSString * _Nonnull, CGFloat); - -@end - /** * Custom view class for embedding Rokt widgets in the UI. * Inherits from UIView and provides container functionality for Rokt placements. @@ -77,38 +58,47 @@ typedef NS_ENUM(NSInteger, MPColorMode) { attributes:(NSDictionary * _Nullable)attributes; /** - * Selects a Rokt placement with full configuration options including embedded views and callbacks. + * Selects a Rokt placement with full configuration options including embedded views and event callback. * * @param identifier Unique identifier for the placement * @param attributes Optional dictionary of attributes to customize the placement * @param embeddedViews Optional dictionary mapping placement names to their embedded views - * @param roktEventCallback Optional callback object to handle widget events + * @param config Optional configuration object for customizing the placement display + * @param onEvent Optional callback block to handle Rokt events */ - (void)selectPlacements:(NSString *_Nonnull)identifier attributes:(NSDictionary * _Nullable)attributes embeddedViews:(NSDictionary * _Nullable)embeddedViews config:(MPRoktConfig * _Nullable)config - callbacks:(MPRoktEventCallback * _Nullable)roktEventCallback; + onEvent:(void (^ _Nullable)(MPRoktEvent * _Nonnull))onEvent; /** * Used to report a successful conversion without displaying a placement * - * @param placementId Unique identifier for the placement + * @param identifier Unique identifier for the placement * @param catalogItemId Unique identifier for the catalog item ID * @param success Indicates whether or not the purchase was successful */ -- (void)purchaseFinalized:(NSString *_Nonnull)placementId +- (void)purchaseFinalized:(NSString *_Nonnull)identifier catalogItemId:(NSString *_Nonnull)catalogItemId success:(BOOL)success; /** - * Used to subscribe to Rokt events + * Used to subscribe to Rokt events for a specific placement * * @param identifier The identifier of the placement to subscribe to * @param onEvent The block to execute when the event is triggered */ - (void)events:(NSString *_Nonnull)identifier onEvent:(void (^ _Nullable)(MPRoktEvent * _Nonnull))onEvent; +/** + * Used to subscribe to global Rokt events from all sources. + * Additional events that are not associated with a view (such as InitComplete) will also be delivered. + * + * @param onEvent The block to execute when the event is triggered + */ +- (void)globalEvents:(void (^ _Nonnull)(MPRoktEvent * _Nonnull))onEvent; + /** * Used to close Rokt overlay placements */ diff --git a/mParticle-Apple-SDK/Include/MPRoktEvent.h b/mParticle-Apple-SDK/Include/MPRoktEvent.h index d281d3541..a3178be65 100644 --- a/mParticle-Apple-SDK/Include/MPRoktEvent.h +++ b/mParticle-Apple-SDK/Include/MPRoktEvent.h @@ -1,4 +1,5 @@ #import +#import NS_ASSUME_NONNULL_BEGIN @@ -11,6 +12,7 @@ NS_ASSUME_NONNULL_BEGIN /** Event indicating Rokt initialization completed. */ +NS_SWIFT_NAME(MPRoktEvent.MPRoktInitComplete) @interface MPRoktInitComplete : MPRoktEvent /** @@ -30,18 +32,21 @@ NS_ASSUME_NONNULL_BEGIN /** Event indicating the loading indicator should be shown. */ +NS_SWIFT_NAME(MPRoktEvent.MPRoktShowLoadingIndicator) @interface MPRoktShowLoadingIndicator : MPRoktEvent @end /** Event indicating the loading indicator should be hidden. */ +NS_SWIFT_NAME(MPRoktEvent.MPRoktHideLoadingIndicator) @interface MPRoktHideLoadingIndicator : MPRoktEvent @end /** Event indicating a placement became interactive. */ +NS_SWIFT_NAME(MPRoktEvent.MPRoktPlacementInteractive) @interface MPRoktPlacementInteractive : MPRoktEvent /** @@ -61,6 +66,7 @@ NS_ASSUME_NONNULL_BEGIN /** Event indicating a placement is ready. */ +NS_SWIFT_NAME(MPRoktEvent.MPRoktPlacementReady) @interface MPRoktPlacementReady : MPRoktEvent /** @@ -80,6 +86,7 @@ NS_ASSUME_NONNULL_BEGIN /** Event indicating an offer engagement occurred. */ +NS_SWIFT_NAME(MPRoktEvent.MPRoktOfferEngagement) @interface MPRoktOfferEngagement : MPRoktEvent /** @@ -99,6 +106,7 @@ NS_ASSUME_NONNULL_BEGIN /** Event indicating a URL should be opened. */ +NS_SWIFT_NAME(MPRoktEvent.MPRoktOpenUrl) @interface MPRoktOpenUrl : MPRoktEvent /** @@ -124,6 +132,7 @@ NS_ASSUME_NONNULL_BEGIN /** Event indicating a positive engagement occurred. */ +NS_SWIFT_NAME(MPRoktEvent.MPRoktPositiveEngagement) @interface MPRoktPositiveEngagement : MPRoktEvent /** @@ -143,6 +152,7 @@ NS_ASSUME_NONNULL_BEGIN /** Event indicating a placement was closed. */ +NS_SWIFT_NAME(MPRoktEvent.MPRoktPlacementClosed) @interface MPRoktPlacementClosed : MPRoktEvent /** @@ -162,6 +172,7 @@ NS_ASSUME_NONNULL_BEGIN /** Event indicating a placement was completed. */ +NS_SWIFT_NAME(MPRoktEvent.MPRoktPlacementCompleted) @interface MPRoktPlacementCompleted : MPRoktEvent /** @@ -181,6 +192,7 @@ NS_ASSUME_NONNULL_BEGIN /** Event indicating a placement failure occurred. */ +NS_SWIFT_NAME(MPRoktEvent.MPRoktPlacementFailure) @interface MPRoktPlacementFailure : MPRoktEvent /** @@ -200,6 +212,7 @@ NS_ASSUME_NONNULL_BEGIN /** Event indicating the first positive engagement occurred. */ +NS_SWIFT_NAME(MPRoktEvent.MPRoktFirstPositiveEngagement) @interface MPRoktFirstPositiveEngagement : MPRoktEvent /** @@ -219,6 +232,7 @@ NS_ASSUME_NONNULL_BEGIN /** Event indicating a cart item instant purchase occurred. */ +NS_SWIFT_NAME(MPRoktEvent.MPRoktCartItemInstantPurchase) @interface MPRoktCartItemInstantPurchase : MPRoktEvent /** @@ -300,4 +314,31 @@ NS_ASSUME_NONNULL_BEGIN @end +/** + Event indicating the height of an embedded placement changed. + This event is only emitted for embedded placements. + */ +NS_SWIFT_NAME(MPRoktEvent.MPRoktEmbeddedSizeChanged) +@interface MPRoktEmbeddedSizeChanged : MPRoktEvent + +/** + The placement identifier. + */ +@property (nonatomic, readonly) NSString *placementId; + +/** + The new height of the placement. + */ +@property (nonatomic, readonly) CGFloat updatedHeight; + +/** + Initializes a new embedded size changed event. + @param placementId The placement identifier + @param updatedHeight The new height of the placement + @return A new MPRoktEmbeddedSizeChanged instance + */ +- (instancetype)initWithPlacementId:(NSString *)placementId updatedHeight:(CGFloat)updatedHeight; + +@end + NS_ASSUME_NONNULL_END diff --git a/mParticle-Apple-SDK/Kits/MPKitContainer.m b/mParticle-Apple-SDK/Kits/MPKitContainer.m index 02e186c71..f1fa2eb0b 100644 --- a/mParticle-Apple-SDK/Kits/MPKitContainer.m +++ b/mParticle-Apple-SDK/Kits/MPKitContainer.m @@ -2303,7 +2303,7 @@ - (void)attemptToLogEventToKit:(id)kitRegister kitFilter return; } execStatus = [kitRegister.wrapperInstance logEvent:((MPEvent *)kitFilter.forwardEvent)]; - } else if (selector == @selector(executeWithIdentifier:attributes:embeddedViews:config:callbacks:filteredUser:)) { + } else if (selector == @selector(executeWithIdentifier:attributes:embeddedViews:config:onEvent:filteredUser:)) { if (kitFilter.shouldFilter) { return; } @@ -2313,7 +2313,7 @@ - (void)attemptToLogEventToKit:(id)kitRegister kitFilter attributes:parameters[1] embeddedViews:parameters[2] config:parameters[3] - callbacks:parameters[4] + onEvent:parameters[4] filteredUser:filteredUser]; } else if (selector == @selector(logScreen:)) { if (!kitFilter.forwardEvent || ![kitFilter.forwardEvent isKindOfClass:[MPEvent class]]) { diff --git a/mParticle-Apple-SDK/MPRokt.m b/mParticle-Apple-SDK/MPRokt.m index 6b5c1270a..16d00d990 100644 --- a/mParticle-Apple-SDK/MPRokt.m +++ b/mParticle-Apple-SDK/MPRokt.m @@ -29,9 +29,6 @@ + (dispatch_queue_t)messageQueue; @end -@implementation MPRoktEventCallback -@end - @implementation MPRoktEmbeddedView @end @@ -47,7 +44,7 @@ @implementation MPRokt /// - attributes: Optional dictionary of user attributes to pass to Rokt (e.g., email, firstName, etc.) - (void)selectPlacements:(NSString *)identifier attributes:(NSDictionary * _Nullable)attributes { - [self selectPlacements:identifier attributes:attributes embeddedViews:nil config:nil callbacks:nil]; + [self selectPlacements:identifier attributes:attributes embeddedViews:nil config:nil onEvent:nil]; } /// Displays a Rokt ad placement with full configuration options. @@ -58,12 +55,12 @@ - (void)selectPlacements:(NSString *)identifier /// - attributes: Optional dictionary of user attributes (email, firstName, etc.). Attributes will be mapped according to dashboard configuration. /// - embeddedViews: Optional dictionary mapping placement identifiers to embedded view containers for inline placements /// - config: Optional Rokt configuration object (e.g., for dark mode or custom styling) -/// - callbacks: Optional callback handlers for Rokt events (selection, display, completion, etc.) +/// - onEvent: Optional callback block to handle Rokt events - (void)selectPlacements:(NSString *)identifier attributes:(NSDictionary * _Nullable)attributes embeddedViews:(NSDictionary * _Nullable)embeddedViews config:(MPRoktConfig * _Nullable)config - callbacks:(MPRoktEventCallback * _Nullable)callbacks { + onEvent:(void (^ _Nullable)(MPRoktEvent * _Nonnull))onEvent { MParticleUser *currentUser = [MParticle sharedInstance].identity.currentUser; // If email is passed in as an attribute and it's different than the existing identity, identify with it @@ -95,9 +92,9 @@ - (void)selectPlacements:(NSString *)identifier [queueParameters addParameter:[self confirmSandboxAttribute:mappedAttributes]]; [queueParameters addParameter:embeddedViews]; [queueParameters addParameter:config]; - [queueParameters addParameter:callbacks]; + [queueParameters addParameter:onEvent]; - SEL roktSelector = @selector(executeWithIdentifier:attributes:embeddedViews:config:callbacks:filteredUser:); + SEL roktSelector = @selector(executeWithIdentifier:attributes:embeddedViews:config:onEvent:filteredUser:); [[MParticle sharedInstance].kitContainer_PRIVATE forwardSDKCall:roktSelector event:nil parameters:queueParameters @@ -114,14 +111,14 @@ - (void)selectPlacements:(NSString *)identifier /// Notifies Rokt that a purchase from a placement offer has been finalized. /// Call this method to inform Rokt about the completion status of an offer purchase initiated from a placement. /// - Parameters: -/// - placementId: The identifier of the placement where the offer was displayed +/// - identifier: The identifier of the placement where the offer was displayed /// - catalogItemId: The identifier of the catalog item that was purchased /// - success: Whether the purchase was successful (YES) or failed (NO) -- (void)purchaseFinalized:(NSString * _Nonnull)placementId catalogItemId:(NSString * _Nonnull)catalogItemId success:(BOOL)success { +- (void)purchaseFinalized:(NSString * _Nonnull)identifier catalogItemId:(NSString * _Nonnull)catalogItemId success:(BOOL)success { dispatch_async(dispatch_get_main_queue(), ^{ // Forwarding call to kits MPForwardQueueParameters *queueParameters = [[MPForwardQueueParameters alloc] init]; - [queueParameters addParameter:placementId]; + [queueParameters addParameter:identifier]; [queueParameters addParameter:catalogItemId]; [queueParameters addParameter:@(success)]; @@ -156,6 +153,26 @@ - (void)events:(NSString * _Nonnull)identifier onEvent:(void (^ _Nullable)(MPRok }); } +/// Registers a callback to receive global events from all Rokt sources. +/// Additional events that are not associated with a view (such as InitComplete) will also be delivered. +/// - Parameters: +/// - onEvent: Callback block that receives MPRoktEvent objects when events occur +- (void)globalEvents:(void (^ _Nonnull)(MPRoktEvent * _Nonnull))onEvent { + dispatch_async(dispatch_get_main_queue(), ^{ + // Forwarding call to kits + MPForwardQueueParameters *queueParameters = [[MPForwardQueueParameters alloc] init]; + [queueParameters addParameter:onEvent]; + + SEL roktSelector = @selector(globalEvents:); + [[MParticle sharedInstance].kitContainer_PRIVATE forwardSDKCall:roktSelector + event:nil + parameters:queueParameters + messageType:MPMessageTypeEvent + userInfo:nil + ]; + }); +} + /// Closes any currently displayed Rokt placement. /// Call this method to programmatically dismiss an active Rokt overlay or embedded placement. - (void)close { diff --git a/mParticle-Apple-SDK/MPRoktEvent.m b/mParticle-Apple-SDK/MPRoktEvent.m index f3342fc10..206596ade 100644 --- a/mParticle-Apple-SDK/MPRoktEvent.m +++ b/mParticle-Apple-SDK/MPRoktEvent.m @@ -248,3 +248,23 @@ - (NSString *)description { } @end + +#pragma mark - MPRoktEmbeddedSizeChanged + +@interface MPRoktEmbeddedSizeChanged () +@property (nonatomic, readwrite) NSString *placementId; +@property (nonatomic, readwrite) CGFloat updatedHeight; +@end + +@implementation MPRoktEmbeddedSizeChanged + +- (instancetype)initWithPlacementId:(NSString *)placementId updatedHeight:(CGFloat)updatedHeight { + self = [super init]; + if (self) { + _placementId = placementId; + _updatedHeight = updatedHeight; + } + return self; +} + +@end