At a glance: The AppsFlyer iOS SDK provides app attribution and event reporting functionality for iOS apps. The SDK supports changes implemented in iOS 14. This SDK version incorporates significant method changes compared to previous versions.
Important!
iOS SDK V5.4.4 is fully operational with iOS 14. However, we recommend adopting SDK V6.X to ensure compatibility with future iOS releases. See Updating to iOS SDK V6.
Apple App Clips attribution is available starting SDK V6.0.8.
According to Apple, App Tracking Transparency (ATT) enforcement is planned to start with iOS 14.5 (early Spring, 2021). Learn how to configure the iOS SDK to support ATT.
1. Overview
The SDK provides app installation and event recording functionality. The SDK is robust, secure, lightweight, and simple to embed.
You can record installs, updates, sessions, and in-app events. In-app events support in-app purchases, game levels, etc. to evaluate ROI and user quality.
iOS version | Attribution models supported |
---|---|
Before 11.3 |
|
11.3–13 |
|
14+ |
|
* iOS SDK V5.4 and earlier:
|
1.1 SDK integration—what you need to do
Tab | Purpose | Result |
---|---|---|
SDK Integration |
Shows how to add and configure the SDK. |
|
Core APIs |
Shows how to use the SDK core APIs. These APIs allow you to measure in-app events and revenue, perform deep linking and gather conversion data. |
In-app events and revenue appear on your dashboard. You are able to perform deep linking. |
Shows how to implement and use optional APIs such as uninstall measurement, Referrals (user invite attribution), and push notifications. |
You are able to measure uninstalls, referrals, user engagements with push notifications, handle user privacy scenarios and more. |
|
Quick reference of the SDK APIs for developers |
|
1.2 SDK compatibility with iOS
- This SDK is:
- Compatible with all iOS and tvOS devices (iPhone, iPod, iPad, Apple TV) with iOS version 6 and later and tvOS version 9 and later.
- Complies with Apple IPv6 DNS64/NAT64 networks.
Note
Apple iOS 14 App Clips are just around the corner!
Learn all about them in our Definitive App Clips Guide for Developers
This tab explains how to implement and initialize the SDK, and is written for you, the app developer. On completion of this tab, two installs display in the app dashboard, one organic and one non-organic.
2. Add the SDK to your app
2.1 Download and add the SDK to Xcode
- Download and install the latest version of CocoaPods.
- Add the following row to
Podfile
:pod 'AppsFlyerFramework'
- Run
pod install
. - Use the
.xcworkspace
file to open the project in Xcode, instead of the.xcodeproj
file, from this point forward.
Note
If you are developing a tvOS app, CocoaPods automatically adds the relevant dependencies from AppsFlyerFramework.
- Install the latest version of Carthage.
- Add the following line to your Cartfile binary:
https://raw.githubusercontent.com/AppsFlyerSDK/AppsFlyerFramework/master/Carthage/appsflyer-ios.json
Note
The link above links to a static library. If you're upgrading to a newer iOS version, do the following:
- Remove the Run Script stage from Xcode that runs copy-frameworks.
- Make sure the library is not embedded.
To learn more, see Carthage docs.
Currently doesn't support tvOS apps.
- Download the iOS SDK as a static framework.
To verify the integrity of the SDK static framework download, click here. - Unzip the AppsFlyerLib.framework.zip file you just downloaded.
- Drag the AppsFlyerLib.framework and drop it into your Xcode project.
- Make sure Copy items if needed is checked.
Note
This approach is only compatible with iOS 8 and above.
For tvOS apps, you need a different AppsFlyerFramework:
- Clone this repo.
- Find AppsFlyerLib.framework in this folder of the cloned repo.
- Repeat steps 3 and 4.
2.2 Native iOS framework dependencies
The SDK automatically adds and uses the following native frameworks:
- AdSupport.framework
- This framework is required to collect the IDFA from devices.
Without IDFA you cannot attribute installs to Facebook Ads, Twitter, Google ads and other networks. - iAd.framework
- Measure performance of Apple Search Ads in your app.
- AdServices Framework (
V6.1.3+
) - Measure performance of Apple Search Ads in your app.
If you want to remove these frameworks and disable IDFA collection:
- Disable IDFA collection
- Disable iAd.framework
Strict mode SDK
Use the strict mode SDK to completely remove IDFA collection functionality and AdSupport framework dependencies (for example, when developing apps for kids).
Note: IDFV remains available.
In your Podfile
, replace AppsFlyerLib
dependency with:
pod 'AppsFlyerFramework/Strict','6.1.1'
Note: If you use strict mode SDK and call disableAdvertisingIdentifier
, you receive a compilation error.
Note
If you are using an SDK version earlier than 4.10.4, you need to manually add ad support frameworks.
To manually add ad support frameworks:
- In your Xcode project, select project target.
- Select the General tab for your target.
- Expand the Linked Frameworks and Libraries section.
- Click + to add a framework.
- Search for AdSupport.framework
- Select AdSupport.framework from the list.
Repeat the process for iAd.framework.
3. Implement and initialize the SDK
This section describes how to initialize and start the AppsFlyer iOS SDK.
3.1 Retrieve your AppsFlyer dev key
AppsFlyer uses the dev key to uniquely identify your account. The dev key is required because it enables the SDK to securely send and retrieve data that belongs to your account.
To get the SDK key:
- In AppsFlyer, go to Configuration > App Settings.
- Copy your dev key, you will need it for the next step.
3.2 Initialize the SDK
Note
To support iOS apps that use SceneDelegate
, see Initialize the SDK with SceneDelegate.
The following code is an example implementation. Make sure to change the <AF_DEV_KEY>
and <APPLE_APP_ID>
placeholders as needed.
In the AppDelegate.h
file:
- Import the
AppsFlyerLib/AppsFlyerLib.h
header. - Add
AppsFlyerLibDelegate
to theAppDelegate
interface declaration.
#import <UIKit/UIKit.h>
#import <AppsFlyerLib/AppsFlyerLib.h>
@interface AppDelegate : UIResponder <UIApplicationDelegate, AppsFlyerLibDelegate>
@end
In the AppDelegate.m
file:
- In
didFinishLaunchingWithOptions
, configure:- The AppsFlyer dev key you copied in the previous step
- The Apple app ID
- To collect IDFA, configure App Tracking Transparency (ATT) support.
- Additional settings
- Override the
onConversionDataSuccess
andonConversionDataFail
callbacks to process conversions and enable deferred deep linking. - Override the
onAppOpenAttribution
andonAppOpenAttributionFailure
callbacks to process attribution and enable direct deep linking. - In the
didReceiveRemoteNotification
callback, callhandlePushNotification
to attribute push notification re-engagements. - In the
applicationDidBecomeActive
callback, callstart
.
#import "AppDelegate.h"
#import <UserNotifications/UserNotifications.h>
@interface AppDelegate ()
@end
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.
/** APPSFLYER INIT **/
[AppsFlyerLib shared].appsFlyerDevKey = @"<AF_DEV_KEY>";
[AppsFlyerLib shared].appleAppID = @"<APPLE_APP_ID>";
[AppsFlyerLib shared].delegate = self;
/* Set isDebug to true to see AppsFlyer debug logs */
[AppsFlyerLib shared].isDebug = true;
if (@available(iOS 10, *)) {
UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];
center.delegate = self;
[center requestAuthorizationWithOptions:(UNAuthorizationOptionSound | UNAuthorizationOptionAlert | UNAuthorizationOptionBadge) completionHandler:^(BOOL granted, NSError * _Nullable error) {
}];
} else {
UIUserNotificationSettings *settings = [UIUserNotificationSettings settingsForTypes: UIUserNotificationTypeAlert | UIUserNotificationTypeSound | UIUserNotificationTypeBadge categories:nil];
[[UIApplication sharedApplication] registerUserNotificationSettings:settings];
}
[[UIApplication sharedApplication] registerForRemoteNotifications];
return YES;
}
- (void)applicationDidBecomeActive:(UIApplication *)application {
[[AppsFlyerLib shared] start];
}
// Deep linking
// Open URI-scheme for iOS 9 and above
- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url options:(NSDictionary *) options {
[[AppsFlyerLib shared] handleOpenUrl:url options:options];
return YES;
}
// Open URI-scheme for iOS 8 and below
- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString*)sourceApplication annotation:(id)annotation {
[[AppsFlyerLib shared] handleOpenURL:url sourceApplication:sourceApplication withAnnotation:annotation];
return YES;
}
// Open Universal Links
- (BOOL)application:(UIApplication *)application continueUserActivity:(NSUserActivity *)userActivity restorationHandler:(void (^)(NSArray * _Nullable))restorationHandler {
[[AppsFlyerLib shared] continueUserActivity:userActivity restorationHandler:restorationHandler];
return YES;
}
// Report Push Notification attribution data for re-engagements
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler {
[[AppsFlyerLib shared] handlePushNotification:userInfo];
}
// AppsFlyerLib implementation
//Handle Conversion Data (Deferred Deep Link)
-(void)onConversionDataSuccess:(NSDictionary*) installData {
id status = [installData objectForKey:@"af_status"];
if([status isEqualToString:@"Non-organic"]) {
id sourceID = [installData objectForKey:@"media_source"];
id campaign = [installData objectForKey:@"campaign"];
NSLog(@"This is a non-organic install. Media source: %@ Campaign: %@",sourceID,campaign);
} else if([status isEqualToString:@"Organic"]) {
NSLog(@"This is an organic install.");
}
}
-(void)onConversionDataFail:(NSError *) error {
NSLog(@"%@",error);
}
//Handle Direct Deep Link
- (void) onAppOpenAttribution:(NSDictionary*) attributionData {
NSLog(@"%@",attributionData);
}
- (void) onAppOpenAttributionFailure:(NSError *)error {
NSLog(@"%@",error);
}
@end
In the AppDelegate.swift
file:
- Import
AppsFlyerLib
. - add
AppsFlyerLibDelegate
to theAppDelegate
class declaration. - In
didFinishLaunchingWithOptions
, configure:- The AppsFlyer dev key
- The app ID
- To collect IDFA, configure App Tracking Transparency (ATT) support.
- Additional settings
- Override the
onConversionDataSuccess
andonConversionDataFail
callbacks to process conversions and enable deferred deep linking. - Override the
onAppOpenAttribution
andonAppOpenAttributionFailure
callbacks to process attribution and enable direct deep linking. - In the
didReceiveRemoteNotification
callback, callhandlePushNotification
to attribute push notification re-engagements. - In the
applicationDidBecomeActive
callback, callstart
.
import UIKit
import AppsFlyerLib
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
let defaults = UserDefaults.standard
//MARK: LifeCycle
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
AppsFlyerLib.shared().appsFlyerDevKey = "<AF_DEV_KEY>"
AppsFlyerLib.shared().appleAppID = "<APPLE_APP_ID>"
AppsFlyerLib.shared().delegate = self
AppsFlyerLib.shared().isDebug = true
// iOS 10 or later
if #available(iOS 10, *) {
UNUserNotificationCenter.current().requestAuthorization(options: [.badge, .alert, .sound]) { _, _ in }
application.registerForRemoteNotifications()
}
// iOS 9 support - Given for reference. This demo app supports iOS 13 and above
else {
application.registerUserNotificationSettings(UIUserNotificationSettings(types: [.badge, .sound, .alert], categories: nil))
application.registerForRemoteNotifications()
}
return true
}
func applicationDidBecomeActive(_ application: UIApplication) {
// Start the SDK (start the IDFA timeout set above, for iOS 14 or later)
AppsFlyerLib.shared().start()
}
// Open Univerasal Links
// For Swift version < 4.2 replace function signature with the commented out code:
// func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([Any]?) -> Void) -> Bool {
func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any]) {
print(" user info \(userInfo)")
AppsFlyerLib.shared().handlePushNotification(userInfo)
}
// Open Deeplinks
// Open URI-scheme for iOS 8 and below
private func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([Any]?) -> Void) -> Bool {
AppsFlyerLib.shared().continue(userActivity, restorationHandler: restorationHandler)
return true
}
// Open URI-scheme for iOS 9 and above
func application(_ application: UIApplication, open url: URL, sourceApplication: String?, annotation: Any) -> Bool {
AppsFlyerLib.shared().handleOpen(url, sourceApplication: sourceApplication, withAnnotation: annotation)
return true
}
// Report Push Notification attribution data for re-engagements
func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
AppsFlyerLib.shared().handleOpen(url, options: options)
return true
}
func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
AppsFlyerLib.shared().handlePushNotification(userInfo)
}
// Reports app open from deep link for iOS 10 or later
func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool {
AppsFlyerLib.shared().continue(userActivity, restorationHandler: nil)
return true
}
}
//MARK: AppsFlyerLibDelegate
extension AppDelegate: AppsFlyerLibDelegate{
// Handle Organic/Non-organic installation
func onConversionDataSuccess(_ installData: [AnyHashable: Any]) {
print("onConversionDataSuccess data:")
for (key, value) in installData {
print(key, ":", value)
}
if let status = installData["af_status"] as? String {
if (status == "Non-organic") {
if let sourceID = installData["media_source"],
let campaign = installData["campaign"] {
print("This is a Non-Organic install. Media source: \(sourceID) Campaign: \(campaign)")
}
} else {
print("This is an organic install.")
}
if let is_first_launch = installData["is_first_launch"] as? Bool,
is_first_launch {
print("First Launch")
} else {
print("Not First Launch")
}
}
}
func onConversionDataFail(_ error: Error) {
print(error)
}
//Handle Deep Link
func onAppOpenAttribution(_ attributionData: [AnyHashable : Any]) {
//Handle Deep Link Data
print("onAppOpenAttribution data:")
for (key, value) in attributionData {
print(key, ":",value)
}
}
func onAppOpenAttributionFailure(_ error: Error) {
print(error)
}
}
3.2.1 Initialize the SDK with SceneDelegate
Use this initialization method only if you're using SceneDelegate
.
Since applicationDidBecomeActive
is not called when using SceneDelegate
, use the UIApplicationDidBecomeActiveNotification
workaround to initialize the SDK, as shown in the following example.
The following code is an example implementation. Make sure to change the <AF_DEV_KEY>
and <APPLE_APP_ID>
placeholders as needed.
In the AppDelegate.h
file:
- Import the
AppsFlyerLib/AppsFlyerLib.h
header. - Add
AppsFlyerLibDelegate
to theAppDelegate
interface declaration.
#import <AppsFlyerLib/AppsFlyerLib.h>
@interface AppDelegate : UIResponder <UIApplicationDelegate, AppsFlyerLibDelegate>
@end
In the AppDelegate.m
file:
- In the
didFinishLaunchingWithOptions
callback, configure:- The AppsFlyer dev key
- The app ID
- To collect IDFA, configure App Tracking Transparency (ATT) support.
- Additional settings
- Override the
onConversionDataSuccess
andonConversionDataFail
callbacks to process conversions and enable deferred deep linking. - Override the
onAppOpenAttribution
andonAppOpenAttributionFailure
callbacks to process attribution and enable direct deep linking. - In the
didReceiveRemoteNotification
callback, callhandlePushNotification::userInfo
to attribute push notification re-engagements. - In the
sendLaunch
callback, callstart
.
#import "AppDelegate.h"
#import <AppsFlyerLib/AppsFlyerLib.h>
#import <UserNotifications/UserNotifications.h>
@interface AppDelegate ()
@end
@implementation AppDelegate
// Start the AppsFlyer SDK
- (void)sendLaunch:(UIApplication *)application {
[[AppsFlyerLib shared] start];
}
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.
/** APPSFLYER INIT **/
[AppsFlyerLib shared].appsFlyerDevKey = @"<AF_DEV_KEY>";
[AppsFlyerLib shared].appleAppID = @"<APPLE_APP_ID>";
[AppsFlyerLib shared].delegate = self;
/* Set isDebug to true to see AppsFlyer debug logs */
[AppsFlyerLib shared].isDebug = true;
// Use UIApplicationDidBecomeActiveNotification to start the SDK
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(sendLaunch:)
name:UIApplicationDidBecomeActiveNotification
object:nil];
return YES;
}
if (@available(iOS 10, *)) {
UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];
center.delegate = self;
[center requestAuthorizationWithOptions:(UNAuthorizationOptionSound | UNAuthorizationOptionAlert | UNAuthorizationOptionBadge) completionHandler:^(BOOL granted, NSError * _Nullable error) {
}];
} else {
UIUserNotificationSettings *settings = [UIUserNotificationSettings settingsForTypes: UIUserNotificationTypeAlert | UIUserNotificationTypeSound | UIUserNotificationTypeBadge categories:nil];
[[UIApplication sharedApplication] registerUserNotificationSettings:settings];
}
[[UIApplication sharedApplication] registerForRemoteNotifications];
return YES;
}
// Report Push Notification attribution data for re-engagements
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler {
[[AppsFlyerLib shared] handlePushNotification:userInfo];
}
// AppsFlyerLib implementation
//Handle Conversion Data (Deferred Deep Link)
-(void)onConversionDataSuccess:(NSDictionary*) installData {
id status = [installData objectForKey:@"af_status"];
if([status isEqualToString:@"Non-organic"]) {
id sourceID = [installData objectForKey:@"media_source"];
id campaign = [installData objectForKey:@"campaign"];
NSLog(@"This is a none organic install. Media source: %@ Campaign: %@",sourceID,campaign);
} else if([status isEqualToString:@"Organic"]) {
NSLog(@"This is an organic install.");
}
}
-(void)onConversionDataFail:(NSError *) error {
NSLog(@"%@",error);
}
//Handle Direct Deep Link
- (void) onAppOpenAttribution:(NSDictionary*) attributionData {
NSLog(@"%@",attributionData);
}
- (void) onAppOpenAttributionFailure:(NSError *)error {
NSLog(@"%@",error);
}
// support for scene delegate
#pragma mark - UISceneSession lifecycle
- (UISceneConfiguration *)application:(UIApplication *)application configurationForConnectingSceneSession:(UISceneSession *)connectingSceneSession options:(UISceneConnectionOptions *)options API_AVAILABLE(ios(13)){
// Called when a new scene session is being created.
// Use this method to select a configuration to create the new scene with.
return [[UISceneConfiguration alloc] initWithName:@"Default Configuration" sessionRole:connectingSceneSession.role];
}
- (void)application:(UIApplication *)application didDiscardSceneSessions:(NSSet *)sceneSessions API_AVAILABLE(ios(13.0)){
// Called when the user discards a scene session.
// If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions.
// Use this method to release any resources that were specific to the discarded scenes, as they will not return.
}
@end
In the SceneDelegate.m
file:
- In the
openURLContexts
callback, callhandleOpenUrl
. - In the
continueUserActivity
callback, callcontinueUserActivity
.
#import "SceneDelegate.h"
@interface SceneDelegate ()
@end
@implementation SceneDelegate
- (void)scene:(UIScene *)scene openURLContexts:(NSSet<UIOpenURLContext *> *)URLContexts API_AVAILABLE(ios(13.0)){
NSURL* url = [[URLContexts allObjects] objectAtIndex:0].URL;
if(url){
[[AppsFlyerLib shared] handleOpenUrl:url options:nil];
}
}
- (void)scene:(UIScene *)scene continueUserActivity:(NSUserActivity *)userActivity API_AVAILABLE(ios(13.0)){
[[AppsFlyerLib shared]continueUserActivity:userActivity restorationHandler:nil];
}
- (void)scene:(UIScene *)scene willConnectToSession:(UISceneSession *)session options:(UISceneConnectionOptions *)connectionOptions {
// Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`.
// If using a storyboard, the `window` property will automatically be initialized and attached to the scene.
// This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead).
NSUserActivity *activity = [[connectionOptions userActivities] allObjects].firstObject;
if (activity) {
[self scene:scene continueUserActivity:activity];
}
[self scene:scene openURLContexts: [connectionOptions URLContexts]];
}
- (void)sceneDidDisconnect:(UIScene *)scene API_AVAILABLE(ios(13.0)){
// Called as the scene is being released by the system.
// This occurs shortly after the scene enters the background, or when its session is discarded.
// Release any resources associated with this scene that can be re-created the next time the scene connects.
// The scene may re-connect later, as its session was not neccessarily discarded (see `application:didDiscardSceneSessions` instead).
}
- (void)sceneDidBecomeActive:(UIScene *)scene API_AVAILABLE(ios(13.0)){
// Called when the scene has moved from an inactive state to an active state.
// Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive.
}
- (void)sceneWillResignActive:(UIScene *)scene API_AVAILABLE(ios(13.0)){
// Called when the scene will move from an active state to an inactive state.
// This may occur due to temporary interruptions (ex. an incoming phone call).
}
- (void)sceneWillEnterForeground:(UIScene *)scene API_AVAILABLE(ios(13.0)){
// Called as the scene transitions from the background to the foreground.
// Use this method to undo the changes made on entering the background.
}
- (void)sceneDidEnterBackground:(UIScene *)scene API_AVAILABLE(ios(13.0)){
// Called as the scene transitions from the foreground to the background.
// Use this method to save data, release shared resources, and store enough scene-specific state information
// to restore the scene back to its current state.
}
@end
In the AppDelegate.swift
file:
- Import
AppsFlyerLib
. - Add
AppsFlyerLibDelegate
to theAppDelegate
class declaration. - In the
didFinishLaunchingWithOptions
callback, configure your:- The AppsFlyer dev key
- The app ID
- To collect IDFA, configure App Tracking Transparency (ATT) support.
- Additional settings
- Override the
onConversionDataSuccess
andonConversionDataFail
callbacks to process conversions and enable deferred deep linking. - Override the
onAppOpenAttribution
andonAppOpenAttributionFailure
callbacks to process attribution and enable direct deep linking - In the
didReceiveRemoteNotification
callback, callhandlePushNotification
to attribute push notification re-engagements. - In the
sendLaunch
callback, callstart
.
import UIKit
import AppsFlyerLib
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate, AppsFlyerLibDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
AppsFlyerLib.shared().appsFlyerDevKey = "<AF_DEV_KEY>"
AppsFlyerLib.shared().appleAppID = "<APPLE_APP_ID>"
AppsFlyerLib.shared().delegate = self
AppsFlyerLib.shared().isDebug = true
NotificationCenter.default.addObserver(self, selector: NSSelectorFromString("sendLaunch"), name: UIApplication.didBecomeActiveNotification, object: nil)
return true
}
@objc func sendLaunch() {
AppsFlyerLib.shared().start()
}
// MARK: UISceneSession Lifecycle
func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
// Called when a new scene session is being created.
// Use this method to select a configuration to create the new scene with.
}
return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
}
func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set<UISceneSession>) {
// Called when the user discards a scene session.
// If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions.
// Use this method to release any resources that were specific to the discarded scenes, as they will not return.
}
//MARK: -GCD
func onConversionDataSuccess(_ installData: [AnyHashable: Any]) {
print("onConversionDataSuccess data:")
for (key, value) in installData {
print(key, ":", value)
}
if let status = installData["af_status"] as? String {
if (status == "Non-organic") {
if let sourceID = installData["media_source"],
let campaign = installData["campaign"] {
print("This is a Non-Organic install. Media source: \(sourceID) Campaign: \(campaign)")
}
} else {
print("This is an organic install.")
}
if let is_first_launch = installData["is_first_launch"] as? Bool,
is_first_launch {
print("First Launch")
} else {
print("Not First Launch")
}
}
}
func onConversionDataFail(_ error: Error!) {
if let err = error{
print(err)
}
}
func onAppOpenAttribution(_ attributionData: [AnyHashable : Any]!) {
if let data = attributionData{
print("\(data)")
}
}
func onAppOpenAttributionFailure(_ error: Error!) {
if let err = error{
print(err)
}
}
}
In the SceneDelegate.swift
file:
- Import
AppsFlyerLib
. - In the
openURLContexts
callback, callAppsFlyerLib.shared().handleOpen
. - In the
continue
callback, callAppsFlyerLib.shared().continue
.
import UIKit
import AppsFlyerLib
@available(iOS 13.0, *)
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
var window: UIWindow?
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
// Processing Universal Link from the killed state
if let userActivity = connectionOptions.userActivities.first {
self.scene(scene, continue: userActivity)
}
// Processing URI-scheme from the killed state
self.scene(scene, openURLContexts: connectionOptions.urlContexts)
guard let _ = (scene as? UIWindowScene) else { return }
}
func scene(_ scene: UIScene, continue userActivity: NSUserActivity) {
AppsFlyerLib.shared().continue(userActivity, restorationHandler: nil)
}
func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {
if let url = URLContexts.first?.url {
AppsFlyerLib.shared().handleOpen(url, options: nil)
}
}
func sceneDidDisconnect(_ scene: UIScene) {
// Called as the scene is being released by the system.
// This occurs shortly after the scene enters the background, or when its session is discarded.
// Release any resources associated with this scene that can be re-created the next time the scene connects.
// The scene may re-connect later, as its session was not neccessarily discarded (see `application:didDiscardSceneSessions` instead).
}
func sceneDidBecomeActive(_ scene: UIScene) {
// Called when the scene has moved from an inactive state to an active state.
// Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive.
}
func sceneWillResignActive(_ scene: UIScene) {
// Called when the scene will move from an active state to an inactive state.
// This may occur due to temporary interruptions (ex. an incoming phone call).
}
func sceneWillEnterForeground(_ scene: UIScene) {
// Called as the scene transitions from the background to the foreground.
// Use this method to undo the changes made on entering the background.
}
func sceneDidEnterBackground(_ scene: UIScene) {
// Called as the scene transitions from the foreground to the background.
// Use this method to save data, release shared resources, and store enough scene-specific state information
// to restore the scene back to its current state.
}
}
3.3 Support for SKAdNetwork attribution
SKAdNetwork is a class used by iOS that validates advertiser-driven app installations. The app install validation process involves the source app and the advertised app.
A source app is an app that participates in ad campaigns by displaying ads signed by an ad network. Configuring your app to display ads is not within the scope of the AppsFlyer SDK. To configure, follow the Apple instructions.
For the advertised app (the app with the AppsFlyer SDK), the AppsFlyer SKAdNetwork Solution uses SKAdNetwork to provide the attribution postback while AppsFlyer collects, translates, and aggregates the data, while maintaining user privacy. On launching the app for the first time, the AppsFlyer platform, using the configuration set by the marketer, instructs the SDK how to set the SKAdNetwork conversion value.
To use the SKAdNetwork Solution:
- The developer does nothing.
- AppsFlyer SDK automatically calls the necessary SKAdNetwork APIs, meaning
registerAppForAdNetworkAttribution()
andupdateConversionValue()
. The developer must not call them. - Don't allow other SDKs to call SKAdNet APIs. Doing so can delay iOS in sending the postback to AppsFlyer and change the conversion value which we use to calculate SKAdNetwork dashboard user quality metrics.
- The marketer needs to configure SKAdNetwork measurement in AppsFlyer.
No action or registration process required in the app store by either the developer or the marketer.
To disable SKAdNetwork attribution, use the disableSKAdNetwork API.
3.4 Delay initializing the SDK
You may want to delay initializing the SDK and sending data until the user gives consent (for example, GDPR or COPPA consent).
Note: This is unrelated to ATT introduced in iOS 14.
To delay initializing the SDK:
- Prevent the session from being sent on background-foreground transition. In a sendLaunch() method (that is called on every applicationDidBecomeActive (as per section 3.3), wrap call to
start
with a condition check. For example:
-(void)sendLaunch:(UIApplication *)application { if (consent_given) { // check the condition [[AppsFlyerLib shared] start]; // Start } }
- Send the session as soon as the reason for delaying is no longer relevant (right after condition changes). Call
start
when you are ready to send first session data. For example:
... consent_given = YES; // change the condition [[AppsFlyerLib shared] start]; // Start ...
4. Test installs
Now you are ready to test the SDK integration by simulating organic and non-organic installs.
For more testing scenarios and instructions, see SDK integration testing.
4.1 Register your test device
Before you start testing installs:
- Make sure your device does not have the app installed.
- Register the device you are going to test on.
4.2 Simulate an organic install
Organic installs are unattributed installs which are usually direct installs from app stores.
To simulate an organic install:
- Make sure you have a mobile device connected to your computer.
- In Xcode, open the debug terminal.
- From Xcode Studio, install the app on the device or simulator.
- Wait for the app to launch.
- In the debug terminal, look for the app package name.
You should see the following:
Send start() in the log indicates that the SDK reports an install. This data comes from the onConversionDataSuccess
method in app delegate. Getting conversion data is discussed later in this guide.
Note: Starting SDK V5, onConversionDataSuccess
is the name of the method for getting conversion data. If you are using an SDK version lower than 5.0.0, the name of the method is onConversionDataReceived
. We recommend that you upgrade to SDK 5.0.0. To learn more, click here.
An organic install should now appear on the Overview page of the app dashboard.
If you don't see an install in the app dashboard, see SDK troubleshooting guide.
4.3 Simulate a non-organic install
A non-organic install is an attributed install that usually follows an ad engagement. You can simulate a non-organic install by using attribution links.
To do:
- Find out what is the itunes ID name of your app e.g. id123456789.
- In the URL below, replace <APP_ID> with your app's itunes ID:
For example:https://app.appsflyer.com/<APP_ID>?pid=sdk_test&c=sdk_test
Thehttps://app.appsflyer.com/id0123456789?pid=sdk_test&c=sdk_test
pid
parameter represents the media source name. Thec
parameter represents the campaign name. - Send this URL to the device (e.g. via email or WhatsApp).
- On the device, click on the URL.
- If the app is listed in the app store, you are redirected to the app store. Don't download the app from the app store. Proceed to step 5.
- If the app is not listed in the app store and is still in development, the screen shows a message that the app is not available in the app store. Simply proceed to step 5.
- In Xcode Studio, open the debug terminal.
- Connect the device to your computer using a USB cable.
- From Xcode, Install the app on the device.
- In the debug terminal, look for your app's itunes ID.
You should see the following:
A non-organic install should now appear in the Overview page of the app dashboard.
Note
When you're done testing and debugging the SDK integration, switch off the SDK logs.
This tab explains how to record in-app events and revenue, and how to set up deep linking.
Recording in-app events and revenue allows you to measure the quality of your users. Deep linking allows you to provide users with a better user experience.
This tab contains instructions for developers, but input from the marketer is essential because:
- The marketer should decide which in-app events need recording to measure user quality.
- The marketer has access to AppsFlyer dashboard, which is required for setting up OneLink for deep linking.
5. Record in-app events
In-App events provide insight into what is happening in your app. We recommend to take the time and define the events you want to record. Recording in-app events helps you to measure KPIs such as ROI (Return on Investment) and LTV (Lifetime Value).
There are several ways to record in-app events. The most common way is sending events via the SDK, which we discuss in this article. To learn about other ways to record in-app events, see our in-app events overview guide.
If your app belongs to a certain vertical, e.g. travel, gaming, eCommerce, etc., you can use the full list of recommended in-app events per vertical.
5.1 In-app event names and parameters
The SDK contains two types of constants that represent in-app event related info.
- Event names - these constants come in the format
AFEventEventName
.
For exampleAFEventPurchase
,AFEventAddToCart
. - Event parameters - These constants come in the format
AFEventParameterParameterName
.
For exampleAFEventParameterRevenue
,AFEventParamaterContentId
.
We strongly recommend using these constants for the following reasons:
- The standard naming allows AppsFlyer to automatically map events to SRNs such as Facebook, Google, Twitter, and Snapchat.
- Backward compatibility - if AppsFlyer decides to change the name of any event or event parameter, your implementation is backward compatible.
To use these two interfaces, import AppsFlyerLib.h if using Objective-C, or AppsFlyerLib if using Swift:
Put this in the class implementation file.
#import AppsFlyerLib.h
Put this in the Swift class file.
import AppsFlyerLib
5.2 Record revenue
You can send revenue with any in-app event. Use the af_revenue
(AFEventParameterRevenue
) event parameter to include revenue in the in-app event. You can populate it with any numeric value, positive or negative.
af_revenue
is the only event parameter that AppsFlyer counts as real revenue on the raw data and dashboard. For more details click here.
When sending events with revenue, keep the following in mind:
- If you set currency code (see example below), it should be a 3 character ISO 4217 code (default currency is USD).
- You can set the currency code for all events by setting the following property:
- Objective-C:
[AppsFlyerLib shared].currencyCode = @"ZZZ";
, - Swift:
AppsFlyerLib.shared().currencyCode = "ZZZ"
- Objective-C:
- The revenue value should not contain comma separators, currency sign, or text. A revenue event should be similar to 1234.56, for example.
Example: In-app event purchase event with revenue
[[AppsFlyerLib shared] logEvent: @"purchase"
withValues:@{
AFEventParamContentId:@"1234567",
AFEventParamContentType : @"category_a",
AFEventParamRevenue: @200,
AFEventParamCurrency:@"USD"
}];
AppsFlyerLib.shared().logEvent("purchase",
withValues: [
AFEventParamContentId:"1234567",
AFEventParamContentType : "category_a",
AFEventParamRevenue: 200,
AFEventParamCurrency:"USD"
]);
The purchase event above has $200 in revenue, appearing as revenue in the dashboard.
Recording negative revenue
There may be situations where you want to record negative revenue.
For example, a user receives a refund or cancels a subscription.
[[AppsFlyerLib shared] logEvent: @"cancel_purchase"
withValues:@{
AFEventParamContentId:@"1234567",
AFEventParamContentType : @"category_a",
AFEventParamRevenue: @-1.99,
AFEventParamCurrency:@"USD"
}];
AppsFlyerLib.shared().logEvent("cancel_purchase",
withValues: [
AFEventParamContentId:"1234567",
AFEventParamContentType : "category_a",
AFEventParamRevenue: -1.99,
AFEventParamCurrency:"USD"
]);
Note
Notice the following in the code above:
- The revenue value is preceded by a minus sign
- The event name has a unique value of "cancel_purchase" - to allow you to identify negative revenue events in the dashboard and raw data reports
5.3 In-app purchase validation
The AppsFlyers SDK provides server verification for in-app purchases. To validate a purchase, call validateAndLogInAppPurchase
.
This call automatically generates an af_purchase
in-app event, given that the purchase is validated.
This call has two callback blocks, one for success and one for ‘failure’ (for any reason, including validation fail). On success, a dictionary is returned with the receipt validation data provided by Apple servers.
Usage example of validating a purchase:
- (void) validateAndLogInAppPurchase:(NSString *) productIdentifier
price:(NSString *) price
currency:(NSString *) currency
transactionId:(NSString *) tranactionId
additionalParameters:(NSDictionary *) params
success:(void (^)(NSDictionary *response)) successBlock
failure:(void (^)(NSError *error, id reponse)) failedBlock;
[[AppsFlyerLib shared] validateAndLogInAppPurchase:@"ProductIdentifier" price:@"price"
currency:@"USD"
transactionId:@"transactionID"
additionalParameters:@{@"test": @"val" , @"test1" : @"val 1"}
success:^(NSDictionary *result){
NSLog(@"Purchase succeeded And verified!!! response: %@", result[@"receipt"]);
} failure:^(NSError *error, id response) {
NSLog(@"response = %@", response);
if([response isKindOfClass:[NSDictionary class]]) {
if([response[@"status"] isEqualToString:@"in_app_arr_empty"]){
// retry with 'SKReceiptRefreshRequest' because
// Apple has returned an empty response
// <YOUR CODE HERE>
}
} else {
//handle other errors
return;
}
}];
AppsFlyerLib
.shared()?
.validateAndLogInAppPurchase ("productIdentifier",
price: "price",
currency: "currency",
transactionId: "transactionId",
additionalParameters: [:],
success: {
guard let dictionary = $0 as? [String:Any] else { return }
dump(dictionary)
}, failure: { error, result in
guard let emptyInApp = result as? [String:Any],
let status = emptyInApp["status"] as? String,
status == "in_app_arr_empty" else {
// Try to handle other errors
return
}
// retry with 'SKReceiptRefreshRequest' because
// Apple has returned an empty response
// <YOUR CODE HERE>
})
When testing purchase validation in the Sandbox environment, add the following code:
[AppsFlyerLib shared].useReceiptValidationSandbox = YES;
AppsFlyerLib.shared().useReceiptValidationSandbox = true
Note this code must be removed from your production builds.
Validating an in-app purchase automatically sends an in-app purchase event to AppsFlyer. See the following sample data that is passed in the event_value parameter:
{
"some_parameter":"some_value", // from additional_event_values
"af_currency":"USD", // from currency
"af_content_id":"test_id", // from purchase
"af_revenue":"10", // from revenue
"af_quantity":"1", // from purchase
"af_validated":true // flag that AF verified the purchase
}
Note
Calling validateAndLogInAppPurchase
automatically generates an af_purchase in-app event. Sending this event yourself creates double duplicate event reporting.
5.4 In-app events limitations
- Event name: up to 45 characters
- Event value: must not exceed 1000 characters - if longer we may truncate it
- Pricing and revenue: use only digits and decimals, e.g. 5 or 5.2
- Pricing and revenue values can have up to 5 digits after the period, e.g. 5.12345
- Non-English characters are supported, in in-app events, other SDK APIs, starting from Android SDK V4.8.1.
5.5 Examples for recording in-app events
You can record in-app events by calling logEvent
with event name and value parameters. See In-App Events documentation for more details.
Below is a simple example of how to record a purchase event. For a comprehensive list of ready-made code snippets per vertical, see our guide for rich in-app events per verticals.
Example: In-app purchase event
[[AppsFlyerLib shared] logEvent:AFEventPurchase
withValues: @{
AFEventParamRevenue: @200,
AFEventParamCurrency: @"USD",
AFEventParamQuantity: @2,
AFEventParamContentId: @"092",
AFEventParamReceiptId: @"9277"
}];
Note
- The event value dictionary passed to the event SDK must be valid for JSON conversion by NSJSONSerialization. For more information, see here.
- For revenue, do not add any currency symbols as these are not recognized.
AppsFlyerLib.shared().logEvent(AFEventPurchase,
withValues: [
AFEventParamRevenue: "200",
AFEventParamCurrency: "USD",
AFEventParamQuantity: 2,
AFEventParamContent: "shoes",
AFEventParamContentId: "092",
AFEventParamReceiptId: "9277"]);
5.6 Record offline in-app events
If a user initiates an event when the internet connection is unavailable, AppsFlyer is still able to record it. This is how it works:
- SDK sends the events to AppsFlyer servers and waits for a response.
- If the SDK doesn’t receive a 200 response, the event is stored in the cache.
- Once the next 200 response is received, the stored event is re-sent to the server.
- If there are multiple events in the cache, they are sent to the server one promptly after another.
Note
The SDK cache can store up to 40 events, which means that only the first 40 events that happen offline are saved. Everything that comes afterward until the next 200 response, gets discarded.
The event time that appears in the raw data is the time the event is sent to AppsFlyer after the device goes online again. It is not the actual time that the event takes place.
5.7 Handle success and failure when recording in-app events
- In-app event recorded successfully.
- An error occurred when recording in-app event.
[[AppsFlyerLib shared] logEventWithEventName:AFEventPurchase
eventValues: @{
AFEventParamRevenue: @200,
AFEventParamCurrency: @"USD",
AFEventParamQuantity: @2,
AFEventParamContentId: @"092",
AFEventParamReceiptId: @"9277"
}
completionHandler:^(NSDictionary<NSString *,id> * _Nullable dictionary, NSError * _Nullable error){
if(dictionary != nil) {
NSLog(@"In app callback success:");
for(id key in dictionary){
NSLog(@"Callback response: key=%@ value=%@", key, [dictionary objectForKey:key]);
}
}
if(error != nil) {
NSLog(@"In app callback error:", error);
}
}];
AppsFlyerLib.shared().logEvent(name: "In app event name", values: ["id": 12345, "name": "John doe"], completionHandler: { (response: [String : Any]?, error: Error?) in
if let response = response {
print("In app event callback Success: ", response)
}
if let error = error {
print("In app event callback ERROR:", error)
}
})
}
In the event that an error occurs when recording the in-app event, an error code and string description are provided, as indicated in the table that follows.
Error code | String description |
---|---|
10 |
"Event timeout. Check 'minTimeBetweenSessions' param" |
11 |
"Skipping event because 'isStopTracking' enabled" |
40 |
Network error: Error description comes from Android |
41 |
"No dev key" |
50 |
"Status code failure" + actual response code from the server |
6. Deep linking with OneLink
OneLink is the AppsFlyer solution for multi-platform attribution, redirection, and deep linking.
6.1 Device detection and redirection
OneLink detects the device type upon click and redirects the user to the matching destination, e.g. Google Play, iOS app store, out-of-store markets, or web pages.
The OneLink redirections guide discusses implementing multi-platform attribution links (no SDK coding required). Its also the basis for deep linking.
6.2 Deep linking
Deep linking allows you to send users to specific activities and serve them with customized content. This is especially useful when running retargeting campaigns.
To set up deep linking with OneLink, a marketer with access to AppsFlyer dashboard and a developer with access to the app must work together.
See our guide on setting up deep linking with OneLink.
6.3 Deferred deep linking
Deferred deep linking allows you to deep link new users and serve them with customized content upon first launch of the app. This is unlike regular deep linking where the app needs to already be installed on the user's device.
To set up deferred deep linking with OneLink, the developer also needs access to the AppsFlyer dashboard.
The setup for deferred deep linking is the same as deep linking. The only difference is that you need to implement additional logic in the app in order to deep link the users and serve them with customized content after they install and launch the app.
See our guide on deferred deep linking to learn more.
6.4 Get deep link data
The SDK provides you with the conversion or engagement data following every install or deep linking event. You can use this data to customize content and the app's behavior programmatically.
To get deep linking data when the direct deep link is used and the app is opened, implement the onAppOpenAttribution method.
To get deep linking re-engagement data manually at any time, implement the performOnAppAttribution method. This allows access to re-engagement data without recording a new re-engagement.
See our guide on deep linking data to learn more.
6.5 Configure push notification deep link resolution
The addPushNotificationDeepLinkPath
method provides app owners with a flexible interface for configuring how deep links are extracted from push notification payloads.
By default, the SDK looks for a deep link value in the af
key of a push notification’s JSON
payload. Many push providers use proprietary JSON
schemas that the SDK can’t resolve without additional configuration.
addPushNotificationDeepLinkPath
enables you to configure which key in the push notification’s JSON payload the SDK should use for the deep link value.
Use this method if you’re integrating your app with push providers that don’t use the default push notification JSON schema the SDK expects.
When calling addPushNotificationDeepLinkPath
the SDK verifies that:
- The required key exists in the payload.
- The key contains a valid OneLink URL.
Basic configuration
Consider the following call to addPushNotificationDeepLinkPath
[AppsFlyerLib shared] addPushNotificationDeepLinkPath:@[@"deeply", @"nested", @"deep_link"]]
AppsFlyerLib.shared().addPushNotificationDeepLinkPath(["deeply", "nested", "deep_link"])
and these scenarios:
Scenario 1
Your app is invoked via push notification. It contains a payload structured as follows.
{
...
"deeply": {
"nested": {
"deep_link": "https://yourdeeplink2.onelink.me"
}
}
...
}
In this scenario, the SDK extracts the value of deep_link
and proceeds with the deep linking flow.
Scenario 2
Your app is invoked via push notification. It contains a payload structured as follows.
{
...
"deeply": {
"nested": {
"banana": "https://yourdeeplink2.onelink.me"
}
},
...
}
In this scenario, when the call executes the SDK can't find the deep_link
key in the payload. Therefore, nothing happens.
Scenario 3
Your app is invoked via push notification. It contains a payload structured as follows.
{
...
"deeply": {
"nested": {
"deep_link": "Corrupted url or regular string"
}
},
...
}
In this scenario, although the SDK finds the deep_link
key, its deep link value is invalid. Therefore, when the above call executes, nothing happens.
Advanced configuration
To configure multiple possible payload structures, call addPushNotificationDeepLinkPath
multiple times:
- The first call that yields a valid deep link value is used
- Other calls are ignored
If none of the payload structures match or no valid OneLink URL is found in the payload, nothing happens.
For example, consider the following payload
{
...
"deeply": {
"nested": {
“deep_link”: “https://yourdeeplink2.onelink.me”
}
},
“this”: {
“is”: {
"banana": "phone"
}
}
...
}
and the following calls to addPushNotificationDeepLinkPath
.
// this.is.deep_link key isn’t found - nothing happens
[AppsFlyerLib shared] addPushNotificationDeepLinkPath:@[@"this", @"is", @"deep_link"]]
// this.is.banana key found, but contains invalid OneLink URL - nothing happens
[AppsFlyerLib shared] addPushNotificationDeepLinkPath:@[@"this", @"is", @"banana"]]
// deeply.nested.deep_link key found and contains valid OneLink URL - proceed deep linking flow
[AppsFlyerLib shared] addPushNotificationDeepLinkPath:@[@"deeply", @"nested", @"deep_link"]]
// this.is.deep_link key isn’t found - nothing happens
AppsFlyerLib.shared().addPushNotificationDeepLinkPath(["this", "is", "deep_link"]);
// this.is.banana key found, but contains invalid OneLink URL - nothing happens
AppsFlyerLib.shared().addPushNotificationDeepLinkPath(["this", "is", "banana"])
// deeply.nested.deep_link key found and contains valid OneLink URL - proceed deep linking flow
AppsFlyerLib.shared().addPushNotificationDeepLinkPath(["deeply", "nested", "deep_link"])
7. Get conversion data
Get conversion data
You can access user attribution data in real-time for every new install, directly from the SDK.
By doing this you can serve users with personalized content or send them to specific activities within the app (see deferred deep linking in this article), which can greatly enhance their engagement with your app.
To obtain conversion data from the iOS SDK, implement the following methods:
- (void) onAppOpenAttribution:(NSDictionary*) attributionData {
NSLog(@"onAppOpenAttribution");
for(id key in attributionData){
NSLog(@"onAppOpenAttribution: key=%@ value=%@", key, [attributionData objectForKey:key]);
}
}
- (void)onAppOpenAttributionFailure:(NSError *)error {
NSLog(@"%@", [error description]);
}
-(void)onConversionDataSuccess:(NSDictionary*) installData {
BOOL first_launch_flag = [[installData objectForKey:@"is_first_launch"] boolValue];
NSString *status = [installData objectForKey:@"af_status"];
if(first_launch_flag) {
if ([status isEqualToString:@"Non-organic"]){
NSString *sourceID = [installData objectForKey:@"media_source"];
NSString *campaign = [installData objectForKey:@"campaign"];
NSLog(@"This is a non-organic install. Media source: %@ Campaign: %@", sourceID, campaign);
} else {
NSLog(@"This is an organic install");
}
} else {
NSLog(@"Not first launch");
}
}
-(void)onConversionDataFail:(NSError *) error {
NSLog(@"%@", [error description]);
}
class AppDelegate: UIResponder, UIApplicationDelegate, AppsFlyerLibDelegate{
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
AppsFlyerLib.shared().appsFlyerDevKey = "MY_DEV_KEY"
AppsFlyerLib.shared().appleAppID = "123456789"
AppsFlyerLib.shared().delegate = self
//AppsFlyerLib.shared().isDebug = true
//AppsFlyerLib.shared().appInviteOneLinkID = "ONELINK_ID";
return true
}
func applicationDidBecomeActive(_ application: UIApplication) {
AppsFlyerLib.shared().start()
}
func onConversionDataSuccess(_ installData: [AnyHashable: Any]) {
guard let first_launch_flag = installData["is_first_launch"] as? Int else {
return
}
guard let status = installData["af_status"] as? String else {
return
}
if(first_launch_flag == 1) {
if(status == "Non-organic") {
if let media_source = installData["media_source"] , let campaign = installData["campaign"]{
print("This is a Non-Organic install. Media source: \(media_source) Campaign: \(campaign)")
}
} else {
print("This is an organic install.")
}
} else {
print("Not First Launch")
}
}
func onConversionDataFail(_ error: Error!) {
if let err = error{
print(err)
}
}
func onAppOpenAttribution(_ attributionData: [AnyHashable : Any]!) {
if let data = attributionData{
print("\(data)")
}
}
func onAppOpenAttributionFailure(_ error: Error!) {
if let err = error{
print(err)
}
}
The two most important methods are:
onConversionDataSuccess
- provides conversion data for new installs.
Note: Starting SDK V5,
onConversionDataSuccess
is the name of the method for getting conversion data. If you are using an SDK version lower than 5.0.0, the name of the method isonConversionDataReceived
. We recommend that you upgrade to SDK 5.0.0. To learn more, click here.onAppOpenAttribution
- provides retargeting conversion data when an existing app is launched, either manually or through deep linking.
To learn more about conversion data, see our guide on conversion data scenarios.
8. Attribution
Measure uninstalls
To learn how to setup uninstall measurement, read here.
Setting a SDK started handler
If you want to receive a confirmation that the SDK started successfully and notified AppsFlyer servers, implement the startWithCompletionHandler
handler. You can then apply logic to handle success or failure of the SDK launch.
Implementation example
[[AppsFlyerLib shared] startWithCompletionHandler:^(NSDictionary<NSString *,id> *dictionary, NSError *error) {
if (error) {
NSLog(@"%@", error);
return;
}
if (dictionary) {
NSLog(@"%@", dictionary);
return;
}
}];
AppsFlyerLib.shared()?.start(completionHandler: { (dictionnary, error) in
if (error != nil){
print(error ?? "")
return
} else {
print(dictionnary ?? "")
return
}
})
In the event that an error occurs during the request listener, an error code and string description are provided, as indicated in the table that follows.
Error code | String description |
---|---|
10 |
"Event timeout. Check 'minTimeBetweenSessions' param" |
11 |
"Skipping event because 'isStopTracking' enabled" |
40 |
Network error: Error description comes from Android |
41 |
"No dev key" |
50 |
"Status code failure" + actual response code from the server |
Set additional custom data
The setAdditionalData
API is required to integrate on the SDK level with several external partner platforms, including Segment, Adobe and Urban Airship.
Use this API only if the integration article of the platform specifically states setAdditionalData
API is needed.
setAdditionalData
code example:
NSDictionary* CustomDataMap = [[NSDictionary alloc] initWithObjectsAndKeys:@"value_of_param_1", @"custom_param_1", nil];
[[AppsFlyerLib shared] setAdditionalData:CustomDataMap];
let CustomDataMap: [AnyHashable: Any] = [
"custom_param_1" : "value_of_param_1"
]
AppsFlyerLib.shared().customData = CustomDataMap
Attribute app sessions initiated from owned websites (domains)
App owners using Universal Links for deep linking (without OneLink), and have a domain associated with their app can attribute sessions initiated via this domain using the appendParametersToDeepLinkingURL
method.
For example, a user searches Google and clicks on your domain, www.example.com:
- If the user doesn’t have the app installed, they are directed to the website (www.example.com).
- If the user has the app installed on their device, they are deep linked into the app associated with www.example.com. The session is attributed to the media source (
pid
parameter) specified inappendParametersToDeepLinkingURL
.
See the iOS SDK reference for additional information.
Note: Smart Banners help app owners convert website visitors to app users.
9. Sessions
Custom time between sessions
By default, at least 5 seconds must pass between two app launches to count as two separate sessions (more about counting sessions).
Use the following API to set the minimum time between sessions:
[AppsFlyerLib shared].minTimeBetweenSessions = <your_custom_time_in_seconds>;
AppsFlyerLib.shared().minTimeBetweenSessions = <your_custom_time_in_seconds>
Setting a high value to the custom time between launches may badly impact APIs relying on sessions data, such as deep linking.
Background sessions for utility apps
Unavailable in iOS.
10. Owned media
Resolve wrapped deep link URLs
Some 3rd party services such as email service providers wrap links in emails with their own click recording domains. Some even allow you to set your own click recording domains. If OneLink is wrapped in such domains, it might limit its functionality.
To overcome this issue you can use the setResolveDeepLinkURLs
API. Use this API to get the OneLink from click domains that launch the app. Make sure to call this API before SDK initialization.
For example, you have three click domains that redirect to your OneLink which is https://mysubdomain.onelink.me/abCD. Use this API to get the OneLink that your click domains redirect toThis API method receives a list of domains that the SDK resolves.
[AppsFlyerLib shared].resolveDeepLinkURLs = @[@"example.com",@"click.example.com"];
AppsFlyerLib.shared().resolveDeepLinkURLs = ["example.com", "click.example.com"]
The code above allows you to use your click domain while preserving OneLink functionality. The click domains are responsible for launching the app. The API, in turn, gets the OneLink from these click domains and then you can use the data from this OneLink to deep link and customize user content.
Record push notifications
With AppsFlyer, you can record push notifications as part of retargeting campaigns.
To enable this feature, call the handlePushNotificationData
method inside AppDelegate.m.
-(void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler {
[[AppsFlyerLib shared] handlePushNotification:userInfo];
}
func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
AppsFlyerLib.shared().handlePushNotification(userInfo)
}
For more information on push notification measurement, read here.
User invite attribution
Allowing your existing users to invite their friends and contacts as new users to your app, can be a key growth factor for your app. With AppsFlyer, you can attribute and record installs that originate from user invites within your app.
For details, see the User Invite Attribution article.
Cross promotion attribution
Note! Cross promotion by IDFV requires SDK 6.0.2+. IDFV is collected automatically and no further action is required by the developer.
Cross promoting apps can be a major growth factor in driving additional installs for your apps. AppsFlyer enables you to attribute and record installs originating from a cross-promotion campaign of one of your apps from within the current app the user has.For details, see the Cross Promotion Attribution article, here.
11. User identifiers
Get AppsFlyer ID
An AppsFlyer unique ID is created for every new install of an app. You can use this ID for various purposes:
- Send server-to-server in-app events.
- Match it with user records in your backend systems.
- Map entries when merging data from pull and push API.
Use the following API to obtain the unique ID:
NSString *appsflyerId = [AppsFlyerLib shared].getAppsFlyerUID;
let appsflyerId = AppsFlyerLib.shared().getAppsFlyerUID()
Set Customer User ID
To set your Customer User ID:
[AppsFlyerLib shared].customerUserID = @"my user id";
AppsFlyerLib.shared().customerUserID = "my user id"
In iOS, the Customer User ID needs to be set with every app launch. We recommend setting the Customer User ID early in the app flow, as it is only associated with events reported after its setup:
- If
setCustomerUserId
is called before callingstart
, the Customer User ID appears in the raw data reports for installs and for events. - If it is set after, Customer User ID is only associated with events that are recorded after setting the Customer User ID.
Getting Customer User ID:
To avoid setting the Customer User ID value again beyond the first launch, and to reduce calls to your server to get the customer user ID, you can check if its value is empty or not by using:
NSString *customerUserID = [AppsFlyerLib shared].customerUserID;
let customerUserID = AppsFlyerLib.shared().customerUserID
For more information about the Customer User ID, click here.
Delay SDK init for customerUserID
You can delay the SDK Initialization until the Customer User ID is set. This is useful if its important for you that install and event data contain your customer user ID.
Implement the following code:
- (void)applicationDidBecomeActive:(UIApplication *)application {
NSString *customUserId = [[NSUserDefaults standardUserDefaults] stringForKey:@"customerUserId"]; // Your custom logic of retrieving CUID
if (customUserId != nil && ![customUserId isEqual: @""]) {
[AppsFlyerLib shared].customerUserID = customUserId; // Set CUID in AppsFlyer SDK for this session
[[AppsFlyerLib shared] start]; // Start
}
}
func applicationDidBecomeActive(_ application: UIApplication) {
let customUserId = UserDefaults.standard.string(forKey: "customUserId") // your logic to retrieve CUID
if(customUserId != nil && customUserId != ""){
AppsFlyerLib.shared().customerUserID = customUserId // Set CUID in AppsFlyer SDK for this session
AppsFlyerLib.shared().start() // Start
}
}
To learn more about delaying the SDK initialization until the Customer User ID is available, go here.
Warning
Use this API only in cases where it is appropriate for your business logic. Using this API increases the chance for discrepancies and might make the app more exposed to fraud.
12. User privacy
Opt-out
In some extreme cases, you might want to shut down all SDK data logging due to legal and privacy compliance. This can be achieved with the isStopped property. Once this property is set to true, the SDK stops functioning and no longer communicates with AppsFlyer servers.
There are several different scenarios for user opt-out. We highly recommend following the exact instructions for the scenario that is relevant for your app.
Warning
Use the isStopped API only in cases where you want to fully ignore this user from any and all recording. Using this API SEVERELY impacts your attribution, data collection and deep linking mechanism.
[AppsFlyerLib shared].isStopped = true;
AppsFlyerLib.shared().isStopped = true
In any event, the SDK can be reactivated by calling the same API, by passing false.
Important!
Do not call start
if isStopped
is set to true
To start logging data again, set isStopped
to false
.
Anonymize user data
Use this API during the SDK Initialization to explicitly anonymize a user install, events and sessions:
[AppsFlyer shared].disableAdvertisingIdentifier = YES;
AppsFlyerLib.shared().disableAdvertisingIdentifier = true
Logging data can be restarted by setting anonymizeUser
to false.
Warning
Anonymizing users SEVERELY impacts your attribution information.Use this option ONLY for regions which legally prevents you from collecting your users' information.
Configuring App Tracking Transparency (ATT) support
Apple messaging indicates that starting iOS 14.5, IDFA collection will require user authorization. Practically, it means IDFA access will be governed by the App Tracking Transparency (ATT) framework. On iOS 14.5+ devices, the AppsFlyer iOS SDK uses the ATT framework to obtain access to device IDFA.
When attribution occurs using IDFA, it's important IDFA is sent on first launch. For this reason, the SDK provides the waitForATTUserAuthorization
utility.
Implementation overview
waitForATTUserAuthorization
enables you to configure how long should the SDK defer sending data to the AppsFlyer servers, and wait for ATT status. A full implementation of ATT support consists of:
- Configuring
waitForATTUserAuthorization
in the app initialization stage - Calling
ATTrackingManager.requestTrackingAuthorization
, whenever the ATT consent dialog should be displayed
waitForATTUserAuthorization description
When a user launches the app, ATT status is undetermined. During the waitForATTUserAuthorization
timeout:
- The SDK queues the launch event and consecutive in-app events in-memory, similar to how offline events are recorded
- If the user consents to IDFA collection, the SDK adds the IDFA to the queued in-memory events and starts (without waiting for the timeout to end)
- If the timeout ends and ATT consent is unauthorized or remains undetermined, the SDK starts and sends the queued in-memory events without IDFA.
Considerations
- The default timeout duration is 60 seconds
-
If the timeout ends and ATT consent status remains undetermined:
- Before iOS 14.5: IDFA is collected by default
- iOS 14.5 and after: IDFA is not collected by default
- If the user moves the app to the background during the timeout:
- The timer pauses until the app returns to the foreground
- Events are cached in-memory
- If the user shuts down or kills the app during the timeout:
- The timer is restarted on the next app launch
- Cached events are lost
Pitfalls
waitForATTUserAuthorization
is required for IDFA collection on iOS 14.5+. Not callingwaitForATTUserAuthorization
will result in launches being sent without IDFA- Don't call
start
in therequestTrackingAuthorization
callback. Doing so might result in incomplete or broken attribution
Configure ATT support in the iOS SDK
To configure ATT support in the iOS SDK:
- In your initialization code, configure
waitForATTUserAuthorization
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { [[AppsFlyerLib shared] setAppsFlyerDevKey:@"<AF_DEV_KEY>"]; [[AppsFlyerLib shared] setAppleAppID:@"<APPLE_APP_ID>"]; [[AppsFlyerLib shared] setDelegate:self]; [[AppsFlyerLib shared] waitForATTUserAuthorizationWithTimeoutInterval:60]; return YES; }
class AppDelegate: UIResponder, UIApplicationDelegate, AppsFlyerLibDelegate { func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { AppsFlyerLib.shared().appsFlyerDevKey = "<AF_DEV_KEY>" AppsFlyerLib.shared().appleAppID = "<APPLE_APP_ID>" AppsFlyerLib.shared().delegate = self AppsFlyerLib.shared().waitForATTUserAuthorization(timeoutInterval: 60) ... } ... }
- In your application code, call
ATTrackingManager.requestTrackingAuthorization
to show the ATT consent dialog. For example, if you want to present the user with the consent dialog when a certain view finished loading:- (void)viewDidLoad { [super viewDidLoad]; [ATTrackingManager requestTrackingAuthorizationWithCompletionHandler:^(ATTrackingManagerAuthorizationStatus status) { NSLog(@"Status: %lu", (unsigned long)status); }]; }
override func viewDidLoad() { super.viewDidLoad() ATTrackingManager.requestTrackingAuthorization { (status) in } }
This call triggers the ATT consent dialog.
Customizing ATT consent dialog
Customizing the ATT consent dialog
- Select the project
Info.plist
file in the Xcode Project Navigator. - Add an entry to the list: Press
+
next toInformation Property List
. - Scroll down and select
Privacy - Tracking Usage Description
. - Add as the
value
the wording you want to present to the user when asking for permission to collect the IDFA.
Kids apps
To make the app available in the App Store Kids Category, Apple requires that you do not "transmit personally identifiable information or device information to third parties". To comply with this, you must explicitly disable the AdSupport and iAD frameworks.
To disable the ad frameworks:
disableCollectASA = true
. Learn moredisableAdvertisingIdentifier = true
. Learn more
[AppsFlyerLib shared].disableCollectASA = YES [AppsFlyerLib shared].disableAdvertisingIdentifier = YES;
AppsFlyerLib.shared().disableCollectASA = true AppsFlyerLib.shared().disableAdvertisingIdentifier = true
Exclude partners from getting data
In some cases, advertisers may want to stop sharing user-level data with ad networks/partners for specific users. Reasons for this include:
- Privacy policies such as CCPA or GDPR
- User opt-out mechanisms
- Competition with some partners (ad networks, 3rd parties)
AppsFlyer provides two API methods to stop sharing data with some or all partners:
- setSharingFilter: Used by advertisers to prevent sharing data with some (one or more) networks/integrated partners.
- setSharingFilterForAllPartners: Used by advertisers to prevent sharing data with all networks/integrated partners.
These filtering methods are supported starting in SDK V5.4.1.
The filtering method must be called every time the SDK is initialized and affects the whole session. If it takes time to determine whether you need to set the sharing filters, then delay the SDK initialization.
When the method is activated before the first start call:
- Users from SRNs are attributed as Organic, and their data is not shared with integrated partners.
- Users from click ad networks (non-SRNs) are attributed correctly in AppsFlyer, but not shared with the ad networks via postbacks, APIs, raw data reports, or by any other method.
Currently, uninstall data can't be filtered using these methods. However, you can stop sending Uninstall events to partners using their setup pages in AppsFlyer.
continueUserActivity
Description |
Calls onAppOpenAttribution when an app is opened from a Universal Link for iOS 9 and above. |
Method signature |
|
Usage example |
|
currencyCode
Description |
Set the currency code for events with revenue. Accepts ISO currency codes. |
Method signature |
|
Usage example |
|
anonymizeUser
Description |
Anonymize a user installs, events, and sessions. For more information, see anonymizing user data. |
Method signature |
|
Usage example |
|
disableCollectASA
Description |
Starting SDK version 4.8.11, AppsFlyer SDK dynamically loads the Apple iAd.framework. This framework is required to record and measure the performance of Apple Search Ads in your app. If you don't want AppsFlyer to dynamically load this framework, set this property to true. |
Method signature |
|
Usage example |
|
handleOpenUrl
Description |
This method reports the URI scheme to AppsFlyer SDK when the app opens using deep linking with URI scheme. This method is placed inside AppDelegate. |
Method signature |
|
Usage example |
|
handlePushNotification
Description |
Measure and get data from push notifications campaigns. For more information, see recording push notifications. |
Method signature |
|
Usage example |
|
isDebug
Description |
Show AppsFlyer SDK logs in Xcode console. Debugging should be restricted to the development phase only. Do not distribute the app to the App Store with debugging enabled. This poses major security and privacy risks. |
Method signature |
|
Usage example |
|
isStopped
Description |
Shut down all SDK functionality. For more information, see user privacy - opt-out. |
Method signature |
|
Usage example |
|
onAppOpenAttribution
Description |
Get deep link data when the app opens via a deep link. |
Method signature |
|
Usage example |
|
onAppOpenAttributionFailure
Description |
Handle errors in getting deep link data. |
Method signature |
|
Usage example |
|
onConversionDataSuccess
Description |
Get conversion data after an install. Useful for deferred deep linking. Note: Starting SDK V5, |
Method signature |
|
Usage example |
|
onConversionDataFail
Description |
Handle errors when failing to get conversion data from installs. |
Method signature |
|
Usage example |
|
didResolveDeepLink
Description |
Send mobile users with and without your app installed to a specific in-app activity as soon as the app is opened. Learn more |
Method signature |
|
performOnAppAttribution
Description |
Allows developers to manually re-trigger onAppOpenAttribution with a specific link (URI or URL), without recording a new re-engagement. This method may be required if the app needs to redirect users based on the given link, or resolve the AppsFlyer short URL while staying in the foreground/opened. This might be needed because regular onAppOpenAttribution callback is only called if the app was opened with the deep link. |
Method signature |
|
Usage example |
|
registerUninstall
Description |
Measure uninstalls. See uninstall measurement for iOS. |
Method signature |
|
Usage example |
|
resolveDeepLinkURLs
Description |
Resolve OneLink from click domains. For more information, see resolving wrapped deep link URLs. |
Method signature |
|
Usage example |
|
setAdditionalData
Description |
Adding additional data to be sent to external partner platforms. |
Method signature |
|
Usage example |
See setting additional custom data. |
setAppInviteOneLink
Description |
Set the OneLink template ID that is used to create custom attribution links for user invites. |
Method signature |
|
Usage example |
See setting OneLink for user invite attribution. |
setAppleAppID
Description |
Set your app ID (the itunes ID) so that the SDK can send data to the correct app dashboard. Use it when you initialize the SDK. |
Method signature |
|
Usage example |
|
appsFlyerDevKey
Description |
Set the AppsFlyer dev key so that SDK can send data to the correct app dashboard. Use it when you initialize the SDK. To learn how to get your dev key, see here. |
Method signature |
|
Usage example |
|
setCustomerUserID
Description |
Set the customer user ID. For more information, see setting the customer user ID. |
Method signature |
|
Usage example |
setPartnerData
Description |
Partners and advertisers can add more data in SDK events. |
Method signature |
|
Usage example |
|
setSharingFilter
Description |
Used by advertisers to set some (one or more) networks/integrated partners to exclude from getting data. |
Method signature |
|
Usage example |
|
setSharingFilterForAllPartners
Description |
Used by advertisers to exclude all networks/integrated partners from getting data. |
Method signature |
|
Usage example |
|
setShouldCollectDeviceName
Description |
Whether the SDK should collect the device name. Default is false. |
Method signature |
|
Usage example |
|
setUseUninstallSandbox
Description |
To test uninstalls for apps that are still in development (not yet published to the Apple App Store), set this property to true. |
Method signature |
|
Usage example |
|
startWithCompletionHandler
Description |
Check if start is successful or not. You can implement your own logic to handle success or failure of the SDK launch. |
Method signature |
|
Usage example |
logEvent
Description |
Send in-app events to AppsFlyer. For more information, see recording in-app events. |
Method signature |
|
Usage example |
|
validateAndLogInAppPurchase
Description |
Set the AppsFlyer dev key so that SDK can send data to the correct app dashboard. To learn how to get your dev key, see here. Use it when you initialize the SDK. |
Method signature |
|
Usage example |
See In-app purchase validation. |
disableAdvertisingIdentifier
Description |
Starting SDK version 4.8.11, AppsFlyer SDK dynamically loads the Apple adSupport.framework. This framework is required to collect IDFA for attribution purposes. If you don't want AppsFlyer to dynamically load this framework, set this property to true. |
Method signature |
|
Usage example |
[AppsFlyerLib shared].disableAdvertisingIdentifier= YES; AppsFlyerLib.shared().disableAdvertisingIdentifier = true |
waitForATTUserAuthorization
Description |
Used if you want to request user authorization via a popup before accessing app-related data for recording the user or the device (for example, IDFA). If the user opts-in, the IDFA will be passed to the SDK. The timeout interval gives the user a set amount of time to opt-in to IDFA collection. After the timer expires, the IDFA is not collected. |
Method signature |
|
Usage example |
|
start
Description |
Once this API is invoked, the SDK will start, sessions will be immediately sent, and all background foreground transitions will record a session. |
Method signature |
|
Usage example |
|
logLocation
Description |
Manually record user location. |
Method signature |
|
Usage example |
|
appendParametersToDeepLinkingURL
Description |
Enables app owners using Universal Links for deep linking (without OneLink) to attribute sessions initiated via a domain associated with their app. Call this method before calling start. The method requires:
|
Method signature |
|
Usage example |
|
addPushNotificationDeepLinkPath
Description |
Configure how the SDK extracts deep link values from push notification payloads. |
Method signature |
|
Usage example |
This call matches the following payload structure:
|
disableSKAdNetwork
Description |
Allows you to disable SKAdNetwork attribution. Set to true to disable. |
Method signature |
|
Usage example |
|
Comments
Hi everyone!
We want to update you that a new AppsFlyer iOS SDK has been released - version 4.10.3.
This version supports iOS 13 updates, specifically push token retrieval for Uninstall Measurement.
The new version also includes bugs fixes and improvements.
We recommend that you update your app to use version 4.10.3.
Thank you!
Hi everyone!
We want to update you that a new AppsFlyer iOS SDK has been released - version 5.0.0.
The new version introduces changes to method names and SDK functionality.
We recommend that you update your app to use version 5.0.0. If you update to version 5.0.0, pay attention to changes in method names and conversion data format.
For more information, see the following link:
https://support.appsflyer.com/hc/en-us/articles/360003090618
Hi everyone!
We want to update you that a new AppsFlyer iOS SDK has been released - version 5.1.0.
The new version improves Objective-C - Swift interoperability.
When our SDK guarantees that a certain value is not null, we return non-optional value. Otherwise, we return an optional so you need to unwrap it manually. If you migrated from older versions of the SDK and used to have unwrapping “?” character in any code that references the SDK, you will get compile-time error now unless you remove “?” as the returned type is no longer optional.
We recommend that you update your app to use version 5.1.0. If you update to this version, make sure you adapt your code accordingly.
For more information on optional and unwrapping in swift, see the following link:
https://docs.swift.org/swift-book/LanguageGuide/OptionalChaining.html
Hi there!
AppsFlyer iOS SDK version 5.2.0 has been released.
Why should you update to it?
Besides maintenance and bug fixes the new version introduces the following features :
* Allow setting advanced deferred deep linking for Facebook users
* Allow generating branded links when using User Invite feature
* Extended security, logging, and debugging capabilities
If you use these features or haven't updated the SDK in a while, we recommend doing so.
Hi everyone!
AppsFlyer iOS SDK version 5.3.0 has been released! This version includes general bugs fixes and improvements, plus the following additional features:
* Direct deep linking attribution data can be accessed at any time
* Additional parameters are available for cross-promotion impressions
* Main apps and app extensions get the same AppsFlyer ID, so in-app events are grouped similarly
If you need these features or haven't updated the SDK in a while, we recommend doing so.
Thanks!
Hi everyone!
AppsFlyer iOS SDK version 5.4.1 has been released! This version includes general bug fixes and improvements, plus the following additional features:
* setSharingFilter API that allows advertisers to control sharing data with integrated partners/networks.
* onAppOpenAttribution method - aligned the response of iOS Universal Links with the rest of the use cases.
If you need these features or haven't updated the SDK in a while, this is a great time to do so :)
Please sign in to leave a comment.