diff --git a/kits/rokt/rokt/Package.swift b/kits/rokt/rokt/Package.swift index 8281fd733..ec5d1f081 100644 --- a/kits/rokt/rokt/Package.swift +++ b/kits/rokt/rokt/Package.swift @@ -18,7 +18,7 @@ let package = Package( ), .package( url: "https://github.com/ROKT/rokt-sdk-ios", - .upToNextMajor(from: "4.16.1") + .upToNextMajor(from: "5.0.0") ), .package( url: "https://github.com/erikdoe/ocmock", diff --git a/kits/rokt/rokt/Sources/mParticle-Rokt/MPKitRokt.m b/kits/rokt/rokt/Sources/mParticle-Rokt/MPKitRokt.m index 48387e7e1..bf3941ac6 100644 --- a/kits/rokt/rokt/Sources/mParticle-Rokt/MPKitRokt.m +++ b/kits/rokt/rokt/Sources/mParticle-Rokt/MPKitRokt.m @@ -14,12 +14,12 @@ static NSString * const kMPRoktAttributeKeySandbox = @"sandbox"; // Rokt kit constants -static NSString * const kMPRemoteConfigKitHashesKey = @"hs"; +static NSString * const kMPRoktRemoteConfigKitHashesKey = @"hs"; static NSString * const kMPRemoteConfigUserAttributeFilter = @"ua"; static NSString * const MPKitRoktErrorDomain = @"com.mparticle.kits.rokt"; static NSString * const MPKitRoktErrorMessageKey = @"mParticle-Rokt Error"; -static NSString * const kMPPlacementAttributesMapping = @"placementAttributesMapping"; -static NSString * const kMPHashedEmailUserIdentityType = @"hashedEmailUserIdentityType"; +static NSString * const kMPRoktPlacementAttributesMapping = @"placementAttributesMapping"; +static NSString * const kMPRoktHashedEmailUserIdentityType = @"hashedEmailUserIdentityType"; static NSString * const kMPRoktEmbeddedViewClassName = @"MPRoktEmbeddedView"; static NSString * const kMPEventNameSelectPlacements = @"selectPlacements"; static NSString * const kMPRoktIdentityTypeEmailSha256 = @"emailsha256"; @@ -74,16 +74,22 @@ - (MPKitExecStatus *)didFinishLaunchingWithConfiguration:(NSDictionary *)configu [MPKitRokt applyMParticleLogLevel]; - [Rokt initWithRoktTagId:partnerId mParticleSdkVersion:sdkVersion mParticleKitVersion:kMPRoktKitVersion onInitComplete:^(BOOL InitComplete) { - if (InitComplete) { - [self start]; - [MPKitRokt MPLog:@"Rokt Init Complete"]; - NSDictionary *userInfo = @{mParticleKitInstanceKey:[[self class] kitCode]}; - [[NSNotificationCenter defaultCenter] postNotificationName:@"mParticle.Rokt.Initialized" - object:nil - userInfo:userInfo]; + // Subscribe to global events to receive InitComplete + [Rokt globalEventsOnEvent:^(RoktEvent * _Nonnull event) { + if ([event isKindOfClass:[InitComplete class]]) { + InitComplete *initComplete = (InitComplete *)event; + if (initComplete.success) { + [self start]; + [MPKitRokt MPLog:@"Rokt Init Complete"]; + NSDictionary *userInfo = @{mParticleKitInstanceKey:[[self class] kitCode]}; + [[NSNotificationCenter defaultCenter] postNotificationName:@"mParticle.Rokt.Initialized" + object:nil + userInfo:userInfo]; + } } }]; + + [Rokt initWithRoktTagId:partnerId mParticleSdkVersion:sdkVersion mParticleKitVersion:kMPRoktKitVersion]; return [self execStatus:MPKitReturnCodeSuccess]; } @@ -111,7 +117,7 @@ - (void)start { /// @param attributes Dictionary of user attributes (email, firstName, etc.). Attributes will be mapped according to dashboard configuration. /// @param embeddedViews Optional dictionary mapping placement identifiers to embedded view containers for inline placements /// @param mpRoktConfig Optional Rokt configuration object (e.g., for dark mode or custom styling) -/// @param callbacks Object that contains all possible callbacks for selectPlacements +/// @param onEvent Callback block that receives RoktEvent objects for all placement events /// @param filteredUser The current user when this placement was requested. Filtered for the kit as per settings in the mParticle UI /// @return MPKitExecStatus indicating success or failure of the operation - (MPKitExecStatus *)executeWithIdentifier:(NSString * _Nullable)identifier @@ -136,26 +142,19 @@ - (MPKitExecStatus *)executeWithIdentifier:(NSString * _Nullable)identifier placementOptions = [[PlacementOptions alloc] initWithJointSdkSelectPlacements:options.jointSdkSelectPlacements dynamicPerformanceMarkers:@{}]; } - [Rokt executeWithViewName:identifier - attributes:finalAtt - placements:confirmedViews - config:roktConfig - placementOptions:placementOptions - onLoad:nil - onUnLoad:nil - onShouldShowLoadingIndicator:nil - onShouldHideLoadingIndicator:nil - onEmbeddedSizeChange:nil - ]; - - if (onEvent) { - [Rokt eventsWithViewName:identifier onEvent:^(RoktEvent * _Nonnull event) { + [Rokt selectPlacementsWithIdentifier:identifier + attributes:finalAtt + placements:confirmedViews + config:roktConfig + placementOptions:placementOptions + onEvent:^(RoktEvent * _Nonnull event) { + if (onEvent) { MPRoktEvent *mpEvent = [MPKitRokt mapEvent:event]; if (mpEvent) { onEvent(mpEvent); } - }]; - } + } + }]; return [[MPKitExecStatus alloc] initWithSDKCode:[[self class] kitCode] returnCode:MPKitReturnCodeSuccess]; } @@ -289,11 +288,7 @@ - (RoktFrameworkType)mapMPWrapperSdkToRoktFrameworkType:(MPWrapperSdk)wrapperSdk transformedDictionary[key] = [numberAttribute stringValue]; } } else if ([obj isKindOfClass:[NSDate class]]) { - NSDateFormatter *rfc3339Formatter = [[NSDateFormatter alloc] init]; - rfc3339Formatter.locale = [NSLocale localeWithLocaleIdentifier:@"en_US_POSIX"]; - rfc3339Formatter.dateFormat = @"yyyy'-'MM'-'dd'T'HH':'mm':'ssZ"; - rfc3339Formatter.timeZone = [NSTimeZone timeZoneForSecondsFromGMT:0]; - transformedDictionary[key] = [rfc3339Formatter stringFromDate:obj]; + transformedDictionary[key] = [MPKitAPI stringFromDateRFC3339:obj]; } else if ([obj isKindOfClass:[NSData class]] && [(NSData *)obj length] > 0) { transformedDictionary[key] = [[NSString alloc] initWithData:obj encoding:NSUTF8StringEncoding]; } else if ([obj isKindOfClass:[NSDictionary class]]) { @@ -333,7 +328,7 @@ - (RoktFrameworkType)mapMPWrapperSdkToRoktFrameworkType:(MPWrapperSdk)wrapperSdk NSData *dataAttributeMap; // Rokt Kit is available though there may not be an attribute map attributeMap = @[]; - id configJSONString = roktKitConfig[kMPRemoteConfigKitConfigurationKey][kMPPlacementAttributesMapping]; + id configJSONString = roktKitConfig[kMPRemoteConfigKitConfigurationKey][kMPRoktPlacementAttributesMapping]; if (configJSONString != nil && configJSONString != [NSNull null]) { strAttributeMap = [configJSONString stringByRemovingPercentEncoding]; dataAttributeMap = [strAttributeMap dataUsingEncoding:NSUTF8StringEncoding]; @@ -523,7 +518,7 @@ + (NSNumber * _Nullable)getRoktHashedEmailUserIdentityType { NSDictionary *roktKitConfig = [MPKitRokt getKitConfig]; // Get the string representing which identity to use and convert it to the key (NSNumber) - NSString *hashedIdentityTypeString = roktKitConfig[kMPRemoteConfigKitConfigurationKey][kMPHashedEmailUserIdentityType]; + NSString *hashedIdentityTypeString = roktKitConfig[kMPRemoteConfigKitConfigurationKey][kMPRoktHashedEmailUserIdentityType]; NSNumber *hashedIdentityTypeNumber = [MPKitRokt identityTypeForString:hashedIdentityTypeString.lowercaseString]; return hashedIdentityTypeNumber; @@ -535,19 +530,26 @@ + (NSNumber * _Nullable)getRoktHashedEmailUserIdentityType { /// @param catalogItemId The identifier of the catalog item that was purchased /// @param success Whether the purchase was successful (YES) or failed (NO) /// @return MPKitExecStatus indicating success or failure of the operation -- (MPKitExecStatus *)purchaseFinalized:(NSString *)placementId catalogItemId:(NSString *)catalogItemId success:(NSNumber *)success { - if (placementId != nil && catalogItemId != nil && success != nil) { - if (@available(iOS 15.0, *)) { - [Rokt purchaseFinalizedWithPlacementId:placementId catalogItemId:catalogItemId success:success.boolValue]; - return [[MPKitExecStatus alloc] initWithSDKCode:[[self class] kitCode] returnCode:MPKitReturnCodeSuccess]; - } - return [[MPKitExecStatus alloc] initWithSDKCode:[[self class] kitCode] returnCode:MPKitReturnCodeUnavailable]; +- (MPKitExecStatus *)purchaseFinalized:(NSString *)identifier catalogItemId:(NSString *)catalogItemId success:(NSNumber *)success { + if (identifier != nil && catalogItemId != nil && success != nil) { + [Rokt purchaseFinalizedWithIdentifier:identifier catalogItemId:catalogItemId success:success.boolValue]; + return [[MPKitExecStatus alloc] initWithSDKCode:[[self class] kitCode] returnCode:MPKitReturnCodeSuccess]; } return [[MPKitExecStatus alloc] initWithSDKCode:[[self class] kitCode] returnCode:MPKitReturnCodeFail]; } - (MPKitExecStatus *)events:(NSString *)identifier onEvent:(void (^)(MPRoktEvent * _Nonnull))onEvent { - [Rokt eventsWithViewName:identifier onEvent:^(RoktEvent * _Nonnull event) { + [Rokt eventsWithIdentifier:identifier onEvent:^(RoktEvent * _Nonnull event) { + MPRoktEvent *mpEvent = [MPKitRokt mapEvent:event]; + if (mpEvent) { + onEvent(mpEvent); + } + }]; + return [[MPKitExecStatus alloc] initWithSDKCode:[[self class] kitCode] returnCode:MPKitReturnCodeSuccess]; +} + +- (MPKitExecStatus *)globalEvents:(void (^)(MPRoktEvent * _Nonnull))onEvent { + [Rokt globalEventsOnEvent:^(RoktEvent * _Nonnull event) { MPRoktEvent *mpEvent = [MPKitRokt mapEvent:event]; if (mpEvent) { onEvent(mpEvent); @@ -969,6 +971,13 @@ + (MPRoktEvent * _Nullable)mapEvent:(RoktEvent *)event { return [[MPRoktFirstPositiveEngagement alloc] initWithPlacementId:firstPositiveEngagement.placementId]; } + // Check for RoktEvent.EmbeddedSizeChanged + if ([event isKindOfClass:[EmbeddedSizeChanged class]]) { + EmbeddedSizeChanged *embeddedSizeChanged = (EmbeddedSizeChanged *)event; + return [[MPRoktEmbeddedSizeChanged alloc] initWithPlacementId:embeddedSizeChanged.placementId + updatedHeight:embeddedSizeChanged.updatedHeight]; + } + // Check for RoktEvent.CartItemInstantPurchase if ([event isKindOfClass:[CartItemInstantPurchase class]]) { CartItemInstantPurchase *cartItemInstantPurchase = (CartItemInstantPurchase *)event; diff --git a/kits/rokt/rokt/Sources/mParticle-Rokt/include/MPKitRokt.h b/kits/rokt/rokt/Sources/mParticle-Rokt/include/MPKitRokt.h index bb8c852e2..4087c6ec3 100644 --- a/kits/rokt/rokt/Sources/mParticle-Rokt/include/MPKitRokt.h +++ b/kits/rokt/rokt/Sources/mParticle-Rokt/include/MPKitRokt.h @@ -1,8 +1,6 @@ #import #if defined(__has_include) && __has_include() #import -#elif defined(__has_include) && __has_include() - #import #else #import "mParticle.h" #endif diff --git a/kits/rokt/rokt/Tests/mParticle-RoktObjCTests/mParticle_RoktTests.m b/kits/rokt/rokt/Tests/mParticle-RoktObjCTests/mParticle_RoktTests.m index fd115df07..3a72d37ad 100644 --- a/kits/rokt/rokt/Tests/mParticle-RoktObjCTests/mParticle_RoktTests.m +++ b/kits/rokt/rokt/Tests/mParticle-RoktObjCTests/mParticle_RoktTests.m @@ -1,10 +1,10 @@ #import #import -#import +@import Rokt_Widget; @import mParticle_Rokt; static NSInteger const kMPRoktKitCode = 181; -static NSString * const kMPHashedEmailUserIdentityType = @"hashedEmailUserIdentityType"; +static NSString * const kMPRoktHashedEmailUserIdentityType = @"hashedEmailUserIdentityType"; @interface MPKitRokt () @@ -156,20 +156,16 @@ - (void)testExecuteWithIdentifier { NSDictionary *attributes = @{@"attr1": @"value1", @"sandbox": @"false"}; FilteredMParticleUser *user = [[FilteredMParticleUser alloc] init]; - // Expect Rokt execute call and verify sandbox attribute is preserved + // Expect Rokt selectPlacements call and verify sandbox attribute is preserved // Note: attributes may include additional device identifiers (idfa, idfv, mpid) - OCMExpect([mockRoktSDK executeWithViewName:identifier - attributes:[OCMArg checkWithBlock:^BOOL(NSDictionary *attrs) { - return [attrs[@"sandbox"] isEqualToString:@"false"]; - }] - placements:OCMOCK_ANY - config:nil - placementOptions:OCMOCK_ANY - onLoad:nil - onUnLoad:nil - onShouldShowLoadingIndicator:nil - onShouldHideLoadingIndicator:nil - onEmbeddedSizeChange:nil]); + OCMExpect([mockRoktSDK selectPlacementsWithIdentifier:identifier + attributes:[OCMArg checkWithBlock:^BOOL(NSDictionary *attrs) { + return [attrs[@"sandbox"] isEqualToString:@"false"]; + }] + placements:OCMOCK_ANY + config:nil + placementOptions:OCMOCK_ANY + onEvent:OCMOCK_ANY]); MPKitExecStatus *status = [self.kitInstance executeWithIdentifier:identifier attributes:attributes @@ -194,21 +190,17 @@ - (void)testExecuteSandboxDetection { NSDictionary *attributes = @{@"attr1": @"value1"}; // No sandbox attribute provided FilteredMParticleUser *user = [[FilteredMParticleUser alloc] init]; - // Expect Rokt execute call and verify sandbox attribute is auto-detected + // Expect Rokt selectPlacements call and verify sandbox attribute is auto-detected // In development environment, sandbox should be "true" // Note: attributes may include additional device identifiers (idfa, idfv, mpid) - OCMExpect([mockRoktSDK executeWithViewName:identifier - attributes:[OCMArg checkWithBlock:^BOOL(NSDictionary *attrs) { - return attrs[@"sandbox"] != nil; // Sandbox should be auto-added - }] - placements:OCMOCK_ANY - config:nil - placementOptions:OCMOCK_ANY - onLoad:nil - onUnLoad:nil - onShouldShowLoadingIndicator:nil - onShouldHideLoadingIndicator:nil - onEmbeddedSizeChange:nil]); + OCMExpect([mockRoktSDK selectPlacementsWithIdentifier:identifier + attributes:[OCMArg checkWithBlock:^BOOL(NSDictionary *attrs) { + return attrs[@"sandbox"] != nil; // Sandbox should be auto-added + }] + placements:OCMOCK_ANY + config:nil + placementOptions:OCMOCK_ANY + onEvent:OCMOCK_ANY]); MPKitExecStatus *status = [self.kitInstance executeWithIdentifier:identifier attributes:attributes @@ -236,19 +228,15 @@ - (void)testExecuteWithIdentifierWithOptions { // Create placement options with a custom timestamp value MPRoktPlacementOptions *options = [[MPRoktPlacementOptions alloc] initWithTimestamp:42]; - // Expect Rokt execute call and verify placementOptions carries the jointSdkSelectPlacements value - OCMExpect([mockRoktSDK executeWithViewName:identifier - attributes:OCMOCK_ANY - placements:OCMOCK_ANY - config:nil - placementOptions:[OCMArg checkWithBlock:^BOOL(PlacementOptions *opts) { - return opts != nil; - }] - onLoad:nil - onUnLoad:nil - onShouldShowLoadingIndicator:nil - onShouldHideLoadingIndicator:nil - onEmbeddedSizeChange:nil]); + // Expect Rokt selectPlacements call and verify placementOptions carries the jointSdkSelectPlacements value + OCMExpect([mockRoktSDK selectPlacementsWithIdentifier:identifier + attributes:OCMOCK_ANY + placements:OCMOCK_ANY + config:nil + placementOptions:[OCMArg checkWithBlock:^BOOL(PlacementOptions *opts) { + return opts != nil; + }] + onEvent:OCMOCK_ANY]); MPKitExecStatus *status = [self.kitInstance executeWithIdentifier:identifier attributes:attributes @@ -272,18 +260,14 @@ - (void)testExecuteWithIdentifierNilOptionsCreatesDefaultPlacementOptions { FilteredMParticleUser *user = [[FilteredMParticleUser alloc] init]; // When options is nil, a default PlacementOptions with jointSdkSelectPlacements=0 should be created - OCMExpect([mockRoktSDK executeWithViewName:identifier - attributes:OCMOCK_ANY - placements:OCMOCK_ANY - config:nil - placementOptions:[OCMArg checkWithBlock:^BOOL(PlacementOptions *opts) { - return opts != nil; - }] - onLoad:nil - onUnLoad:nil - onShouldShowLoadingIndicator:nil - onShouldHideLoadingIndicator:nil - onEmbeddedSizeChange:nil]); + OCMExpect([mockRoktSDK selectPlacementsWithIdentifier:identifier + attributes:OCMOCK_ANY + placements:OCMOCK_ANY + config:nil + placementOptions:[OCMArg checkWithBlock:^BOOL(PlacementOptions *opts) { + return opts != nil; + }] + onEvent:OCMOCK_ANY]); MPKitExecStatus *status = [self.kitInstance executeWithIdentifier:identifier attributes:attributes @@ -596,28 +580,26 @@ - (void)testSetWrapperSdk { } - (void)testPurchaseFinalized { - if (@available(iOS 15.0, *)) { - id mockRoktSDK = OCMClassMock([Rokt class]); - - // Set up test parameters - NSString *placementId = @"testonversion"; - NSString *catalogItemId = @"testcatalogItemId"; - BOOL success = YES; - - // Expect Rokt reportConversion call with correct parameters - OCMExpect([mockRoktSDK purchaseFinalizedWithPlacementId:placementId - catalogItemId:catalogItemId - success:success]); - - MPKitExecStatus *status = [self.kitInstance purchaseFinalized:placementId - catalogItemId:catalogItemId - success:@(success)]; - - // Verify - XCTAssertNotNil(status); - XCTAssertEqual(status.returnCode, MPKitReturnCodeSuccess); - OCMVerifyAll(mockRoktSDK); - } + id mockRoktSDK = OCMClassMock([Rokt class]); + + // Set up test parameters + NSString *identifier = @"testonversion"; + NSString *catalogItemId = @"testcatalogItemId"; + BOOL success = YES; + + // Expect Rokt purchaseFinalized call with correct parameters + OCMExpect([mockRoktSDK purchaseFinalizedWithIdentifier:identifier + catalogItemId:catalogItemId + success:success]); + + MPKitExecStatus *status = [self.kitInstance purchaseFinalized:identifier + catalogItemId:catalogItemId + success:@(success)]; + + // Verify + XCTAssertNotNil(status); + XCTAssertEqual(status.returnCode, MPKitReturnCodeSuccess); + OCMVerifyAll(mockRoktSDK); } - (void)testEvents_Success { @@ -628,7 +610,7 @@ - (void)testEvents_Success { __block MPRoktEvent *receivedEvent = nil; // Mock the Rokt SDK call and simulate triggering the callback with a mock event - OCMStub([mockRoktSDK eventsWithViewName:identifier onEvent:[OCMArg any]]).andDo(^(NSInvocation *invocation) { + OCMStub([mockRoktSDK eventsWithIdentifier:identifier onEvent:[OCMArg any]]).andDo(^(NSInvocation *invocation) { // Get the callback block from the invocation void (^onEventCallback)(RoktEvent *) = nil; [invocation getArgument:&onEventCallback atIndex:3]; // Index 3 is the second parameter (onEvent) @@ -649,7 +631,7 @@ - (void)testEvents_Success { }]; // Verify the Rokt SDK method was called - OCMVerify([mockRoktSDK eventsWithViewName:identifier onEvent:[OCMArg any]]); + OCMVerify([mockRoktSDK eventsWithIdentifier:identifier onEvent:[OCMArg any]]); // Verify the return status XCTAssertNotNil(status); @@ -671,7 +653,7 @@ - (void)testEvents_MappingReturnsNil { __block BOOL callbackCalled = NO; // Mock the Rokt SDK call and simulate triggering the callback with a mock event - OCMStub([mockRoktSDK eventsWithViewName:identifier onEvent:[OCMArg any]]).andDo(^(NSInvocation *invocation) { + OCMStub([mockRoktSDK eventsWithIdentifier:identifier onEvent:[OCMArg any]]).andDo(^(NSInvocation *invocation) { // Get the callback block from the invocation void (^onEventCallback)(RoktEvent *) = nil; [invocation getArgument:&onEventCallback atIndex:3]; @@ -691,7 +673,7 @@ - (void)testEvents_MappingReturnsNil { }]; // Verify the Rokt SDK method was called - OCMVerify([mockRoktSDK eventsWithViewName:identifier onEvent:[OCMArg any]]); + OCMVerify([mockRoktSDK eventsWithIdentifier:identifier onEvent:[OCMArg any]]); // Verify the return status XCTAssertNotNil(status); @@ -710,7 +692,7 @@ - (void)testEvents_NilIdentifier { __block BOOL callbackCalled = NO; // The Rokt SDK should still be called even with nil identifier - OCMExpect([mockRoktSDK eventsWithViewName:@"" onEvent:[OCMArg any]]); + OCMExpect([mockRoktSDK eventsWithIdentifier:@"" onEvent:[OCMArg any]]); // Execute the method under test MPKitExecStatus *status = [self.kitInstance events:identifier onEvent:^(MPRoktEvent * _Nonnull event) { @@ -718,7 +700,7 @@ - (void)testEvents_NilIdentifier { }]; // Verify the Rokt SDK method was called - OCMVerify([mockRoktSDK eventsWithViewName:@"" onEvent:[OCMArg any]]); + OCMVerify([mockRoktSDK eventsWithIdentifier:@"" onEvent:[OCMArg any]]); // Verify the return status XCTAssertNotNil(status); @@ -776,7 +758,7 @@ - (void)testGetRoktHashedEmailUserIdentityTypeOther4 { NSDictionary *roktKitConfig = @{ @"id": @(kMPRoktKitCode), @"as": @{ - kMPHashedEmailUserIdentityType: @"other4" + kMPRoktHashedEmailUserIdentityType: @"other4" } }; @@ -810,7 +792,7 @@ - (void)testGetRoktHashedEmailUserIdentityTypeNil { NSDictionary *roktKitConfigNoHash = @{ @"id": @(kMPRoktKitCode), @"as": @{ - // No kMPHashedEmailUserIdentityType specified + // No kMPRoktHashedEmailUserIdentityType specified } }; [[[mockMPKitRoktClass stub] andReturn:roktKitConfigNoHash] getKitConfig]; diff --git a/kits/rokt/rokt/mParticle-Rokt.podspec b/kits/rokt/rokt/mParticle-Rokt.podspec index cfe207cd4..8b3c9ab9e 100644 --- a/kits/rokt/rokt/mParticle-Rokt.podspec +++ b/kits/rokt/rokt/mParticle-Rokt.podspec @@ -14,5 +14,5 @@ Pod::Spec.new do |s| s.ios.source_files = 'kits/rokt/rokt/Sources/mParticle-Rokt/**/*.{h,m,mm}', 'kits/rokt/rokt/Sources/mParticle-Rokt-Swift/**/*.swift' s.ios.resource_bundles = { 'mParticle-Rokt-Privacy' => ['kits/rokt/rokt/Sources/mParticle-Rokt/PrivacyInfo.xcprivacy'] } s.ios.dependency 'mParticle-Apple-SDK/mParticle', '~> 9.0' - s.ios.dependency 'Rokt-Widget', '~> 4.16' + s.ios.dependency 'Rokt-Widget', '~> 5.0' end