Skip to content

AppsFlyerSDK/appsflyer-apple-app-clips-sample-app

Repository files navigation

Latest version including integration with AppsFlyer SDK and measurement solution

Those interested in an example vanilla App Clip project without AppsFlyer code, go to tag vanilla

Apple App Clips Sample App

Table of content

Let’s imagine for a moment that you walk into a coffee shop and notice that there is a long line. Next to the cash register you see a sign inviting you to skip the line and purchase your coffee via the coffee shop’s app.

Any first thoughts?

Let me tell you, mine would be, “No way am I going to install an app that will take up precious real-estate on my device.” It would then lead me to question what kind of data they will collect about my life and then I will probably be spammed. No thanks. And all this just to skip the line...

Skip the line without giving up on privacy

Apple App Clips are about to change the way you think, and; moreover, they will probably change the way that we interact with our environment using our mobile devices. App Clips enable you to do ‘here-and-now’ activities using your device almost instantly without sacrificing privacy or sharing your geo location.

In the coffee shop example above, the QR code will invoke an App Clip where you are given the opportunity to identify using Apple Sign-in and purchase using Apple Pay, allowing you to complete your purchase within seconds, effectively skipping the line.

App Clips require app developers to understand a few new concepts and develop the App Clip alongside their app, which may require some refactoring.

We hope you'll find this sample app useful.

Any comments are appreciated and of course stars ⭐️

   func scene(_ scene: UIScene, continue userActivity: NSUserActivity) {
      
      // Get URL components from the incoming user activity
      guard userActivity.activityType == NSUserActivityTypeBrowsingWeb,
            let incomingURL = userActivity.webpageURL,
            let components = NSURLComponents(url: incomingURL,
            resolvingAgainstBaseURL: true)
      else {
          return
      }
            
      // Direct to the linked content in your app clip.
      if let fruitName = components.queryItems?.first(where: { $0.name == "fruit_name" })?.value {
        walkToViewWithParams(fruitName: fruitName)
      }
  }

App clip

The following code uses the configured app group to create the shared UserDefaults instance and store a custom_user_id on first launch:

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
                 
        guard let sharedUserDefaults = UserDefaults(suiteName: "group.fruitapp.appClipMigration") else {
            return true
        }
        
        if sharedUserDefaults.string(forKey: "custom_user_id") == nil {
            sharedUserDefaults.set(UUID().uuidString, forKey: "custom_user_id")
        }
        
        return true
    }

Full app

When users install the full app, it can access the shared user defaults. For example, to access the custom_user_id stored in the previous code:

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        
        guard let sharedUserDefaults = UserDefaults(suiteName: "group.fruitapp.appClipMigration"),
              let uuid = sharedUserDefaults.string(forKey: "custom_user_id")
        else {
            // no custom_user_id found from app clip
            return true
        }

        print("uuid is \(uuid)")
        
        return true
    }

If you create an app clip that users invoke at a physical location, you may need to confirm the user’s location before allowing them to perform a task.

    func verifyUserLocation(activity: NSUserActivity?) {
      
      // Guard against faulty data.
      guard activity != nil else { return }
      guard activity!.activityType == NSUserActivityTypeBrowsingWeb else { return }
      guard let payload = activity!.appClipActivationPayload else { return }
      guard let incomingURL = activity?.webpageURL else { return }

      // Create a CLRegion object.
      guard let region = location(from: incomingURL) else {
          return
      }
      
      // Verify that the invocation happened at the expected location.
      payload.confirmAcquired(in: region) { (inRegion, error) in
          guard let confirmationError = error as? APActivationPayloadError else {
              if inRegion {
                  print("The location of the NFC tag matches the user's location.")
              } else {
                  print("The location of the NFC tag doesn't match the records")
              }
              return
          }
          
          if confirmationError.code == .doesNotMatch {
              print("The scanned URL wasn't registered for the app clip")
          } else {
              print("The user denied location access, or the source of the app clip’s invocation wasn’t an NFC tag or visual code.")
          }
      }
  }

If notifications are important for your app clip’s functionality, enable it to schedule or receive notifications for up to 8 hours after each launch.

func setNotification(){
    let center = UNUserNotificationCenter.current()
    center.getNotificationSettings(completionHandler: { settings in
        if settings.authorizationStatus == .ephemeral {
            
            let content = UNMutableNotificationContent()
            content.title = "Hello from the fruitapp"
            content.body = "Check out oour amazing fruit"
            content.categoryIdentifier = "alarm"
            content.userInfo = ["customData": "myData"]
            content.sound = UNNotificationSound.default
        
            // Configure the recurring date.
            var dateComponents = DateComponents()
            dateComponents.calendar = Calendar.current

            dateComponents.hour = 10    // 10:00 hours
            dateComponents.minute = 20  // 20 min
               
            // Create the trigger as a repeating event.
            let trigger = UNCalendarNotificationTrigger(
                     dateMatching: dateComponents, repeats: true)
            
            
            // Create the request
            let uuidString = UUID().uuidString
            let request = UNNotificationRequest(identifier: uuidString,
                        content: content, trigger: trigger)

            // Schedule the request with the system.
            let notificationCenter = UNUserNotificationCenter.current()
            notificationCenter.add(request) { (error) in
               if error != nil {
                  // Handle any errors.
               }
            }
            
               return
           }
    })
}

Use SKOverlay to recommend your full app to users and enable them to install it from within your app clip.

    @IBAction func downloadFullVersionPressed(_ sender: Any) {
      
      guard let scene = UIApplication.shared.connectedScenes.first as? UIWindowScene else { return }
      let config = SKOverlay.AppClipConfiguration(position: .bottom)
      let overlay = SKOverlay(configuration: config)
      overlay.delegate = self
      overlay.present(in: scene)
      
  }

AppsFlyer SDK integration lets you collect App Clip attribution data.

And configuring AppsFlyer OneLink lets you automatically redirect users with iOS13 or Android to the places defined in your OneLink deep linking links.

The full techical details of AppsFlyer SDK integration in iOS are here.

The steps required to integrate the SDK in an App Clip are detailed in our developer hub.

  • AppsFlyer SDK is imported using Cocoapods. The pods are described in the Podfile
# Uncomment the next line to define a global platform for your project
# platform :ios, '9.0'

target 'Fruit App' do
  # Comment the next line if you don't want to use dynamic frameworks
  use_frameworks!

  pod 'AppsFlyerFramework','6.0.8'
end

target 'Fruit AppClip' do
  # Comment the next line if you don't want to use dynamic frameworks
  use_frameworks!

  pod 'AppsFlyerFramework','6.0.8'
end
  • It is very important you run the project through the Fruit App.xcworkspace file. Else the projec will not be able to compile
  • Get your AppsFlyer Dev Key using these instructions.
  1. Create the file afdevkey_donotpush.plist with the following content:
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
        <key>appsFlyerDevKey</key>
        <string>YOUR_AF_DEV_KEY_HERE</string>
        <key>appleAppID</key>
        <string>YOUR_APPLE_APP_ID_HERE</string>
</dict>
</plist>
  • Add the file into your Xcode project

The file Fruit App.xcodeproj/project.pbxproj` will have some changes. Do not commit them!

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Packages

No packages published