diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000..582ba4d Binary files /dev/null and b/.DS_Store differ diff --git a/README.md b/README.md index 1de2f1e..9a30165 100644 --- a/README.md +++ b/README.md @@ -5,15 +5,20 @@ This app demonstrates Singular's best practices for implementing the iOS SDK. This sample contains examples of: -- Initializing the SDK +- Requesting App Tracking Transparency +- Initializing the Singular SDK +- Setting Singular configuration options +- Enabling SkAdNetwork and Conversion Value management +- Setting Data Privacy option for GDPR or CCPA compliance - Setting custom user id - Sending custom events -- Sending simple revenue events -- Handling deep links / deferred deep links +- Sending revenue events +- Handling deep links / deferred deep links and passthroughs +- Enable ESP Tracking Domain ## Getting Started To run the sample app you'll have to configure your own API Key / Secret as follows: -1. Retrieve the API Key & Secret from your account's SDK Keys page +1. Retrieve the API Key & Secret from your account's SDK Keys page, here: https://app.singular.net/?#/sdk 2. Open the ios-sample-app project in XCode 3. Go to the Constants.h file and replace the values of `APIKEY` & `SECRET` with your credentials diff --git a/ios-sample-app/.DS_Store b/ios-sample-app/.DS_Store new file mode 100644 index 0000000..8490980 Binary files /dev/null and b/ios-sample-app/.DS_Store differ diff --git a/ios-sample-app/Singular-SDK/.DS_Store b/ios-sample-app/Singular-SDK/.DS_Store new file mode 100644 index 0000000..f2f9e4b Binary files /dev/null and b/ios-sample-app/Singular-SDK/.DS_Store differ diff --git a/ios-sample-app/Singular-SDK/Singular.h b/ios-sample-app/Singular-SDK/Singular.h index 06a3c44..875dcbd 100644 --- a/ios-sample-app/Singular-SDK/Singular.h +++ b/ios-sample-app/Singular-SDK/Singular.h @@ -8,6 +8,7 @@ #import #import #import +#import "SingularAdData.h" // avoid circular include of SingularLinkParams #ifndef SingularLinkParams @@ -100,6 +101,7 @@ + (void)unsetCustomUserId; + (void)setSessionTimeout:(int)timeout; ++ (NSInteger)getFirstSessionUnixTime; + (void)setWrapperName:(NSString*)name andVersion:(NSString*)version; @@ -126,4 +128,12 @@ + (BOOL)skanUpdateConversionValue:(NSInteger)conversionValue; + (NSNumber *)skanGetConversionValue; +/* Singular Links */ + ++(BOOL)isSingularLink:(id)linkHolder; + +/* Admon Methods*/ + ++ (void)adRevenue:(SingularAdData*)adData; + @end diff --git a/ios-sample-app/Singular-SDK/SingularAdData.h b/ios-sample-app/Singular-SDK/SingularAdData.h new file mode 100644 index 0000000..cd3bb71 --- /dev/null +++ b/ios-sample-app/Singular-SDK/SingularAdData.h @@ -0,0 +1,33 @@ +// +// SingularAdData.h +// Singular +// +// Created by Eyal Rabinovich on 13/06/2019. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface SingularAdData : NSMutableDictionary + +- (id)initWithAdPlatfrom:(NSString*)adPlatform withCurrency:(NSString*)currency withRevenue:(NSNumber*)revenue; + +- (void)setNetworkName:(NSString*)networkName; +- (void)setAdType:(NSString*)adType; +- (void)setGroupType:(NSString*)adGroupType; +- (void)setImpressionId:(NSString*)impressionId; +- (void)setAdPlacementName:(NSString*)adPlacementName; +- (void)setAdUnitId:(NSString*)adUnitId; +- (void)setAdGroupId:(NSString*)adGroupId; +- (void)setAdGroupName:(NSString*)adGroupName; +- (void)setAdGroupPriority:(NSString*)adGroupPriority; +- (void)setPrecision:(NSString*)precision; +- (void)setPlacementId:(NSString*)placementId; +- (void)setAdUnitName:(NSString*)adUnitName; + +- (BOOL)hasRequiredParams; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ios-sample-app/Singular-SDK/SingularConfig.h b/ios-sample-app/Singular-SDK/SingularConfig.h index 1acda74..f71249d 100644 --- a/ios-sample-app/Singular-SDK/SingularConfig.h +++ b/ios-sample-app/Singular-SDK/SingularConfig.h @@ -25,6 +25,7 @@ @property void(^singularLinksHandler)(SingularLinkParams*); @property long shortLinkResolveTimeOut; @property NSArray* supportedDomains; +@property NSArray* espDomains; // Global Properties fields @property (readonly) NSMutableDictionary* globalProperties; @@ -38,5 +39,6 @@ -(id)initWithApiKey:(NSString*)apikey andSecret:(NSString*)secret; -(void)setGlobalProperty:(NSString*)key withValue:(NSString*)value overrideExisting:(BOOL)overrideExisiting; +-(NSString*)toJsonString; @end diff --git a/ios-sample-app/Singular-SDK/libSingular.a b/ios-sample-app/Singular-SDK/libSingular.a index 62fa53e..a02b673 100644 Binary files a/ios-sample-app/Singular-SDK/libSingular.a and b/ios-sample-app/Singular-SDK/libSingular.a differ diff --git a/ios-sample-app/ios-sample-app.xcodeproj/project.pbxproj b/ios-sample-app/ios-sample-app.xcodeproj/project.pbxproj index 3b5b34d..bfb6a8c 100644 --- a/ios-sample-app/ios-sample-app.xcodeproj/project.pbxproj +++ b/ios-sample-app/ios-sample-app.xcodeproj/project.pbxproj @@ -13,7 +13,6 @@ 2162674A238432070019FD74 /* libz.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 21626749238432070019FD74 /* libz.tbd */; }; 2162674C2384320C0019FD74 /* AdSupport.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2162674B2384320C0019FD74 /* AdSupport.framework */; }; 2162674E238432110019FD74 /* iAd.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2162674D238432110019FD74 /* iAd.framework */; }; - 216267542384340D0019FD74 /* libSingular.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 216267502384340D0019FD74 /* libSingular.a */; }; 216267552384340D0019FD74 /* Singular.js in Resources */ = {isa = PBXBuildFile; fileRef = 216267512384340D0019FD74 /* Singular.js */; }; 2162675823843BE70019FD74 /* CustomEventsController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2162675723843BE70019FD74 /* CustomEventsController.m */; }; 2162675C2384464C0019FD74 /* RevenueController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2162675B2384464C0019FD74 /* RevenueController.m */; }; @@ -28,6 +27,9 @@ 21DB6DC2237C036E008E845A /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 21DB6DC0237C036E008E845A /* LaunchScreen.storyboard */; }; 21DB6DC5237C036E008E845A /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 21DB6DC4237C036E008E845A /* main.m */; }; 27D1C9D924DC57120023B35F /* AppTrackingTransparency.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 27D1C9D824DC57120023B35F /* AppTrackingTransparency.framework */; }; + 7FD16A032746D2C90065908B /* WebKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7FD16A022746D2C90065908B /* WebKit.framework */; }; + 7FD16A052746D2D30065908B /* StoreKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7FD16A042746D2D30065908B /* StoreKit.framework */; }; + 7FD16A062746D2F00065908B /* libSingular.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 216267502384340D0019FD74 /* libSingular.a */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ @@ -64,6 +66,8 @@ 21DB6DC3237C036E008E845A /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 21DB6DC4237C036E008E845A /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 27D1C9D824DC57120023B35F /* AppTrackingTransparency.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppTrackingTransparency.framework; path = System/Library/Frameworks/AppTrackingTransparency.framework; sourceTree = SDKROOT; }; + 7FD16A022746D2C90065908B /* WebKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WebKit.framework; path = System/Library/Frameworks/WebKit.framework; sourceTree = SDKROOT; }; + 7FD16A042746D2D30065908B /* StoreKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = StoreKit.framework; path = System/Library/Frameworks/StoreKit.framework; sourceTree = SDKROOT; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -71,6 +75,9 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 7FD16A062746D2F00065908B /* libSingular.a in Frameworks */, + 7FD16A052746D2D30065908B /* StoreKit.framework in Frameworks */, + 7FD16A032746D2C90065908B /* WebKit.framework in Frameworks */, 216EAB98261F20B00085D00C /* AdServices.framework in Frameworks */, 27D1C9D924DC57120023B35F /* AppTrackingTransparency.framework in Frameworks */, 2162674E238432110019FD74 /* iAd.framework in Frameworks */, @@ -79,7 +86,6 @@ 21626748238432010019FD74 /* Security.framework in Frameworks */, 21626746238431F80019FD74 /* SystemConfiguration.framework in Frameworks */, 21626744238431F10019FD74 /* libsqlite3.0.tbd in Frameworks */, - 216267542384340D0019FD74 /* libSingular.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -89,6 +95,8 @@ 21626742238431F10019FD74 /* Frameworks */ = { isa = PBXGroup; children = ( + 7FD16A042746D2D30065908B /* StoreKit.framework */, + 7FD16A022746D2C90065908B /* WebKit.framework */, 216EAB97261F20B00085D00C /* AdServices.framework */, 27D1C9D824DC57120023B35F /* AppTrackingTransparency.framework */, 2162674D238432110019FD74 /* iAd.framework */, @@ -191,7 +199,7 @@ 21DB6DA7237C036B008E845A /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 1110; + LastUpgradeCheck = 1310; ORGANIZATIONNAME = "Singular Labs"; TargetAttributes = { 21DB6DAE237C036B008E845A = { @@ -295,6 +303,7 @@ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; @@ -354,6 +363,7 @@ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; @@ -385,10 +395,11 @@ isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = YES; CODE_SIGN_ENTITLEMENTS = "ios-sample-app/iOS Sample App.entitlements"; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - DEVELOPMENT_TEAM = DZG6XY7PWJ; + DEVELOPMENT_TEAM = UL6RU95W7J; INFOPLIST_FILE = "ios-sample-app/Info.plist"; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", @@ -398,7 +409,8 @@ "${PROJECT_DIR}/Singular-SDK", "$(PROJECT_DIR)/Singular-SDK", ); - PRODUCT_BUNDLE_IDENTIFIER = ios.sample.app; + MARKETING_VERSION = 2.0; + PRODUCT_BUNDLE_IDENTIFIER = ios.sample.app.objc; PRODUCT_NAME = iOSSampleApp; PROVISIONING_PROFILE_SPECIFIER = ""; TARGETED_DEVICE_FAMILY = "1,2"; @@ -409,10 +421,11 @@ isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = YES; CODE_SIGN_ENTITLEMENTS = "ios-sample-app/iOS Sample App.entitlements"; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - DEVELOPMENT_TEAM = S84G4K2QJ2; + DEVELOPMENT_TEAM = UL6RU95W7J; INFOPLIST_FILE = "ios-sample-app/Info.plist"; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", @@ -422,7 +435,8 @@ "${PROJECT_DIR}/Singular-SDK", "$(PROJECT_DIR)/Singular-SDK", ); - PRODUCT_BUNDLE_IDENTIFIER = ios.sample.app4; + MARKETING_VERSION = 2.0; + PRODUCT_BUNDLE_IDENTIFIER = ios.sample.app.objc; PRODUCT_NAME = iOSSampleApp; PROVISIONING_PROFILE_SPECIFIER = ""; TARGETED_DEVICE_FAMILY = "1,2"; diff --git a/ios-sample-app/ios-sample-app/.DS_Store b/ios-sample-app/ios-sample-app/.DS_Store new file mode 100644 index 0000000..e51992b Binary files /dev/null and b/ios-sample-app/ios-sample-app/.DS_Store differ diff --git a/ios-sample-app/ios-sample-app/AppDelegate.h b/ios-sample-app/ios-sample-app/AppDelegate.h index f33599f..f764902 100644 --- a/ios-sample-app/ios-sample-app/AppDelegate.h +++ b/ios-sample-app/ios-sample-app/AppDelegate.h @@ -3,17 +3,24 @@ // ios-sample-app // // Created by Eyal Rabinovich on 13/11/2019. -// Copyright © 2019 Singular Labs. All rights reserved. +// Updated by Jared Ornstead on 2021/11/19 +// Copyright © 2021 Singular Labs. All rights reserved. // #import +#import @interface AppDelegate : UIResponder { NSDictionary* deeplinkData; + //NSString* s_idfa; + //NSString* s_idfv; } @property (strong, nonatomic) UIWindow *window; @property (nonatomic, retain) NSDictionary* deeplinkData; +//@property (nonatomic, retain) NSString* att_state; +//@property (nonatomic, retain) NSString* s_idfa; +//@property (nonatomic, retain) NSString* s_idfv; @end diff --git a/ios-sample-app/ios-sample-app/AppDelegate.m b/ios-sample-app/ios-sample-app/AppDelegate.m index 85551c4..4efc641 100644 --- a/ios-sample-app/ios-sample-app/AppDelegate.m +++ b/ios-sample-app/ios-sample-app/AppDelegate.m @@ -3,19 +3,39 @@ // ios-sample-app // // Created by Eyal Rabinovich on 13/11/2019. -// Copyright © 2019 Singular Labs. All rights reserved. +// Updated by Jared Ornstead on 2021/11/19 +// Copyright © 2021 Singular Labs. All rights reserved. +// +/* + This sample app illustrates working examples of a Singular implementation in Obj-C. + Console Logging and Event have been configured to out activity to the Singular SDK Console. + To build this App you must: + 1. update your SDK Key and Secret in the Constants File. + 2. update your Associated Domain capabilitiy per: https://support.singular.net/hc/en-us/articles/360031371451-Singular-Links-Prerequisites?navigation_side_bar=true + 3. update your URL scheme in the Target App > Info > URL Types + 4. update the bundle identifier in the Target App > General tab to something unqiue + + You will find a View Controller illustrating each method to invoke Custom Events and Revenue Events. Examples + are provided where possible. + + The Identity Controller will demonstrate the methods for Data Privacy and allows you to share + your identifiers or testing purposes. +*/ // - #import "AppDelegate.h" #import #import "Singular.h" #import "Utils.h" #import "TabController.h" #import "Constants.h" +#import +#import #import #import +#define SYSTEM_VERSION_GRATERTHAN_OR_EQUALTO(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] != NSOrderedAscending) + @interface AppDelegate () @end @@ -23,8 +43,17 @@ @interface AppDelegate () @implementation AppDelegate @synthesize deeplinkData; +//@synthesize att_state; +//@synthesize s_idfa; +//@synthesize s_idfv; + +- (void)applicationDidBecomeActive:(UIApplication *)application { + // Request user consent to use the Advertising Identifier (idfa) + [self requestTrackingAuthorization]; +} - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { + NSLog(@"SDK Version: %@", Singular.version); // If your app uses scenes and you want to support Singular Links, please refer to the documentation here: // https://support.singular.net/hc/en-us/articles/360038341692 @@ -32,17 +61,8 @@ - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:( // Starts a new session when the user opens the app if the session timeout has passed / opened using a Singular Link SingularConfig *config = [self getConfig]; config.launchOptions = launchOptions; - - // Will be zeros (unless tracking consent was given in a previous session) - NSString *idfaString = [[[ASIdentifierManager sharedManager] advertisingIdentifier] UUIDString]; - [Singular start:config]; - // Request user consent to use the Advertising Identifier (idfa) - [self requestTrackingAuthorization]; - - idfaString = [[[ASIdentifierManager sharedManager] advertisingIdentifier] UUIDString]; - return YES; } @@ -52,43 +72,92 @@ - (BOOL)application:(UIApplication *)application continueUserActivity:(NSUserAct // Starts a new session when the user opens the app using a Singular Link while it was in the background SingularConfig *config = [self getConfig]; config.userActivity = userActivity; - [Singular start:config]; - - // Request user consent to use the Advertising Identifier (idfa) - [self requestTrackingAuthorization]; + + return YES; +} +- (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary *)options{ + // Starts a new session when the user opens the app using a Non Singular Link, like a traditional App scheme. + // This code block will provide the Non Singular link value to the Singular Deeplink Handler + SingularConfig *config = [self getConfig]; + config.openUrl = url; + [Singular start:config]; + + // Replace this code with your own logic to handle Non Singular Deeplinks + NSLog(@"Handle URL: %@", url); + NSString *deeplink = url.absoluteString; + if (deeplink == (id)[NSNull null] || deeplink.length == 0 ) deeplink = @""; + NSMutableDictionary* values = [[NSMutableDictionary alloc] init]; + [values setObject:deeplink forKey:DEEPLINK]; + self.deeplinkData = values; + [self navigateToDeeplinkController]; + return YES; } - (SingularConfig *)getConfig { + // Singular Configuration + + // Optional: Modifying the Session Timeout + // By default, if the app runs in the background for 60 seconds or more before returning + // to the foreground, the SDK registers a new session. + //[Singular setSessionTimeout:60]; + + // Optional: Setting the Custom User ID + // If you already know the user ID when the app opens, set it before initializing the + // Singular SDK. This way, Singular will have the user ID from the very first session. + // Otherwise, set the user ID during Authentication. + //[Singular setCustomUserId:@"a_user_id"]; + + // Invoke the Config Object SingularConfig *config = [[SingularConfig alloc] initWithApiKey:APIKEY andSecret:SECRET]; + // Initialize the Singular Links Handler and Callback to retreive the deeplink or deferred + // deeplink values. config.singularLinksHandler = ^(SingularLinkParams * params) { [self handleSingularLink:params]; }; - - // Enable use of SKAN for iOS14 tracking - // Singular will call registerAppForAdNetworkAttribution for you - // Invoking [Singular skanRegisterAppForAdNetworkAttribution] will set this value to YES, even if done before/after [Singular start] + + // Enable skAdNetwork support for iOS14+ tracking. Singular will call + // registerAppForAdNetworkAttribution for you. config.skAdNetworkEnabled = YES; - // Enable manual conversion value updates - // IMPORTANT: set as NO (or just don't set - it defaults to NO) to let Singular manage conversion values - config.manualSkanConversionManagement = YES; + // Optional: To enable manual conversion value updates, set this to YES + // IMPORTANT: The default is NO, allowing Singular to manage your Conversion Values. + //config.manualSkanConversionManagement = YES; + // Optional: Receive a callback whenever the Conversion Value is updated config.conversionValueUpdatedCallback = ^(NSInteger newConversionValue) { - // Receive a callback whenever the Conversion Value is updated + NSLog(@"Conversion Value Callback: %lu", (unsigned long)newConversionValue); }; - // Delay sending events for up to 3 minutes, or until Tracking is Authorized (only on iOS 14) - config.waitForTrackingAuthorizationWithTimeoutInterval = 180; + // Optional: To delay the firing of a user session, you can initialize the Singular SDK + // with the waitForTrackingAuthorizationWithTimeoutInterval. Singular will then start the + // attribution process, taking advantage of the IDFA if it is available or using probabilistic + // attribution if the IDFA is not available. Delay sending events for up to 3 minutes, + // or until Tracking is Authorized (only on iOS 14) + config.waitForTrackingAuthorizationWithTimeoutInterval = 300; + + // Optional: Setting Global Properties through SingularConfig + // You can define up to 5 global properties. + // They persist between app runs (with the latest value you gave them) until you unset them + // or the user uninstalls the app. + // Each property name and value can be up to 200 characters long. If you pass a longer property + // name or value, it will be truncated to 200 characters. + // + // Note that since global properties and their values persist between app runs, the property + // that you are setting may already be set to a different value. Use the overrideExisting + // parameter to tell the SDK whether to override an existing property with the new value or not. + [config setGlobalProperty:@"example_key" withValue:@"example_value" overrideExisting:YES]; return config; } +// This function is used to display the ATT Pop up for consent. It is assumed that your App is already managing ATT. - (void)requestTrackingAuthorization { if (@available(iOS 14, *)) { + NSLog(@"Requesting ATT Consent"); // call requestTrackingAuthorizationWithCompletionHandler from ATTrackingManager to start the user consent process [ATTrackingManager requestTrackingAuthorizationWithCompletionHandler:^(ATTrackingManagerAuthorizationStatus status){ // your authorization handler here @@ -97,24 +166,37 @@ - (void)requestTrackingAuthorization { } } +// This is function will parse the parameters from a Singular Link or from the Response of +// the first session to provide the deeplink, deferred deeplink, and passthrough values. - (void)handleSingularLink:(SingularLinkParams*)params{ - NSMutableDictionary* values = [[NSMutableDictionary alloc] init]; - - [values setObject:[params getDeepLink] forKey:DEEPLINK]; - [values setObject:[params getPassthrough] forKey:PASSTHROUGH]; - [values setObject:[NSNumber numberWithBool:[params isDeferred]] forKey:IS_DEFERRED]; + NSLog(@"\n**** - handleSingularLink - ****\n"); + NSString* deeplink = [params getDeepLink]; + NSString* passthrough = [params getPassthrough]; + BOOL isDeferredDeeplink = [params isDeferred]; + + // Handle the Deeplink and Passthorugh Values + if([Utils isEmptyOrNull:deeplink]){ + deeplink = @""; + } + if([Utils isEmptyOrNull:passthrough]){ + passthrough = @""; + } + NSLog(@"Deeplink: %@", deeplink); + NSLog(@"Passthrough: %@", passthrough); + NSMutableDictionary* values = [[NSMutableDictionary alloc] init]; + [values setObject:deeplink forKey:DEEPLINK]; + [values setObject:passthrough forKey:PASSTHROUGH]; + [values setObject:[NSNumber numberWithBool:isDeferredDeeplink] forKey:IS_DEFERRED]; self.deeplinkData = values; - [self navigateToDeeplinkController]; } +// This function opens the deeplink view controller to display the provided deeplink data -(void)navigateToDeeplinkController{ - // UI changes must run on main thread dispatch_async(dispatch_get_main_queue(), ^{ TabController* tabBar = (TabController*)self.window.rootViewController; - // Signal to the TabController the app opened using a Singular Link [tabBar openedWithDeeplink]; }); diff --git a/ios-sample-app/ios-sample-app/Assets.xcassets/.DS_Store b/ios-sample-app/ios-sample-app/Assets.xcassets/.DS_Store new file mode 100644 index 0000000..d783853 Binary files /dev/null and b/ios-sample-app/ios-sample-app/Assets.xcassets/.DS_Store differ diff --git a/ios-sample-app/ios-sample-app/Assets.xcassets/AppIcon.appiconset/Contents.json b/ios-sample-app/ios-sample-app/Assets.xcassets/AppIcon.appiconset/Contents.json old mode 100644 new mode 100755 index d8db8d6..4062365 --- a/ios-sample-app/ios-sample-app/Assets.xcassets/AppIcon.appiconset/Contents.json +++ b/ios-sample-app/ios-sample-app/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -1,93 +1,153 @@ { "images" : [ { - "idiom" : "iphone", "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@2x.png", "scale" : "2x" }, { - "idiom" : "iphone", "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@3x.png", "scale" : "3x" }, { + "size" : "29x29", "idiom" : "iphone", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@2x.png", "scale" : "2x" }, { - "idiom" : "iphone", "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@3x.png", "scale" : "3x" }, { - "idiom" : "iphone", "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@2x.png", "scale" : "2x" }, { - "idiom" : "iphone", "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@3x.png", "scale" : "3x" }, { + "size" : "57x57", "idiom" : "iphone", - "size" : "60x60", + "filename" : "Icon-App-57x57@1x.png", + "scale" : "1x" + }, + { + "size" : "57x57", + "idiom" : "iphone", + "filename" : "Icon-App-57x57@2x.png", "scale" : "2x" }, { + "size" : "60x60", "idiom" : "iphone", + "filename" : "Icon-App-60x60@2x.png", + "scale" : "2x" + }, + { "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@3x.png", "scale" : "3x" }, { - "idiom" : "ipad", "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@1x.png", "scale" : "1x" }, { - "idiom" : "ipad", "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@2x.png", "scale" : "2x" }, { - "idiom" : "ipad", "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@1x.png", "scale" : "1x" }, { - "idiom" : "ipad", "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@2x.png", "scale" : "2x" }, { + "size" : "40x40", "idiom" : "ipad", + "filename" : "Icon-App-40x40@1x.png", + "scale" : "1x" + }, + { "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "50x50", + "idiom" : "ipad", + "filename" : "Icon-Small-50x50@1x.png", "scale" : "1x" }, { + "size" : "50x50", "idiom" : "ipad", - "size" : "40x40", + "filename" : "Icon-Small-50x50@2x.png", "scale" : "2x" }, { + "size" : "72x72", "idiom" : "ipad", - "size" : "76x76", + "filename" : "Icon-App-72x72@1x.png", "scale" : "1x" }, { + "size" : "72x72", "idiom" : "ipad", - "size" : "76x76", + "filename" : "Icon-App-72x72@2x.png", "scale" : "2x" }, { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@1x.png", + "scale" : "1x" + }, + { + "size" : "76x76", "idiom" : "ipad", + "filename" : "Icon-App-76x76@2x.png", + "scale" : "2x" + }, + { "size" : "83.5x83.5", + "idiom" : "ipad", + "filename" : "Icon-App-83.5x83.5@2x.png", "scale" : "2x" }, { - "idiom" : "ios-marketing", "size" : "1024x1024", + "idiom" : "ios-marketing", + "filename" : "ItunesArtwork@2x.png", "scale" : "1x" } ], diff --git a/ios-sample-app/ios-sample-app/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/ios-sample-app/ios-sample-app/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png new file mode 100644 index 0000000..497843b Binary files /dev/null and b/ios-sample-app/ios-sample-app/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png differ diff --git a/ios-sample-app/ios-sample-app/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/ios-sample-app/ios-sample-app/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png new file mode 100644 index 0000000..fec5774 Binary files /dev/null and b/ios-sample-app/ios-sample-app/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png differ diff --git a/ios-sample-app/ios-sample-app/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/ios-sample-app/ios-sample-app/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png new file mode 100644 index 0000000..5f7ff18 Binary files /dev/null and b/ios-sample-app/ios-sample-app/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png differ diff --git a/ios-sample-app/ios-sample-app/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/ios-sample-app/ios-sample-app/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png new file mode 100644 index 0000000..09d48f1 Binary files /dev/null and b/ios-sample-app/ios-sample-app/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png differ diff --git a/ios-sample-app/ios-sample-app/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/ios-sample-app/ios-sample-app/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png new file mode 100644 index 0000000..54b06a1 Binary files /dev/null and b/ios-sample-app/ios-sample-app/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png differ diff --git a/ios-sample-app/ios-sample-app/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/ios-sample-app/ios-sample-app/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png new file mode 100644 index 0000000..9ab2296 Binary files /dev/null and b/ios-sample-app/ios-sample-app/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png differ diff --git a/ios-sample-app/ios-sample-app/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/ios-sample-app/ios-sample-app/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png new file mode 100644 index 0000000..fec5774 Binary files /dev/null and b/ios-sample-app/ios-sample-app/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png differ diff --git a/ios-sample-app/ios-sample-app/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/ios-sample-app/ios-sample-app/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png new file mode 100644 index 0000000..595638d Binary files /dev/null and b/ios-sample-app/ios-sample-app/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png differ diff --git a/ios-sample-app/ios-sample-app/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/ios-sample-app/ios-sample-app/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png new file mode 100644 index 0000000..bf543f8 Binary files /dev/null and b/ios-sample-app/ios-sample-app/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png differ diff --git a/ios-sample-app/ios-sample-app/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png b/ios-sample-app/ios-sample-app/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png new file mode 100644 index 0000000..25a5173 Binary files /dev/null and b/ios-sample-app/ios-sample-app/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png differ diff --git a/ios-sample-app/ios-sample-app/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png b/ios-sample-app/ios-sample-app/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png new file mode 100644 index 0000000..7725450 Binary files /dev/null and b/ios-sample-app/ios-sample-app/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png differ diff --git a/ios-sample-app/ios-sample-app/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/ios-sample-app/ios-sample-app/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png new file mode 100644 index 0000000..bf543f8 Binary files /dev/null and b/ios-sample-app/ios-sample-app/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png differ diff --git a/ios-sample-app/ios-sample-app/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/ios-sample-app/ios-sample-app/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png new file mode 100644 index 0000000..7f6fd84 Binary files /dev/null and b/ios-sample-app/ios-sample-app/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png differ diff --git a/ios-sample-app/ios-sample-app/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png b/ios-sample-app/ios-sample-app/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png new file mode 100644 index 0000000..de64581 Binary files /dev/null and b/ios-sample-app/ios-sample-app/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png differ diff --git a/ios-sample-app/ios-sample-app/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png b/ios-sample-app/ios-sample-app/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png new file mode 100644 index 0000000..404be8d Binary files /dev/null and b/ios-sample-app/ios-sample-app/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png differ diff --git a/ios-sample-app/ios-sample-app/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/ios-sample-app/ios-sample-app/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png new file mode 100644 index 0000000..a876223 Binary files /dev/null and b/ios-sample-app/ios-sample-app/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png differ diff --git a/ios-sample-app/ios-sample-app/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/ios-sample-app/ios-sample-app/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png new file mode 100644 index 0000000..5f4548d Binary files /dev/null and b/ios-sample-app/ios-sample-app/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png differ diff --git a/ios-sample-app/ios-sample-app/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/ios-sample-app/ios-sample-app/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png new file mode 100644 index 0000000..cece780 Binary files /dev/null and b/ios-sample-app/ios-sample-app/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png differ diff --git a/ios-sample-app/ios-sample-app/Assets.xcassets/AppIcon.appiconset/Icon-Small-50x50@1x.png b/ios-sample-app/ios-sample-app/Assets.xcassets/AppIcon.appiconset/Icon-Small-50x50@1x.png new file mode 100644 index 0000000..2b19c6f Binary files /dev/null and b/ios-sample-app/ios-sample-app/Assets.xcassets/AppIcon.appiconset/Icon-Small-50x50@1x.png differ diff --git a/ios-sample-app/ios-sample-app/Assets.xcassets/AppIcon.appiconset/Icon-Small-50x50@2x.png b/ios-sample-app/ios-sample-app/Assets.xcassets/AppIcon.appiconset/Icon-Small-50x50@2x.png new file mode 100644 index 0000000..f725972 Binary files /dev/null and b/ios-sample-app/ios-sample-app/Assets.xcassets/AppIcon.appiconset/Icon-Small-50x50@2x.png differ diff --git a/ios-sample-app/ios-sample-app/Assets.xcassets/AppIcon.appiconset/ItunesArtwork@2x.png b/ios-sample-app/ios-sample-app/Assets.xcassets/AppIcon.appiconset/ItunesArtwork@2x.png new file mode 100644 index 0000000..a4ecf78 Binary files /dev/null and b/ios-sample-app/ios-sample-app/Assets.xcassets/AppIcon.appiconset/ItunesArtwork@2x.png differ diff --git a/ios-sample-app/ios-sample-app/Base.lproj/LaunchScreen.storyboard b/ios-sample-app/ios-sample-app/Base.lproj/LaunchScreen.storyboard index c2b1d00..ee7fcd7 100644 --- a/ios-sample-app/ios-sample-app/Base.lproj/LaunchScreen.storyboard +++ b/ios-sample-app/ios-sample-app/Base.lproj/LaunchScreen.storyboard @@ -1,9 +1,11 @@ - + - + + + @@ -15,13 +17,20 @@ - - - + + + + + + - + + + + + @@ -31,5 +40,8 @@ + + + diff --git a/ios-sample-app/ios-sample-app/Base.lproj/Main.storyboard b/ios-sample-app/ios-sample-app/Base.lproj/Main.storyboard index d414567..945cc17 100644 --- a/ios-sample-app/ios-sample-app/Base.lproj/Main.storyboard +++ b/ios-sample-app/ios-sample-app/Base.lproj/Main.storyboard @@ -1,9 +1,11 @@ - - + + - + + + @@ -19,9 +21,9 @@ + - @@ -34,38 +36,52 @@ - + - - - + + - - - - - + + + + + - + + + + + + + + + + + + + + + + @@ -81,58 +97,92 @@ - + - - - - - - + + + + + + + + + + + + + + + + + + + + + + + @@ -143,60 +193,91 @@ - + - + - - - - - - - + + - - - + + - - - + + - - + + + + + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -214,54 +295,251 @@ - + - - - - - - - + + + + + + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - - - + + + + + + + + + + + + + + + + diff --git a/ios-sample-app/ios-sample-app/Constants.h b/ios-sample-app/ios-sample-app/Constants.h index cb39c9d..3f85a75 100644 --- a/ios-sample-app/ios-sample-app/Constants.h +++ b/ios-sample-app/ios-sample-app/Constants.h @@ -12,3 +12,6 @@ #define DEEPLINK @"deeplink" #define PASSTHROUGH @"passthrough" #define IS_DEFERRED @"is_deferred" + +// Uncomment and update your ESP tracking domain here +//#define ESPDOMAIN @"links.mydomain.com" diff --git a/ios-sample-app/ios-sample-app/Controllers/CustomEventsController.h b/ios-sample-app/ios-sample-app/Controllers/CustomEventsController.h index 5c6b9b8..6eb91ab 100644 --- a/ios-sample-app/ios-sample-app/Controllers/CustomEventsController.h +++ b/ios-sample-app/ios-sample-app/Controllers/CustomEventsController.h @@ -3,7 +3,8 @@ // ios-sample-app // // Created by Eyal Rabinovich on 19/11/2019. -// Copyright © 2019 Singular Labs. All rights reserved. +// Updated by Jared Ornstead on 2021/11/19 +// Copyright © 2021 Singular Labs. All rights reserved. // #import diff --git a/ios-sample-app/ios-sample-app/Controllers/CustomEventsController.m b/ios-sample-app/ios-sample-app/Controllers/CustomEventsController.m index 28e3493..e78c427 100644 --- a/ios-sample-app/ios-sample-app/Controllers/CustomEventsController.m +++ b/ios-sample-app/ios-sample-app/Controllers/CustomEventsController.m @@ -3,7 +3,8 @@ // ios-sample-app // // Created by Eyal Rabinovich on 19/11/2019. -// Copyright © 2019 Singular Labs. All rights reserved. +// Updated by Jared Ornstead on 2021/11/19 +// Copyright © 2021 Singular Labs. All rights reserved. // #import "CustomEventsController.h" @@ -13,43 +14,58 @@ @implementation CustomEventsController - (IBAction)sendEvent:(id)sender { - NSString* eventName =self.eventNameField.text; + NSString* eventName = self.eventNameField.text; if([Utils isEmptyOrNull:eventName]){ - [Utils displayMessage:@"Please enter a valid event name" withView:self]; - return; + [Utils displayMessage:@"Please enter a valid event name." withView:self]; + } else { + if(eventName.length <= 32){ + //Add code here + // Set Conversion Value manually (when using manualSkanConversionManagement) + // Note that conversion values may only increase, so only the first call will update it + [Singular skanUpdateConversionValue:7]; + + // Reporting a simple Custom Event to Singular + [Singular event:eventName]; + + // Logging for Testing + NSLog(@"Event Sent: %@", eventName); + [Utils displayMessage:@"Event sent" withView:self]; + + } else { + [Utils displayMessage:@"Event name is too long. Event name length limit is 32 characters." withView:self]; + } } - - // Set Conversion Value manually (when using manualSkanConversionManagement) - // Note that conversion values may only increase, so only the first call will update it - [Singular skanUpdateConversionValue:7]; - - // Reporting a simple event to Singular - [Singular event:eventName]; - - [Utils displayMessage:@"Event sent" withView:self]; + return; } - (IBAction)sendEventsWithAttributes:(id)sender { - NSString* eventName =self.eventNameField.text; + NSString* eventName = self.eventNameField.text; - if([Utils isEmptyOrNull:eventName]){ - [Utils displayMessage:@"Please enter a valid event name" withView:self]; - return; + if([Utils isEmptyOrNull:eventName]){ + [Utils displayMessage:@"Please enter a valid event name." withView:self]; + } else { + if(eventName.length <= 32){ + NSMutableDictionary* args = [[NSMutableDictionary alloc] init]; + [args setObject:@"value1" forKey:@"key1"]; + [args setObject:@"value2" forKey:@"key2"]; + + // Set Conversion Value manually (when using manualSkanConversionManagement) + // Note that conversion values may only increase, so only the first call will update it + [Singular skanUpdateConversionValue:3]; + + // Reporting a Custom Event with custom attributes in a Dictionary + [Singular event:eventName withArgs:args]; + + // Logging for Testing + NSLog(@"Event Sent with Args: %@", eventName); + [Utils displayMessage:@"Event sent" withView:self]; + + } else { + [Utils displayMessage:@"Event name is too long. Event name length limit is 32 characters." withView:self]; + } } - - NSMutableDictionary* args = [[NSMutableDictionary alloc] init]; - [args setObject:@"value1" forKey:@"key1"]; - [args setObject:@"value2" forKey:@"key2"]; - - // Set Conversion Value manually (when using manualSkanConversionManagement) - // Note that conversion values may only increase, so only the first call will update it - [Singular skanUpdateConversionValue:3]; - - // Reporting a simple event with your custom attributes to pass with the event - [Singular event:eventName withArgs:args]; - - [Utils displayMessage:@"Event sent" withView:self]; + return; } @end diff --git a/ios-sample-app/ios-sample-app/Controllers/DeeplinkController.h b/ios-sample-app/ios-sample-app/Controllers/DeeplinkController.h index 0c3a32f..574ec8a 100644 --- a/ios-sample-app/ios-sample-app/Controllers/DeeplinkController.h +++ b/ios-sample-app/ios-sample-app/Controllers/DeeplinkController.h @@ -3,7 +3,8 @@ // ios-sample-app // // Created by Eyal Rabinovich on 20/11/2019. -// Copyright © 2019 Singular Labs. All rights reserved. +// Updated by Jared Ornstead on 2021/11/19 +// Copyright © 2021 Singular Labs. All rights reserved. // #import diff --git a/ios-sample-app/ios-sample-app/Controllers/DeeplinkController.m b/ios-sample-app/ios-sample-app/Controllers/DeeplinkController.m index 0095e60..f3c2da3 100644 --- a/ios-sample-app/ios-sample-app/Controllers/DeeplinkController.m +++ b/ios-sample-app/ios-sample-app/Controllers/DeeplinkController.m @@ -3,7 +3,8 @@ // ios-sample-app // // Created by Eyal Rabinovich on 20/11/2019. -// Copyright © 2019 Singular Labs. All rights reserved. +// Updated by Jared Ornstead on 2021/11/19 +// Copyright © 2021 Singular Labs. All rights reserved. // #import "DeeplinkController.h" diff --git a/ios-sample-app/ios-sample-app/Controllers/IdentityController.h b/ios-sample-app/ios-sample-app/Controllers/IdentityController.h index af69302..5ffd291 100644 --- a/ios-sample-app/ios-sample-app/Controllers/IdentityController.h +++ b/ios-sample-app/ios-sample-app/Controllers/IdentityController.h @@ -3,13 +3,37 @@ // ios-sample-app // // Created by Eyal Rabinovich on 01/12/2019. -// Copyright © 2019 Singular Labs. All rights reserved. +// Updated by Jared Ornstead on 2021/11/19 +// Copyright © 2021 Singular Labs. All rights reserved. // #import -@interface IdentityController : UIViewController +@interface IdentityController : UIViewController + +@property (weak, nonatomic) NSString *custom_user_id; +@property (weak, nonatomic) NSString *att_state; +@property (weak, nonatomic) NSString *s_idfa; +@property (weak, nonatomic) NSString *s_idfv; +@property (weak, nonatomic) NSString *shareMessage; @property (weak, nonatomic) IBOutlet UITextField *customUserIdField; +@property (weak, nonatomic) IBOutlet UILabel *idfa_value; +@property (weak, nonatomic) IBOutlet UILabel *idfv_value; +@property (weak, nonatomic) IBOutlet UILabel *att_value; + +@property (weak, nonatomic) IBOutlet UISwitch *gdpr; +@property (weak, nonatomic) IBOutlet UISwitch *limited_data_sharing; +@property (weak, nonatomic) IBOutlet UISwitch *ccpa; +@property (weak, nonatomic) IBOutlet UIButton *share; + + +- (IBAction)gdpr:(UISwitch *)sender; +- (IBAction)limited_data_sharing:(UISwitch *)sender; +- (IBAction)ccpa:(UISwitch *)sender; + +- (IBAction)shareText:(id)sender; + +- (void)presentActivityController:(UIActivityViewController *)shareController; @end diff --git a/ios-sample-app/ios-sample-app/Controllers/IdentityController.m b/ios-sample-app/ios-sample-app/Controllers/IdentityController.m index a2b3a4e..7eae6e9 100644 --- a/ios-sample-app/ios-sample-app/Controllers/IdentityController.m +++ b/ios-sample-app/ios-sample-app/Controllers/IdentityController.m @@ -3,21 +3,84 @@ // ios-sample-app // // Created by Eyal Rabinovich on 01/12/2019. -// Copyright © 2019 Singular Labs. All rights reserved. +// Updated by Jared Ornstead on 2021/11/19 +// Copyright © 2021 Singular Labs. All rights reserved. // #import "IdentityController.h" #import "Singular.h" #import "Utils.h" +#import "AppDelegate.h" +#import +#import @interface IdentityController () @end @implementation IdentityController +NSString* shareMessage; - (void)viewDidLoad { [super viewDidLoad]; + [self requestTrackingAuthorization]; +} + +- (IBAction)gdpr:(UISwitch *)sender { + if (self.gdpr.isOn){ + [Singular event:@"GDPR_CCPA_OptOut"]; + [Singular stopAllTracking]; + + //Logging for testing + NSLog(@"GDPR_CCPA Tracking OptOut - Tracking Stopped"); + [Utils displayMessage:[[NSString alloc] initWithFormat:@"GDPR_CCPA Tracking OptOut - Tracking Stopped"] withView:self]; + } else { + //[Singular trackingOptIn]; + if ([Singular isAllTrackingStopped] == TRUE){ + [Singular resumeAllTracking]; + [Singular event:@"GDPR_CCPA_OptIn"]; + + //Logging for testing + NSLog(@"GDPR_CCPA Tracking OptIn - Tracking Started"); + [Utils displayMessage:[[NSString alloc] initWithFormat:@"GDPR_CCPA Tracking OptIn - Tracking Started"] withView:self]; + } + } +} + +- (IBAction)limited_data_sharing:(UISwitch *)sender { + if (self.limited_data_sharing.isOn){ + [Singular limitDataSharing:YES]; + [Singular event:@"LimitedDataSharing_OptIn"]; + + //Logging for testing + NSLog(@"Limited Data Sharing OptIn - Tracking remains enabled but limited data will not be shared with Networks"); + [Utils displayMessage:[[NSString alloc] initWithFormat:@"Limited Data Sharing OptIn - Tracking remains enabled but limited data will not be shared with Networks"] withView:self]; + } else { + [Singular limitDataSharing:NO]; + [Singular event:@"LimitedDataSharing_OptOut"]; + + //Logging for testing + NSLog(@"Limited Data Sharing OptOut - All Data will be shared with networks"); + [Utils displayMessage:[[NSString alloc] initWithFormat:@"Limited Data Sharing OptOut - All Data will be shared with networks"] withView:self]; + } +} + +- (IBAction)ccpa:(UISwitch *)sender { + if (self.ccpa.isOn){ + [Singular event:@"CCPA_AgeUnder13_OptOut"]; + [Singular stopAllTracking]; + + //Logging for testing + NSLog(@"CCPA OptOut - Age is below 13 - Tracking Stopped"); + [Utils displayMessage:[[NSString alloc] initWithFormat:@"CCPA OptOut - Age is below 13 - Tracking Stopped"] withView:self]; + } else { + [Singular resumeAllTracking]; + [Singular event:@"CCPA_Disabled"]; + + //Logging for testing + NSLog(@"CCPA OptOut Disabled - Tracking Started"); + [Utils displayMessage:[[NSString alloc] initWithFormat:@"CCPA OptOut Disabled - Tracking Started"] withView:self]; + } } - (IBAction)setCustomUserId:(id)sender { @@ -25,18 +88,95 @@ - (IBAction)setCustomUserId:(id)sender { if([Utils isEmptyOrNull:customUserId]){ [Utils displayMessage:@"Please enter a valid Custom User Id" withView:self]; + } else { + // Once set, the Custom User Id will persist between runs until `[Singular unsetCustomUserId]` is called. + // This can also be called before SDK init if you want the first session to include the Custom User Id. + [Singular setCustomUserId:customUserId]; + + // Logging for Testing + NSLog(@"Custom User ID Set %@", customUserId); + [Utils displayMessage:[[NSString alloc] initWithFormat:@"Custom User Id set to: %@", customUserId] withView:self]; } - - // Once set, the Custom User Id will persist between runs until `[Singular unsetCustomUserId]` is called. - // This can also be called before SDK init if you want the first session to include the Custom User Id. - [Singular setCustomUserId:customUserId]; - - [Utils displayMessage:[[NSString alloc] initWithFormat:@"Custom User Id set to: %@", customUserId] withView:self]; } - (IBAction)unsetCustomUserId:(id)sender { [Singular unsetCustomUserId]; + + // Logging for Testing + NSLog(@"Custom User ID Unset"); [Utils displayMessage:@"Custom User Id unset" withView:self]; } +- (void)requestTrackingAuthorization { + // call requestTrackingAuthorizationWithCompletionHandler from ATTrackingManager to start the user consent process + if (@available(iOS 14, *)) { + [ATTrackingManager requestTrackingAuthorizationWithCompletionHandler:^(ATTrackingManagerAuthorizationStatus status) { + // Tracking authorization completed. Start loading ads here. + switch (status) { + case 0: self.att_state = @"(0) Not Determined"; + case 1: self.att_state = @"(1) Restricted"; + case 2: self.att_state = @"(2) Denied"; + case 3: self.att_state = @"(3) Authorized"; + } + self.s_idfa = [[[ASIdentifierManager sharedManager] advertisingIdentifier] UUIDString]; + self.s_idfv = [[[UIDevice currentDevice] identifierForVendor] UUIDString]; + [self.idfa_value setText:self.s_idfa]; + [self.idfv_value setText:self.s_idfv]; + [self.att_value setText:self.att_state]; + shareMessage = [NSString stringWithFormat:@"Sample App Device Info:\nATT Status: %@ \nAdvertising IDs\n(IDFA): %@ \n(IDFV): %@ \n",self.att_state,self.s_idfa,self.s_idfv]; + }]; + } else { + [self.idfa_value setText:[[[ASIdentifierManager sharedManager] advertisingIdentifier] UUIDString]]; + [self.idfv_value setText:[[[UIDevice currentDevice] identifierForVendor] UUIDString]]; + [self.att_value setText:@"(0) Not Determined < iOS14"]; + shareMessage = [NSString stringWithFormat:@"Sample App Device Info:\nATT Status: %@ \nAdvertising IDs\n(IDFA): %@ \n(IDFV): %@ \n",self.att_state,self.s_idfa,self.s_idfv]; + } + + // Logging for Testing + NSLog(@"ATT Status: %@", self.att_value); + NSLog(@"IDFA: %@", self.s_idfa); + NSLog(@"IDFV: %@", self.s_idfv); +} + +- (IBAction)shareText:(id)sender { + // create a share message + NSArray *items = @[ shareMessage ]; + + // build an activity view controller and present it + UIActivityViewController *shareController = [[UIActivityViewController alloc]initWithActivityItems:items applicationActivities:nil]; + [self presentActivityController:shareController]; +} + +- (void)presentActivityController:(UIActivityViewController *)shareController { + // for iPad: make the presentation a Popover + shareController.modalPresentationStyle = UIModalPresentationPopover; + [self presentViewController:shareController animated:YES completion:nil]; + + UIPopoverPresentationController *popController = [shareController popoverPresentationController]; + popController.permittedArrowDirections = UIPopoverArrowDirectionAny; + popController.barButtonItem = self.navigationItem.leftBarButtonItem; + + // access the completion handler + shareController.completionWithItemsHandler = ^(NSString *activityType, + BOOL completed, + NSArray *returnedItems, + NSError *error){ + // react to the completion + if (completed) { + + // user shared an item + NSLog(@"We used activity type%@", activityType); + + } else { + + // user cancelled + NSLog(@"We didn't want to share anything after all."); + } + + if (error) { + NSLog(@"An Error occured: %@, %@", error.localizedDescription, error.localizedFailureReason); + } + }; +} + @end diff --git a/ios-sample-app/ios-sample-app/Controllers/RevenueController.h b/ios-sample-app/ios-sample-app/Controllers/RevenueController.h index 79d65d5..3612925 100644 --- a/ios-sample-app/ios-sample-app/Controllers/RevenueController.h +++ b/ios-sample-app/ios-sample-app/Controllers/RevenueController.h @@ -3,7 +3,8 @@ // ios-sample-app // // Created by Eyal Rabinovich on 19/11/2019. -// Copyright © 2019 Singular Labs. All rights reserved. +// Updated by Jared Ornstead on 2021/11/19 +// Copyright © 2021 Singular Labs. All rights reserved. // #import diff --git a/ios-sample-app/ios-sample-app/Controllers/RevenueController.m b/ios-sample-app/ios-sample-app/Controllers/RevenueController.m index c93d457..a8f6944 100644 --- a/ios-sample-app/ios-sample-app/Controllers/RevenueController.m +++ b/ios-sample-app/ios-sample-app/Controllers/RevenueController.m @@ -3,7 +3,8 @@ // ios-sample-app // // Created by Eyal Rabinovich on 19/11/2019. -// Copyright © 2019 Singular Labs. All rights reserved. +// Updated by Jared Ornstead on 2021/11/19 +// Copyright © 2021 Singular Labs. All rights reserved. // #import "RevenueController.h" @@ -25,23 +26,27 @@ - (IBAction)sendRevenueEvent:(id)sender { if([Utils isEmptyOrNull:eventName]){ [Utils displayMessage:@"Please enter a valid event name" withView:self]; - return; + } else { + if(eventName.length <= 32){ + if([Utils isEmptyOrNull:currency]){ + [Utils displayMessage:@"Please enter a valid currency" withView:self]; + } else { + if(revenue <= 0){ + [Utils displayMessage:@"Revenue can't be zero or empty" withView:self]; + } else { + // Reporting a simple Revenue Event to Singular + [Singular customRevenue:eventName currency:currency amount:revenue]; + + // Logging for Testing + NSLog(@"Revenue Event Sent: %@ %@ %f", eventName, currency, revenue); + [Utils displayMessage:@"Revenue event sent" withView:self]; + } + } + } else { + [Utils displayMessage:@"Event name is too long. Event name length limit is 32 characters." withView:self]; + } } - - if([Utils isEmptyOrNull:currency]){ - [Utils displayMessage:@"Please enter a valid currency" withView:self]; - return; - } - - if(revenue <= 0){ - [Utils displayMessage:@"Revenue can't be zero or empty" withView:self]; - return; - } - - // Reporting a simple revenue event to Singular - [Singular customRevenue:eventName currency:currency amount:revenue]; - - [Utils displayMessage:@"Revenue event sent" withView:self]; + return; } - (IBAction)sendRevenueEventWithArgs:(id)sender { @@ -51,28 +56,68 @@ - (IBAction)sendRevenueEventWithArgs:(id)sender { if([Utils isEmptyOrNull:eventName]){ [Utils displayMessage:@"Please enter a valid event name" withView:self]; - return; - } - - if([Utils isEmptyOrNull:currency]){ - [Utils displayMessage:@"Please enter a valid currency" withView:self]; - return; + } else { + if(eventName.length <= 32){ + if([Utils isEmptyOrNull:currency]){ + [Utils displayMessage:@"Please enter a valid currency" withView:self]; + } else { + if(revenue <= 0){ + [Utils displayMessage:@"Revenue can't be zero or empty" withView:self]; + } else { + NSMutableDictionary* args = [[NSMutableDictionary alloc] init]; + [args setObject:@"value1" forKey:@"key1"]; + [args setObject:@"value2" forKey:@"key2"]; + + // Reporting a Custom Revenue Event to Singular with Arguments in a Dictionary + [Singular customRevenue:eventName currency:currency amount:revenue withAttributes:args]; + + // Logging for Testing + NSLog(@"Revenue Event Sent: %@ %@ %f %@", eventName, currency, revenue, args); + [Utils displayMessage:@"Revenue event sent" withView:self]; + } + } + } else { + [Utils displayMessage:@"Event name is too long. Event name length limit is 32 characters." withView:self]; + } } + return; +} + +- (IBAction)sendRevenueEventWithProductDetails:(id)sender { + NSString* eventName = self.eventNameField.text; + NSString* currency = self.currencyField.text; + double revenue = [self getRevenue]; + NSString* productSKU = @"SKU1928375"; + NSString* productName = @"Reservation Fee"; + NSString* productCategory = @"Fee"; + int productQuantity = 1; - if(revenue <= 0){ - [Utils displayMessage:@"Revenue must be a number greater than 0" withView:self]; - return; + if([Utils isEmptyOrNull:eventName]){ + [Utils displayMessage:@"Please enter a valid event name" withView:self]; + } else { + if(eventName.length <= 32){ + if([Utils isEmptyOrNull:currency]){ + [Utils displayMessage:@"Please enter a valid currency" withView:self]; + } else { + if(revenue <= 0){ + [Utils displayMessage:@"Revenue can't be zero or empty" withView:self]; + } else { + // Reporting a Custom Revenue Event to Singular with Product Details + [Singular customRevenue:eventName currency:currency amount:revenue productSKU:productSKU productName:productName productCategory:productCategory productQuantity:productQuantity productPrice:revenue]; + + // Logging for Testing + NSLog(@"Revenue Event Sent: %@ %@ %f \nproductSKU: %@ productName: %@ productCategory: %@ productQuantity: %i productPrice: %f", eventName, currency, revenue, productSKU, productName, productCategory, productQuantity, revenue); + [Utils displayMessage:@"Custom Revenue Event\nwith product details\nsent" withView:self]; + } + } + } else { + [Utils displayMessage:@"Event name is too long. Event name length limit is 32 characters." withView:self]; + } } - - // Reporting a simple revenue event with your custom attributes to pass with the event - NSMutableDictionary* args = [[NSMutableDictionary alloc] init]; - [args setObject:@"value1" forKey:@"key1"]; - [args setObject:@"value2" forKey:@"key2"]; - - [Singular customRevenue:eventName currency:currency amount:revenue withAttributes:args]; - [Utils displayMessage:@"Revenue event sent" withView:self]; + return; } + - (double)getRevenue{ if ([Utils isEmptyOrNull:self.revenueField.text]){ return 0; diff --git a/ios-sample-app/ios-sample-app/Controllers/TabController.h b/ios-sample-app/ios-sample-app/Controllers/TabController.h index c8ae4c2..95c952b 100644 --- a/ios-sample-app/ios-sample-app/Controllers/TabController.h +++ b/ios-sample-app/ios-sample-app/Controllers/TabController.h @@ -3,7 +3,8 @@ // ios-sample-app // // Created by Eyal Rabinovich on 20/11/2019. -// Copyright © 2019 Singular Labs. All rights reserved. +// Updated by Jared Ornstead on 2021/11/19 +// Copyright © 2021 Singular Labs. All rights reserved. // #import diff --git a/ios-sample-app/ios-sample-app/Controllers/TabController.m b/ios-sample-app/ios-sample-app/Controllers/TabController.m index 9e0fdcc..b0c1ee2 100644 --- a/ios-sample-app/ios-sample-app/Controllers/TabController.m +++ b/ios-sample-app/ios-sample-app/Controllers/TabController.m @@ -3,7 +3,8 @@ // ios-sample-app // // Created by Eyal Rabinovich on 20/11/2019. -// Copyright © 2019 Singular Labs. All rights reserved. +// Updated by Jared Ornstead on 2021/11/19 +// Copyright © 2021 Singular Labs. All rights reserved. // #import "TabController.h" diff --git a/ios-sample-app/ios-sample-app/Info.plist b/ios-sample-app/ios-sample-app/Info.plist index 2e91d2d..a4557b3 100644 --- a/ios-sample-app/ios-sample-app/Info.plist +++ b/ios-sample-app/ios-sample-app/Info.plist @@ -2,10 +2,10 @@ - NSUserTrackingUsageDescription - This is a message you can customize in the info.plist file CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + Sample ObjC CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier @@ -17,11 +17,26 @@ CFBundlePackageType $(PRODUCT_BUNDLE_PACKAGE_TYPE) CFBundleShortVersionString - 1.0 + $(MARKETING_VERSION) + CFBundleURLTypes + + + CFBundleTypeRole + Editor + CFBundleURLName + ios.sample.app.objc + CFBundleURLSchemes + + singularsample + + + CFBundleVersion 1 LSRequiresIPhoneOS + NSUserTrackingUsageDescription + This is a message you can customize in the info.plist file UILaunchStoryboardName LaunchScreen UIMainStoryboardFile @@ -33,8 +48,6 @@ UISupportedInterfaceOrientations UIInterfaceOrientationPortrait - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight UISupportedInterfaceOrientations~ipad diff --git a/ios-sample-app/ios-sample-app/Utils.m b/ios-sample-app/ios-sample-app/Utils.m index 0463384..bb898da 100644 --- a/ios-sample-app/ios-sample-app/Utils.m +++ b/ios-sample-app/ios-sample-app/Utils.m @@ -27,7 +27,7 @@ + (void)displayMessage:(NSString*) message withView:(UIViewController*) view{ [view presentViewController:alert animated:YES completion:nil]; - int duration = 1; // duration in seconds + int duration = 2; // duration in seconds dispatch_after(dispatch_time(DISPATCH_TIME_NOW, duration * NSEC_PER_SEC), dispatch_get_main_queue(), ^{ [alert dismissViewControllerAnimated:YES completion:nil]; diff --git a/ios-sample-app/ios-sample-app/iOS Sample App.entitlements b/ios-sample-app/ios-sample-app/iOS Sample App.entitlements index cbd2a28..7cf3b6d 100644 --- a/ios-sample-app/ios-sample-app/iOS Sample App.entitlements +++ b/ios-sample-app/ios-sample-app/iOS Sample App.entitlements @@ -4,7 +4,7 @@ com.apple.developer.associated-domains - applinks:sample.sng.link + applinks:se.sng.link