From 39f83635980171cb12770c5b1a0d002a36410b55 Mon Sep 17 00:00:00 2001 From: Dave Mead Date: Sun, 29 Sep 2024 23:29:26 -0700 Subject: [PATCH 01/27] Consolidate configurations --- .../configure-deep-link-settings.mdx | 90 +++++++++++++++++ .../v4/features/deep-links/data-points.mdx | 98 ------------------- .../ios/v4/features/deep-links/deep-link.mdx | 55 ----------- .../configure-deep-link-settings.mdx | 90 +++++++++++++++++ .../v5/features/deep-links/data-points.mdx | 98 ------------------- .../ios/v5/features/deep-links/deep-link.mdx | 55 ----------- 6 files changed, 180 insertions(+), 306 deletions(-) create mode 100644 src/content/docs/sdk/ios/v4/features/deep-links/configure-deep-link-settings.mdx delete mode 100644 src/content/docs/sdk/ios/v4/features/deep-links/data-points.mdx delete mode 100644 src/content/docs/sdk/ios/v4/features/deep-links/deep-link.mdx create mode 100644 src/content/docs/sdk/ios/v5/features/deep-links/configure-deep-link-settings.mdx delete mode 100644 src/content/docs/sdk/ios/v5/features/deep-links/data-points.mdx delete mode 100644 src/content/docs/sdk/ios/v5/features/deep-links/deep-link.mdx diff --git a/src/content/docs/sdk/ios/v4/features/deep-links/configure-deep-link-settings.mdx b/src/content/docs/sdk/ios/v4/features/deep-links/configure-deep-link-settings.mdx new file mode 100644 index 000000000..6b1ce23d2 --- /dev/null +++ b/src/content/docs/sdk/ios/v4/features/deep-links/configure-deep-link-settings.mdx @@ -0,0 +1,90 @@ +--- +title: Configure deep link settings +description: Configure deep link settings for your app +slug: en/sdk/ios/v4/features/deep-links/configure-deep-link-settings +sidebar-position: 1 +versions: + - label: v5 + value: v5 + default: true + - label: v4 + value: v4 +redirects: + v5: /en/sdk/ios/features/deep-links/configure-deep-link-settings +--- + +
+## Set up a branded domain +
+In the Adjust dashboard, [set up a branded +domain](https://help.adjust.com/en/article/set-up-branded-domain) using Adjust's +go.link domain (for example: `example.go.link`). + +Copy your branded domain to configure it in Xcode in the section below. + +
+## Configure settings in Xcode +
+### Configure universal links + +1. Open your Xcode project/workspace (use the .xcworkspace file if you are using Cocoapods, otherwise use the .xcodeproj file). +2. In the navigator pane, select the project name to access the project settings. +3. In the project settings, under **Targets**, select the appropriate target (usually your app's name). +4. Select the **Signing & Capabilities** tab. +5. Complete these steps for both the **Release** and **Debug** sub-tabs: + + - Note the value in the **Bundle Identifier** field. This is your app's Bundle ID ("Release Bundle ID" or "Debug Bundle ID," respectively), which you'll need to add to the Adjust dashboard later. + - In the **Associated Domains** section, add an entry for your branded domain. Here is an example using the branded domain `example.go.link`: + + `applinks:example.go.link` + +**Troubleshoot missing or problematic Associated Domains settings** + +- If you don't see the **Associated Domains** section you might need to enable it first: + 1. Next to the **Release** or **Debug** sub-tab (wherever it's missing), click **+ Capability**. + 2. Search for "Associated Domains" and select it. +- If you get an error message, such as "Provisioning profile \{profile_name\} doesn't support the Associated Domains capability" when trying to enable Associated Domains, your provisioning profile likely needs to be updated: + - For automatic signing, make sure "Automatically manage signing" is selected at the top of the Signing & Capabilities page. + - For manual signing: + 1. Go to the Apple Developer portal and [enable the Associated Domains capability](https://developer.apple.com/help/account/manage-identifiers/enable-app-capabilities) for your app. + 2. [Download and import](https://help.apple.com/xcode/mac/current/#/dev1bf96f17e) the updated provisioning profile into Xcode. + +### Configure app scheme + +App scheme is required for certain use cases where iOS doesn’t support universal links. You can reuse an existing app scheme for Adjust deep linking. + +1. In Xcode, select the **Info** tab. +2. Expand the **URL Types** section. + +If your app already has an app scheme, determine which of the below configurations you have and retrieve it: + +| **URL Schemes** field | **Identifier** field | App Scheme | +| --------------------------------------------------------------------- | ------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------- | +| Static value, such as `example` | Static value, such as `com.example.app` | `example://` is the "Release App Scheme" | +| Static value, such as `example` | Build setting variable, such as `$(PRODUCT_BUNDLE_IDENTIFIER)` | `example://` is the "Release App Scheme" | +| Two static values, such as `example` and `exampleDebug`, respectively | Static values, such as `com.example.app` and `com.example.debug`, respectively | `example://` is the "Release App Scheme" and `exampleDebug://` is the "Debug App Scheme" | +| Build setting variable, such as `$(APP_SCHEME)` | Static values, such as `com.example.app` and `com.example.debug`, respectively | **Build Settings** tab -> search for "APP_SCHEME" -> values for Release and Debug are the "Release App Scheme" and "Debug App Scheme," respectively | +| Build setting variable, such as `$(APP_SCHEME)` | Build setting variable, such as `$(PRODUCT_BUNDLE_IDENTIFIER)` | **Build Settings** tab -> search for "APP_SCHEME" -> values for Release and Debug are the "Release App Scheme" and "Debug App Scheme," respectively | + +If you need to set up an app scheme, follow these steps: - Select the **+** button to add a new URL Type. - Fill in the following fields: - **Identifier**: `$(PRODUCT_BUNDLE_IDENTIFIER)` - **Role**: Editor - **URL Schemes**: Enter your desired app scheme (for example: enter `example` to represent `example://`). Don't use `http`, `https`, or reserved iOS schemes like `mailto`, `tel`, `sms`, or `facetime`. Entering a static value here will create a single app scheme used for both release and debug builds. - Once created, remember to note the app scheme for configuration in the Adjust dashboard later. + +
+## Retrieve App ID Prefix from Apple Developer Portal + +1. Log into the [Apple Developer portal](https://developer.apple.com/account/). +2. Under **Certificates, IDs & Profiles**, select **Identifiers**. +3. Select your app. +4. Near the top of the page, copy the **App ID Prefix** to configure in the Adjust dashboard below. +
+ +## Configure settings in Adjust dashboard + +In the Adjust dashboard, [create an app](https://help.adjust.com/en/article/app-setup) if you haven't already done so. Then, configure its [iOS platform settings](https://help.adjust.com/en/article/platforms-ios-android-amazon-microsoft) using the previously collected data points, detailed below. Note that the Adjust dashboard only supports one bundle ID and one app scheme per app. If you need to test with a Debug Bundle ID or Debug App Scheme, create a separate test app. + +| Data Point | Example | Requirement | +| ------------------ | ----------------- | ----------------------------------------------------------------------------------------------------- | +| Release Bundle ID | com.example.app | Required. | +| Debug Bundle ID | com.example.debug | Required if Debug Bundle ID is different than Release Bundle ID, and you are testing a debug build. | +| Release App Scheme | example:// | Required for use cases where iOS doesn't support universal links. | +| Debug App Scheme | exampleDebug:// | Required if Debug App Scheme is different than Release App Scheme, and you are testing a debug build. | +| App ID Prefix | ABCDE12345 | Required. | diff --git a/src/content/docs/sdk/ios/v4/features/deep-links/data-points.mdx b/src/content/docs/sdk/ios/v4/features/deep-links/data-points.mdx deleted file mode 100644 index d7c8b804a..000000000 --- a/src/content/docs/sdk/ios/v4/features/deep-links/data-points.mdx +++ /dev/null @@ -1,98 +0,0 @@ ---- -title: Retrieve data points -description: Retrieve the data required to set up deep links. -slug: en/sdk/ios/v4/features/deep-links/data-points -sidebar-position: 1 -versions: - - label: v5 - value: v5 - default: true - - label: v4 - value: v4 -redirects: - v5: /en/sdk/ios/features/deep-links/data-points ---- - -You need to retrieve the following data points before you can set up deep links in your app: - -- App ID Prefix -- Release Bundle ID -- Debug Bundle ID -- Release Custom URL Scheme -- Debug Custom URL Scheme -- Link resolution domain or domains - -## Instructions {#instructions} - -Follow these instructions to retrieve your data points. - -### App ID Prefix and Release Bundle ID {#app-id-prefix-and-release-bundle-id} - -Your App ID is found on the Apple Developer portal. It contains two parts: - -1. The **App ID prefix** -2. The **Bundle ID** - -The ID is formatted as `.`. For example: `ABC1234567.com.example.app` - -To find your App ID Prefix and Bundle ID, follow these steps: - -1. Log in to the [Apple Developer portal](https://developer.apple.com/account/). -2. Select **Certificates, IDs & Profiles** from the left-hand menu. -3. Select **Identifiers** from the left-hand menu. -4. Find your app and select it to open the edit page. -5. Your App ID Prefix and Bundle ID are displayed at the top of the page. Copy the relevant information and store it somewhere for later use. - -### Debug Bundle ID {#debug-bundle-id} - -If you're using a different bundle ID for your debug build, you can find its ID in Xcode. - -1. Open your app project in Xcode. -2. Select your project from the left-hand menu. -3. Select your app under **Targets**. -4. Select **Signing & Capabilities** from the top menu. -5. Select **Debug** from the sub menu that appears. -6. Your Bundle ID is shown. Copy this information and store it somewhere for later use. - -### Custom URL schemes {#custom-url-schemes} - - - -A custom URL scheme is required for linking from other applications on the device, such as Telegram, X (formerly Twitter), and YouTube, or from push notifications. Check with your marketing team to see if a custom URL scheme is needed for the app. It's highly recommend to use the same custom URL scheme for iOS and Android. - - - -To retrieve your Custom URL Scheme, follow these steps: - -1. Open your app project in Xcode. -2. Select your project from the left-hand menu. -3. Select your app under **Targets**. -4. Select **Info** from the top menu. -5. Expand the **URL Types** section and get the custom URL scheme. If the URL Schemes field contains a build setting (for example: `$(CUSTOM_URL_SCHEME)`), go to the build settings to retrieve the custom URL scheme values: - 1. Select **Build Settings** from the menu at the top. - 2. Find the setting named in the URL Schemes field and retrieve both the release and debug values. - -If your iOS app doesn't have a custom URL scheme yet, follow these steps to set a custom URL scheme: - -1. Open your app project in Xcode. -2. Select your project from the left-hand menu. -3. Select your app under **Targets**. -4. Select **Info** from the top menu. -5. Expand the **URL Types** section. -6. Select the Add option to add a new URL type. -7. Fill in the following information to create a URL scheme: - - **Identifier**: `$(PRODUCT_BUNDLE_IDENTIFIER)` - - **URL Schemes**: your custom URL scheme. This must be unique. Don't use protected schemes such as `http`, `https`, or `mailto` - - **Role**: Editor - -This scheme will work for your production **and** debug builds. - -### Link Resolution domains {#link-resolution-domains} - - - -A link resolution domain is required for deep linking via email, SMS, QR codes, and platforms that shorten links. Check with your marketing team to see if [link resolution](https://help.adjust.com/en/article/link-resolution) is needed for the app. - - - -Your marketing team may already be using a link resolution domain for their email marketing platform. Get this domain from them and store it somewhere for later use. diff --git a/src/content/docs/sdk/ios/v4/features/deep-links/deep-link.mdx b/src/content/docs/sdk/ios/v4/features/deep-links/deep-link.mdx deleted file mode 100644 index 671b4261d..000000000 --- a/src/content/docs/sdk/ios/v4/features/deep-links/deep-link.mdx +++ /dev/null @@ -1,55 +0,0 @@ ---- -title: Enable deep links in Adjust -description: Configure your app in Adjust to enable deep linking. -slug: en/sdk/ios/v4/features/deep-links/deep-link -sidebar-position: 2 -versions: - - label: v5 - value: v5 - default: true - - label: v4 - value: v4 -redirects: - v5: /en/sdk/ios/features/deep-links/deep-link ---- - -You need to configure your app in Adjust to enable deep linking. To do this, make sure you have done the following: - -- [ ] [Added your app in Adjust](https://help.adjust.com/en/article/app-setup). -- [ ] Retrieved all the required deep linking data. - -## Set up universal links in your app {#set-up-universal-links-in-your-app} - -To enable deep linking support for iOS 9 and later you need to set up universal links in Adjust. - - - -You can enter only one Bundle ID per app. If you are testing an app with a Debug Bundle ID, you need to create a separate app. - - - -Once you have gathered your setup data, you can add this to your app in Adjust. Adding the information to your app enables you to add deep links to your campaigns. To set up universal links, follow these steps: - -1. Go to **AppView** and select your app. -2. Ensure the **iOS bundle ID** is present. -3. Under **Device type**, choose your app's default device: - - Universal - iPhone and iPad - - iPhone - - iPad -4. Under **Universal linking**, turn on Enable universal linking to enable universal links for your app. - - Enter the **App ID prefix**. - - Enter the **App scheme**. -5. (Optional) Turn on **Redirect all clicks to a custom URL** and enter a **Custom URL**, if you want your users to go to a custom website instead of the App Store. This is recommended if your app doesn't have an App ID. -6. (Optional) Turn on **Send data to App Store Connect** and enter the **Apple provider ID** to send data to App Store Connect App Analytics. -7. Select **Save**. - -## Set up deep links with a custom URL scheme {#set-up-deep-links-with-a-custom-url-scheme} - -In this case, you need to pick a custom URL scheme name which your app will be responsible for opening. You can then use this scheme name in the Adjust link as part of the deep link parameter. - -To create a deep link with a custom URL scheme, follow these steps: - -1. Define the format of your custom URL scheme. If you are using a cross-platform framework, refer to the documentation for that framework to define the format of your custom URL scheme. Example: `example://summer-clothes?promo=beach` -2. URL encode the deep link. Example: `example%3A%2F%2Fsummer-clothes%3Fpromo%3Dbeach` -3. Pass this encoded deep link into an Adjust link. Example: `https://app.adjust.com/abc123?deeplink=%3A%2F%2Fsummer-clothes%3Fpromo%3Dbeach` -4. Append the `deeplink_js=1` parameter to the link with the encoded deep link. This forces the Adjust system to use the iOS custom URL scheme. Example: `https://app.adjust.com/abc123?deeplink_js=1&deeplink=%3A%2F%2Fsummer-clothes%3Fpromo%3Dbeach` diff --git a/src/content/docs/sdk/ios/v5/features/deep-links/configure-deep-link-settings.mdx b/src/content/docs/sdk/ios/v5/features/deep-links/configure-deep-link-settings.mdx new file mode 100644 index 000000000..a28681510 --- /dev/null +++ b/src/content/docs/sdk/ios/v5/features/deep-links/configure-deep-link-settings.mdx @@ -0,0 +1,90 @@ +--- +title: Configure deep link settings +description: Configure deep link settings for your app +slug: en/sdk/ios/features/deep-links/configure-deep-link-settings +sidebar-position: 1 +versions: + - label: v5 + value: v5 + default: true + - label: v4 + value: v4 +redirects: + v4: /en/sdk/ios/v4/features/deep-links/configure-deep-link-settings +--- + +
+## Set up a branded domain +
+In the Adjust dashboard, [set up a branded +domain](https://help.adjust.com/en/article/set-up-branded-domain) using Adjust's +go.link domain (for example: `example.go.link`). + +Copy your branded domain to configure it in Xcode in the section below. + +
+## Configure settings in Xcode +
+### Configure universal links + +1. Open your Xcode project/workspace (use the .xcworkspace file if you are using Cocoapods, otherwise use the .xcodeproj file). +2. In the navigator pane, select the project name to access the project settings. +3. In the project settings, under **Targets**, select the appropriate target (usually your app's name). +4. Select the **Signing & Capabilities** tab. +5. Complete these steps for both the **Release** and **Debug** sub-tabs: + + - Note the value in the **Bundle Identifier** field. This is your app's Bundle ID ("Release Bundle ID" or "Debug Bundle ID," respectively), which you'll need to add to the Adjust dashboard later. + - In the **Associated Domains** section, add an entry for your branded domain. Here is an example using the branded domain `example.go.link`: + + `applinks:example.go.link` + +**Troubleshoot missing or problematic Associated Domains settings** + +- If you don't see the **Associated Domains** section you might need to enable it first: + 1. Next to the **Release** or **Debug** sub-tab (wherever it's missing), click **+ Capability**. + 2. Search for "Associated Domains" and select it. +- If you get an error message, such as "Provisioning profile \{profile_name\} doesn't support the Associated Domains capability" when trying to enable Associated Domains, your provisioning profile likely needs to be updated: + - For automatic signing, make sure "Automatically manage signing" is selected at the top of the Signing & Capabilities page. + - For manual signing: + 1. Go to the Apple Developer portal and [enable the Associated Domains capability](https://developer.apple.com/help/account/manage-identifiers/enable-app-capabilities) for your app. + 2. [Download and import](https://help.apple.com/xcode/mac/current/#/dev1bf96f17e) the updated provisioning profile into Xcode. + +### Configure app scheme + +App scheme is required for certain use cases where iOS doesn’t support universal links. You can reuse an existing app scheme for Adjust deep linking. + +1. In Xcode, select the **Info** tab. +2. Expand the **URL Types** section. + +If your app already has an app scheme, determine which of the below configurations you have and retrieve it: + +| **URL Schemes** field | **Identifier** field | App Scheme | +| --------------------------------------------------------------------- | ------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------- | +| Static value, such as `example` | Static value, such as `com.example.app` | `example://` is the "Release App Scheme" | +| Static value, such as `example` | Build setting variable, such as `$(PRODUCT_BUNDLE_IDENTIFIER)` | `example://` is the "Release App Scheme" | +| Two static values, such as `example` and `exampleDebug`, respectively | Static values, such as `com.example.app` and `com.example.debug`, respectively | `example://` is the "Release App Scheme" and `exampleDebug://` is the "Debug App Scheme" | +| Build setting variable, such as `$(APP_SCHEME)` | Static values, such as `com.example.app` and `com.example.debug`, respectively | **Build Settings** tab -> search for "APP_SCHEME" -> values for Release and Debug are the "Release App Scheme" and "Debug App Scheme," respectively | +| Build setting variable, such as `$(APP_SCHEME)` | Build setting variable, such as `$(PRODUCT_BUNDLE_IDENTIFIER)` | **Build Settings** tab -> search for "APP_SCHEME" -> values for Release and Debug are the "Release App Scheme" and "Debug App Scheme," respectively | + +If you need to set up an app scheme, follow these steps: - Select the **+** button to add a new URL Type. - Fill in the following fields: - **Identifier**: `$(PRODUCT_BUNDLE_IDENTIFIER)` - **Role**: Editor - **URL Schemes**: Enter your desired app scheme (for example: enter `example` to represent `example://`). Don't use `http`, `https`, or reserved iOS schemes like `mailto`, `tel`, `sms`, or `facetime`. Entering a static value here will create a single app scheme used for both release and debug builds. - Once created, remember to note the app scheme for configuration in the Adjust dashboard later. + +
+## Retrieve App ID Prefix from Apple Developer Portal + +1. Log into the [Apple Developer portal](https://developer.apple.com/account/). +2. Under **Certificates, IDs & Profiles**, select **Identifiers**. +3. Select your app. +4. Near the top of the page, copy the **App ID Prefix** to configure in the Adjust dashboard below. +
+ +## Configure settings in Adjust dashboard + +In the Adjust dashboard, [create an app](https://help.adjust.com/en/article/app-setup) if you haven't already done so. Then, configure its [iOS platform settings](https://help.adjust.com/en/article/platforms-ios-android-amazon-microsoft) using the previously collected data points, detailed below. Note that the Adjust dashboard only supports one bundle ID and one app scheme per app. If you need to test with a Debug Bundle ID or Debug App Scheme, create a separate test app. + +| Data Point | Example | Requirement | +| ------------------ | ----------------- | ----------------------------------------------------------------------------------------------------- | +| Release Bundle ID | com.example.app | Required. | +| Debug Bundle ID | com.example.debug | Required if Debug Bundle ID is different than Release Bundle ID, and you are testing a debug build. | +| Release App Scheme | example:// | Required for use cases where iOS doesn't support universal links. | +| Debug App Scheme | exampleDebug:// | Required if Debug App Scheme is different than Release App Scheme, and you are testing a debug build. | +| App ID Prefix | ABCDE12345 | Required. | diff --git a/src/content/docs/sdk/ios/v5/features/deep-links/data-points.mdx b/src/content/docs/sdk/ios/v5/features/deep-links/data-points.mdx deleted file mode 100644 index 252bd3d63..000000000 --- a/src/content/docs/sdk/ios/v5/features/deep-links/data-points.mdx +++ /dev/null @@ -1,98 +0,0 @@ ---- -title: Retrieve data points -description: Retrieve the data required to set up deep links. -slug: en/sdk/ios/features/deep-links/data-points -sidebar-position: 1 -versions: - - label: v5 - value: v5 - default: true - - label: v4 - value: v4 -redirects: - v4: /en/sdk/ios/v4/features/deep-links/data-points ---- - -You need to retrieve the following data points before you can set up deep links in your app: - -- App ID Prefix -- Release Bundle ID -- Debug Bundle ID -- Release Custom URL Scheme -- Debug Custom URL Scheme -- Link resolution domain or domains - -## Instructions {#instructions} - -Follow these instructions to retrieve your data points. - -### App ID Prefix and Release Bundle ID {#app-id-prefix-and-release-bundle-id} - -Your App ID is found on the Apple Developer portal. It contains two parts: - -1. The **App ID prefix** -2. The **Bundle ID** - -The ID is formatted as `.`. For example: `ABC1234567.com.example.app` - -To find your App ID Prefix and Bundle ID, follow these steps: - -1. Log in to the [Apple Developer portal](https://developer.apple.com/account/). -2. Select **Certificates, IDs & Profiles** from the left-hand menu. -3. Select **Identifiers** from the left-hand menu. -4. Find your app and select it to open the edit page. -5. Your App ID Prefix and Bundle ID are displayed at the top of the page. Copy the relevant information and store it somewhere for later use. - -### Debug Bundle ID {#debug-bundle-id} - -If you're using a different bundle ID for your debug build, you can find its ID in Xcode. - -1. Open your app project in Xcode. -2. Select your project from the left-hand menu. -3. Select your app under **Targets**. -4. Select **Signing & Capabilities** from the top menu. -5. Select **Debug** from the sub menu that appears. -6. Your Bundle ID is shown. Copy this information and store it somewhere for later use. - -### Custom URL schemes {#custom-url-schemes} - - - -A custom URL scheme is required for linking from other applications on the device, such as Telegram, X (formerly Twitter), and YouTube, or from push notifications. Check with your marketing team to see if a custom URL scheme is needed for the app. It's highly recommend to use the same custom URL scheme for iOS and Android. - - - -To retrieve your Custom URL Scheme, follow these steps: - -1. Open your app project in Xcode. -2. Select your project from the left-hand menu. -3. Select your app under **Targets**. -4. Select **Info** from the top menu. -5. Expand the **URL Types** section and get the custom URL scheme. If the URL Schemes field contains a build setting (for example: `$(CUSTOM_URL_SCHEME)`), go to the build settings to retrieve the custom URL scheme values: - 1. Select **Build Settings** from the menu at the top. - 2. Find the setting named in the URL Schemes field and retrieve both the release and debug values. - -If your iOS app doesn't have a custom URL scheme yet, follow these steps to set a custom URL scheme: - -1. Open your app project in Xcode. -2. Select your project from the left-hand menu. -3. Select your app under **Targets**. -4. Select **Info** from the top menu. -5. Expand the **URL Types** section. -6. Select the Add option to add a new URL type. -7. Fill in the following information to create a URL scheme: - - **Identifier**: `$(PRODUCT_BUNDLE_IDENTIFIER)` - - **URL Schemes**: your custom URL scheme. This must be unique. Don't use protected schemes such as `http`, `https`, or `mailto` - - **Role**: Editor - -This scheme will work for your production **and** debug builds. - -### Link Resolution domains {#link-resolution-domains} - - - -A link resolution domain is required for deep linking via email, SMS, QR codes, and platforms that shorten links. Check with your marketing team to see if [link resolution](https://help.adjust.com/en/article/link-resolution) is needed for the app. - - - -Your marketing team may already be using a link resolution domain for their email marketing platform. Get this domain from them and store it somewhere for later use. diff --git a/src/content/docs/sdk/ios/v5/features/deep-links/deep-link.mdx b/src/content/docs/sdk/ios/v5/features/deep-links/deep-link.mdx deleted file mode 100644 index 6ac3dc79d..000000000 --- a/src/content/docs/sdk/ios/v5/features/deep-links/deep-link.mdx +++ /dev/null @@ -1,55 +0,0 @@ ---- -title: Enable deep links in Adjust -description: Configure your app in Adjust to enable deep linking. -slug: en/sdk/ios/features/deep-links/deep-link -sidebar-position: 2 -versions: - - label: v5 - value: v5 - default: true - - label: v4 - value: v4 -redirects: - v4: /en/sdk/ios/v4/features/deep-links/deep-link ---- - -You need to configure your app in Adjust to enable deep linking. To do this, make sure you have done the following: - -- [ ] [Added your app in Adjust](https://help.adjust.com/en/article/app-setup). -- [ ] Retrieved all the required deep linking data. - -## Set up universal links in your app {#set-up-universal-links-in-your-app} - -To enable deep linking support for iOS 9 and later you need to set up universal links in Adjust. - - - -You can enter only one Bundle ID per app. If you are testing an app with a Debug Bundle ID, you need to create a separate app. - - - -Once you have gathered your setup data, you can add this to your app in Adjust. Adding the information to your app enables you to add deep links to your campaigns. To set up universal links, follow these steps: - -1. Go to **AppView** and select your app. -2. Ensure the **iOS bundle ID** is present. -3. Under **Device type**, choose your app's default device: - - Universal - iPhone and iPad - - iPhone - - iPad -4. Under **Universal linking**, turn on Enable universal linking to enable universal links for your app. - - Enter the **App ID prefix**. - - Enter the **App scheme**. -5. (Optional) Turn on **Redirect all clicks to a custom URL** and enter a **Custom URL**, if you want your users to go to a custom website instead of the App Store. This is recommended if your app doesn't have an App ID. -6. (Optional) Turn on **Send data to App Store Connect** and enter the **Apple provider ID** to send data to App Store Connect App Analytics. -7. Select **Save**. - -## Set up deep links with a custom URL scheme {#set-up-deep-links-with-a-custom-url-scheme} - -In this case, you need to pick a custom URL scheme name which your app will be responsible for opening. You can then use this scheme name in the Adjust link as part of the deep link parameter. - -To create a deep link with a custom URL scheme, follow these steps: - -1. Define the format of your custom URL scheme. If you are using a cross-platform framework, refer to the documentation for that framework to define the format of your custom URL scheme. Example: `example://summer-clothes?promo=beach` -2. URL encode the deep link. Example: `example%3A%2F%2Fsummer-clothes%3Fpromo%3Dbeach` -3. Pass this encoded deep link into an Adjust link. Example: `https://app.adjust.com/abc123?deeplink=%3A%2F%2Fsummer-clothes%3Fpromo%3Dbeach` -4. Append the `deeplink_js=1` parameter to the link with the encoded deep link. This forces the Adjust system to use the iOS custom URL scheme. Example: `https://app.adjust.com/abc123?deeplink_js=1&deeplink=%3A%2F%2Fsummer-clothes%3Fpromo%3Dbeach` From 47f7a7c5642d67ab035f6926c6b307c83e41742f Mon Sep 17 00:00:00 2001 From: Dave Mead Date: Sat, 5 Oct 2024 23:42:32 -0700 Subject: [PATCH 02/27] Add new deep link setup --- .../deep-links/set-up-deep-linking.mdx | 1427 +++++++++++++++++ 1 file changed, 1427 insertions(+) create mode 100644 src/content/docs/sdk/ios/v4/features/deep-links/set-up-deep-linking.mdx diff --git a/src/content/docs/sdk/ios/v4/features/deep-links/set-up-deep-linking.mdx b/src/content/docs/sdk/ios/v4/features/deep-links/set-up-deep-linking.mdx new file mode 100644 index 000000000..12b92a2ab --- /dev/null +++ b/src/content/docs/sdk/ios/v4/features/deep-links/set-up-deep-linking.mdx @@ -0,0 +1,1427 @@ +--- +title: Set up deep linking +description: Set up deep linking in your app. +slug: en/sdk/ios/v4/features/deep-links/set-up-deep-linking +sidebar-position: 2 +versions: + - label: v5 + value: v5 + default: true + - label: v4 + value: v4 +redirects: + v5: /en/sdk/ios/features/deep-links/set-up-deep-linking +--- + +Please follow the steps in the section that corresponds to the type of app you have: + +- [UIKit apps using AppDelegate lifecycle](#uikit-apps-using-appdelegate-lifecycle) +- [UIKit apps using SceneDelegate lifecycle](#uikit-apps-using-scenedelegate-lifecycle) +- [SwiftUI apps not using SceneDelegate lifecycle](#swiftui-apps-not-using-scenedelegate-lifecycle) +- [SwiftUI apps using SceneDelegate Lifecycle](#swiftui-apps-using-scenedelegate-lifecycle) + +
+ In addition, implement deep link handling, as described in the below section: + +- [Deep link handling](#deep-link-handling) + + + +If you're using the Facebook SDK for deferred deep linking, disable or remove [this feature](https://developers.facebook.com/docs/ios/deep-linking#deferred-deep-linking) to avoid conflicts with Adjust SDK's deferred deep link handling. + + + +## UIKit apps using AppDelegate lifecycle + +Update your AppDelegate to initialize the Adjust SDK, and to implement direct and deferred deep linking. + + + + +```swift +// AppDelegate.swift + +import Adjust +import UserNotifications +import UIKit + +@UIApplicationMain +class AppDelegate: UIResponder, UIApplicationDelegate, + UNUserNotificationCenterDelegate, AdjustDelegate { + + var window: UIWindow? + + func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: + [UIApplication.LaunchOptionsKey: Any]?) -> Bool { + // Configure Adjust SDK + // Replace {YourAppToken} with your Adjust app token + let appToken = "{YourAppToken}" + var adjustConfig: ADJConfig? + + #if DEBUG + adjustConfig = ADJConfig( + appToken: appToken, + environment: ADJEnvironmentSandbox) + adjustConfig?.logLevel = ADJLogLevelVerbose + #else + adjustConfig = ADJConfig( + appToken: appToken, + environment: ADJEnvironmentProduction) + #endif + + // Wait up to X seconds after app open for user to respond to ATT + // before sending install session to Adjust's servers. + // Ensure this interval is long enough for user to respond. + adjustConfig?.attConsentWaitingInterval = 30 + + // Create delegate for deferred deep linking + adjustConfig?.delegate = self + + // Initialize Adjust SDK + Adjust.appDidLaunch(adjustConfig) + + // Initialize window and root view controller + window = UIWindow(frame: UIScreen.main.bounds) + let rootViewController = ViewController() + let navigationController = UINavigationController(rootViewController: rootViewController) + window?.rootViewController = navigationController + window?.makeKeyAndVisible() + + // Set the delegate for UNUserNotificationCenter + UNUserNotificationCenter.current().delegate = self + + return true + } + + // Receive direct universal link while app isn't running, + // or in background. + func application( + _ application: UIApplication, + continue userActivity: NSUserActivity, + restorationHandler: + @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool { + if userActivity.activityType == NSUserActivityTypeBrowsingWeb, + let incomingLink = userActivity.webpageURL { + let navigationController = self.window?.rootViewController as? UINavigationController { + // Handle incoming universal link + DeeplinkHandler.handleDeeplink( + incomingLink, + navigationController: navigationController) + } + } + return true + } + + // Receive direct app scheme deep link while app isn't running, + // in background, or in foreground. + func application( + _ application: UIApplication, + open incomingLink: URL, + options: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool { + let navigationController = self.window?.rootViewController as? UINavigationController { + // Handle incoming app scheme deep link + DeeplinkHandler.handleDeeplink( + incomingLink, + navigationController: navigationController) + } + return true + } + + // Optionally show push notification while app is in foreground + func userNotificationCenter( + _ center: UNUserNotificationCenter, + willPresent notification: UNNotification) async -> + UNNotificationPresentationOptions { + // Add push notification handling here, if applicable + + // Change to your preferred presentation option + return [.banner, .sound, .badge] + } + + // Receive direct universal link or app scheme deep link + // from push notification while app isn't running, + // in background, or in foreground and user interacts + // with push notification. + // Replace "{deepLink}" with your custom key name + // in push payload that contains the deeplink. + func userNotificationCenter( + _ center: UNUserNotificationCenter, + didReceive response: UNNotificationResponse) async { + let userInfo = response.notification.request.content.userInfo + + if let deepLinkURLString = userInfo["{deepLink}"] as? String, + let incomingLink = URL(string: deepLinkURLString) { + + let navigationController = self.window?.rootViewController as? UINavigationController { + // Handle incoming deep link + DeeplinkHandler.handleDeeplink( + incomingLink, + navigationController: navigationController) + } + } + } + + // Receive deferred deep link via AdjustDelegate method + func adjustDeeplinkResponse(_ deeplink: URL?) -> Bool { + if let incomingLink = deeplink { + // Store incoming deferred deep link to invoke after + // onboarding screens and login. + UserDefaults.standard.set( + incomingLink.absoluteString, + forKey: "lastDeferredDeeplink") + } + + // Return true to try to open deep link immediately upon receipt. + // Otherwise, return false. + return false + } +} + +// ... +``` + + + + +```objc +// AppDelegate.m + +// ... + +- (BOOL)application:(UIApplication *)application + didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { + // Configure Adjust SDK + // Replace {YourAppToken} with your Adjust app token + NSString *appToken = @"{YourAppToken}"; + ADJConfig *adjustConfig; + +#ifdef DEBUG + adjustConfig = [ADJConfig configWithAppToken:appToken + environment:ADJEnvironmentSandbox]; + [adjustConfig setLogLevel:ADJLogLevelVerbose]; +#else + adjustConfig = [ADJConfig configWithAppToken:appToken + environment:ADJEnvironmentProduction]; + [adjustConfig setLogLevel:ADJLogLevelSuppress]; + adjustConfig.allowSuppressLogLevel = YES; +#endif + + // Wait up to X seconds after app open for user to respond to ATT + // before sending install session to Adjust's servers. + // Ensure this interval is long enough for user to respond. + adjustConfig.attConsentWaitingInterval = 30; + + // Create delegate for deferred deep linking + adjustConfig.delegate = self; + + // Initialize Adjust SDK + [Adjust appDidLaunch:adjustConfig]; + + return YES; +} + +// Receive direct universal link while app isn't running, +// or in background. +- (BOOL)application:(UIApplication *)application + continueUserActivity:(NSUserActivity *)userActivity + restorationHandler: + (void (^)(NSArray> *_Nullable)) + restorationHandler { + if ([userActivity.activityType + isEqualToString:NSUserActivityTypeBrowsingWeb]) { + NSURL *incomingLink = userActivity.webpageURL; + // Handle incoming universal link + [DeeplinkHandler handleDeeplink:incomingLink + navigationController:navigationController]; + } + return YES; +} + +// Receive direct app scheme deep link while app isn't running, +// in background, or in foreground. +- (BOOL)application:(UIApplication *)app + openURL:(NSURL *)incomingLink + options:(NSDictionary *) + options { + // Handle incoming app scheme deep link + [DeeplinkHandler handleDeeplink:incomingLink + navigationController:navigationController]; + + return YES; +} + +// Optionally show push notification while app is in foreground +- (void)userNotificationCenter:(UNUserNotificationCenter *)center + willPresentNotification:(UNNotification *)notification + withCompletionHandler: + (void (^)(UNNotificationPresentationOptions options)) + completionHandler { + + // Add push notification handling here, if applicable + + // Change to your preferred presentation option + completionHandler(UNNotificationPresentationOptionBanner | + UNNotificationPresentationOptionSound | + UNNotificationPresentationOptionBadge); +} + +// Receive direct universal link or app scheme deep link +// from push notification while app isn't running, +// in background, or in foreground and user interacts +// with push notification. +// Replace "{deepLink}" with your custom key name +// in push payload that contains the deeplink. +- (void)userNotificationCenter:(UNUserNotificationCenter *)center + didReceiveNotificationResponse:(UNNotificationResponse *)response + withCompletionHandler:(void (^)(void))completionHandler { + NSDictionary *userInfo = response.notification.request.content.userInfo; + + NSString *deepLinkURLString = userInfo[@"{deepLink}"]; + if (deepLinkURLString) { + NSURL *incomingLink = [NSURL URLWithString:deepLinkURLString]; + // Handle incoming deep link + [DeeplinkHandler handleDeeplink:incomingLink + navigationController:navigationController]; + } + + // Add other push notification handling here, if applicable + + completionHandler(); +} + +// Receive deferred deep link via AdjustDelegate method +- (BOOL)adjustDeeplinkResponse:(NSURL *)incomingLink { + if (incomingLink) { + // Store incoming deferred deep link to invoke after + // onboarding screens and login. + [[NSUserDefaults standardUserDefaults] + setObject:incomingLink.absoluteString + forKey:@"DeferredDeeplinkURL"]; + } + + // Return true to try to open deep link immediately upon receipt. + // Otherwise, return false. + return NO; +} + +// ... +``` + + + + +On first app open, if your app displays onboarding screens and/or prompts the user to log in, afterward your app has to retrieve and handle the stored deferred deep link (for example: in `ViewController` as shown below). If your app doesn't display onboarding screens or prompt the user to log in, and you want the Adjust SDK to open the deferred deep link immediately on first app open, then you can skip the deferred deep linking logic shown below. + +The below code also shows the ATT popup during the onboarding process. After the user responds to the ATT popup (or the ATT waiting interval expires, whichever comes first), the SDK sends the /session and /attribution requests to Adjust's servers. Adjust's servers then respond with the deferred deep link. This roundtrip takes about 2.5 - 3.0 seconds total. + + + + +```swift +// ViewController.swift + +import AppTrackingTransparency +import UIKit + +class ViewController: UIViewController { + + var hasCompletedOnboarding: Bool { + get { + UserDefaults.standard.bool(forKey: "HasCompletedOnboarding") + } + set { + UserDefaults.standard.set(newValue, forKey: "HasCompletedOnboarding") + } + } + + override func viewDidAppear() { + super.viewDidAppear() + + // Check if onboarding has been completed + if !hasCompletedOnboarding { + // Show ATT dialog + ATTrackingManager.requestTrackingAuthorization { _ in } + + // Show onboarding screens and user login + + // On completion, set hasCompletedOnboarding to true + hasCompletedOnboarding = true + } + + // Check if there's a stored deferred deep link + if let deferredLinkString = UserDefaults.standard.string( + forKey: "lastDeferredLink"), + let deferredLink = URL(string: deferredLinkString) { + // Remove the stored URL to avoid handling it again later + UserDefaults.standard.removeObject(forKey: "lastDeferredLink") + + // Handle deferred deep link + DeeplinkHandler.handleDeeplink(deferredLink, navigationController: self.navigationController) + + } else { + // Show main content + } + } +} + +// ... +``` + + + + +```objc +// ViewController.m + +// ... + +@implementation ViewController + +- (void)viewDidLoad { + [super viewDidLoad]; + + // Check if onboarding has been completed + if (!self.hasCompletedOnboarding) { + // Show ATT popup + [ATTrackingManager + requestTrackingAuthorizationWithCompletionHandler:nil]; + + // Show onboarding screens and user login + + // On completion, set hasCompletedOnboarding to true + self.hasCompletedOnboarding = YES; + } + + // Check if there's a stored deferred deep link + NSString *deferredLinkString = [[NSUserDefaults standardUserDefaults] + stringForKey:@"DeferredDeeplinkURL"]; + NSURL *deferredLink = [NSURL URLWithString:deferredLinkString]; + if (deferredLink) { + // Handle deferred deep link + [[DeeplinkHandler shared] handleDeeplink:deferredLink]; + + // Remove the stored URL to avoid handling it again later + [[NSUserDefaults standardUserDefaults] + removeObjectForKey:@"DeferredDeeplinkURL"]; + } else { + // Show main content + } +} + +// ... + +``` + + + + +## UIKit apps using SceneDelegate lifecycle + +Update your AppDelegate to initialize the Adjust SDK, and to implement direct deep linking for push notifications and deferred deep linking. + + + + +```swift +// AppDelegate.swift + +// ... + +@UIApplicationMain +class AppDelegate: UIResponder, UIApplicationDelegate, + UNUserNotificationCenterDelegate, AdjustDelegate { + + func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: + [UIApplication.LaunchOptionsKey: Any]?) -> Bool { + + // Configure Adjust SDK + // Replace {YourAppToken} with your Adjust app token + let appToken = "{YourAppToken}" + var adjustConfig: ADJConfig? + + #if DEBUG + adjustConfig = ADJConfig( + appToken: appToken, + environment: ADJEnvironmentSandbox) + adjustConfig?.logLevel = ADJLogLevelVerbose + #else + adjustConfig = ADJConfig( + appToken: appToken, + environment: ADJEnvironmentProduction, + allowSuppressLogLevel: true) + adjustConfig?.logLevel = ADJLogLevelSuppress + #endif + + // Wait up to X seconds after app open for user to respond to ATT + // before sending install session to Adjust backend. + // Ensure this interval is long enough for user to respond. + adjustConfig.attConsentWaitingInterval = 30 + + // Create delegate for deferred deep linking + adjustConfig.delegate = self + + // Initialize Adjust SDK + Adjust.appDidLaunch(adjustConfig) + + return true + } + + // Receive push notification while app is in foreground + func userNotificationCenter( + _ center: UNUserNotificationCenter, + willPresent notification: UNNotification) async -> + UNNotificationPresentationOptions { + + // Add your push notification handling here + + // Change to your preferred presentation option + return [.banner, .sound, .badge] + } + + // Receive direct universal link or app scheme deep link + // from push notification while app isn't running, + // in background, or in foreground and user interacts + // with push notification. + // Replace "{deepLink}" with your custom key name. + func userNotificationCenter( + _ center: UNUserNotificationCenter, + didReceive response: UNNotificationResponse) async { + let userInfo = response.notification.request.content + .userInfo + + // Add your push notification handling here + + if let deepLinkURLString = userInfo["{deepLink}"] + as? String, + let incomingLink = URL( + string: deepLinkURLString) { + // Handle incoming deep link + DeeplinkHandler.shared.handleDeeplink( + incomingLink) + } + } + + // Receive deferred deep link via AdjustDelegate method + func adjustDeeplinkResponse(_ deeplink: URL?) -> Bool { + if let incomingLink = deeplink { + // Store incoming deferred deep link to invoke after + // onboarding screens and login. + UserDefaults.standard.set( + incomingLink.absoluteString, + forKey: "DeferredDeeplinkURL") + } + + // Return true to try to open deep link immediately upon receipt. + // Otherwise, return false. + return false + } +} + +// ... +``` + + + + +```objc +// AppDelegate.m + +// ... + +@implementation AppDelegate + +- (BOOL)application:(UIApplication *)application + didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { + + // Configure Adjust SDK + // Replace {YourAppToken} with your Adjust app token + NSString *appToken = @"{YourAppToken}"; + ADJConfig *adjustConfig; + +#ifdef DEBUG + adjustConfig = [ADJConfig configWithAppToken:appToken + environment:ADJEnvironmentSandbox]; + adjustConfig.logLevel = ADJLogLevelVerbose; +#else + adjustConfig = [ADJConfig configWithAppToken:appToken + environment:ADJEnvironmentProduction + allowSuppressLogLevel:YES]; + adjustConfig.logLevel = ADJLogLevelSuppress; +#endif + + // Wait up to X seconds after app open for user to respond to ATT + // before sending install session to Adjust backend. + // Ensure this interval is long enough for user to respond. + adjustConfig.attConsentWaitingInterval = 30; + + // Create delegate for deferred deep linking + adjustConfig.delegate = self; + + // Initialize Adjust SDK + [Adjust appDidLaunch:adjustConfig]; + + return YES; +} + +// Receive push notification while app is in foreground +- (void)userNotificationCenter:(UNUserNotificationCenter *)center + willPresentNotification:(UNNotification *)notification + withCompletionHandler: + (void (^)(UNNotificationPresentationOptions))completionHandler { + // Add your push notification handling here + + // Change to your preferred presentation option + completionHandler(UNNotificationPresentationOptionBanner | + UNNotificationPresentationOptionSound | + UNNotificationPresentationOptionBadge); +} + +// Receive direct universal link or app scheme deep link +// from push notification while app isn't running, +// in background, or in foreground and user interacts +// with push notification. +// Replace "{deepLink}" with your custom key name. +- (void)userNotificationCenter:(UNUserNotificationCenter *)center + didReceiveNotificationResponse:(UNNotificationResponse *)response + withCompletionHandler:(void (^)(void))completionHandler { + NSDictionary *userInfo = response.notification.request.content.userInfo; + + // Add your push notification handling here + + NSString *deepLinkURLString = userInfo[@"{deepLink}"]; + if (deepLinkURLString) { + NSURL *incomingLink = [NSURL URLWithString:deepLinkURLString]; + // Handle incoming deep link + [DeeplinkHandler.shared handleDeeplink:incomingLink]; + } + completionHandler(); +} + +// Receive deferred deep link via AdjustDelegate method +- (BOOL)adjustDeeplinkResponse:(NSURL *)deeplink { + if (deeplink) { + // Store incoming deferred deep link to invoke after + // onboarding screens and login. + [[NSUserDefaults standardUserDefaults] + setObject:[deeplink absoluteString] + forKey:@"DeferredDeeplinkURL"]; + } + + // Return true to try to open deep link immediately upon receipt. + // Otherwise, return false. + return NO; +} + +// ... +``` + + + + +Update your SceneDelegate to implement direct deep linking. + + + + +```swift +// SceneDelegate.swift + +// ... + +class SceneDelegate: UIResponder, UIWindowSceneDelegate { + + var window: UIWindow? + + // Receive direct universal link or app scheme deep link + // while app isn't running, or when new scene is created. + func scene( + _ scene: UIScene, + willConnectTo session: UISceneSession, + options connectionOptions: UIScene.ConnectionOptions) { + // Initialize and make the window visible + guard let windowScene = (scene as? UIWindowScene) else { return } + window = UIWindow(windowScene: windowScene) + let rootViewController = ViewController() + let navigationController = UINavigationController(rootViewController: rootViewController) + window?.rootViewController = navigationController + window?.makeKeyAndVisible() + + if let urlContext = connectionOptions.urlContexts.first { + let incomingLink = urlContext.url + + // Handle incoming deep link + DeeplinkHandler.handleDeeplink( + incomingLink, + navigationController: navigationController) + } + } + + // Receive direct universal link in existing scene + // while app is in background. + func scene(_ scene: UIScene, continue userActivity: NSUserActivity) { + if userActivity.activityType == NSUserActivityTypeBrowsingWeb, + let incomingLink = userActivity.webpageURL { + // Handle incoming universal link + DeeplinkHandler.handleDeeplink( + incomingLink, + navigationController: navigationController) + } + } + + // Receive direct app scheme deep link in existing scene + // while app is in background or in foreground. + func scene( + _ scene: UIScene, openURLContexts URLContexts: Set) { + if let urlContext = URLContexts.first { + let incomingLink = urlContext.url + + // Handle incoming app scheme deep link + DeeplinkHandler.handleDeeplink( + incomingLink, + navigationController: navigationController) + } + } +} + +// ... +``` + + + + +```objc +// SceneDelegate.m + +// ... + +@implementation SceneDelegate + +// Receive direct universal link or app scheme deep link +// while app isn't running, or when new scene is created. +- (void)scene:(UIScene *)scene + willConnectToSession:(UISceneSession *)session + options:(UISceneConnectionOptions *)connectionOptions { + if (connectionOptions.URLContexts.count > 0) { + UIOpenURLContext *urlContext = + connectionOptions.URLContexts.allObjects.firstObject; + NSURL *incomingLink = urlContext.URL; + + // Handle incoming deep link + [DeeplinkHandler handleDeeplink:incomingLink + navigationController:navigationController]; + } +} + +// Receive direct universal link in existing scene +// while app is in background. +- (void)scene:(UIScene *)scene + continueUserActivity:(NSUserActivity *)userActivity { + if ([userActivity.activityType + isEqualToString:NSUserActivityTypeBrowsingWeb]) { + NSURL *incomingLink = userActivity.webpageURL; + + // Handle incoming universal link + [DeeplinkHandler handleDeeplink:incomingLink + navigationController:navigationController]; + } +} + +// Receive direct app scheme deep link in existing scene +// while app is in background or in foreground. +- (void)scene:(UIScene *)scene + openURLContexts:(NSSet *)URLContexts { + UIOpenURLContext *urlContext = URLContexts.allObjects.firstObject; + if (urlContext) { + NSURL *incomingLink = urlContext.URL; + + // Handle incoming app scheme deep link + [DeeplinkHandler handleDeeplink:incomingLink + navigationController:navigationController]; + } +} + +// ... +``` + + + + +"ViewController" + +## SwiftUI apps not using SceneDelegate lifecycle + +Create an `AppDelegate.swift` file in your project's main directory and reference it in your main application file (for example: `App.swift` as shown below). This is required to handle app lifecycle events and Adjust SDK integration. + + + + +```swift +// App.swift + +import SwiftUI + +@main +struct MyApp: App { + @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate + + var body: some Scene { + WindowGroup { + ContentView() + .environmentObject(DeeplinkManager.shared) + } + } +} +``` + + + + +Update your AppDelegate to initialize the Adjust SDK, and to implement direct deep linking for push notifications and deferred deep linking. + + + + +```swift +// AppDelegate.swift + +import Adjust +import UIKit + +@UIApplicationMain +class AppDelegate: UIResponder, UIApplicationDelegate, + UNUserNotificationCenterDelegate, AdjustDelegate { + + let deeplinkManager = DeeplinkManager.shared + + func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication + .LaunchOptionsKey: Any]?) -> Bool { + // Set delegate for push notifications + UNUserNotificationCenter.current().delegate = self + + // Configure Adjust SDK + // Replace {YourAppToken} with your Adjust app token + let appToken = "{YourAppToken}" + let adjustConfig: ADJConfig? + + #if DEBUG + adjustConfig = ADJConfig( + appToken: appToken, + environment: ADJEnvironmentSandbox) + adjustConfig?.logLevel = ADJLogLevelVerbose + #else + adjustConfig = ADJConfig( + appToken: appToken, + environment: ADJEnvironmentProduction, + allowSuppressLogLevel: true) + adjustConfig?.logLevel = ADJLogLevelSuppress + #endif + + // Wait up to 30 seconds after app open for user to respond to ATT + // before sending install session to Adjust backend. + adjustConfig.attConsentWaitingInterval = 30 + + // Create delegate for deferred deep linking + adjustConfig.delegate = self + + // Initialize Adjust SDK + Adjust.appDidLaunch(adjustConfig) + + // Initialize and make window visible + let contentView = ContentView().environmentObject(deeplinkManager) + if let windowScene = UIApplication.shared.connectedScenes.first + as? UIWindowScene { + let window = UIWindow(windowScene: windowScene) + window.rootViewController = UIHostingController( + rootView: contentView) + self.window = window + window.makeKeyAndVisible() + } + + return true + } + + // Receive push notification while app is in foreground + func userNotificationCenter( + _ center: UNUserNotificationCenter, + willPresent notification: UNNotification) async -> + UNNotificationPresentationOptions { + + // Add your push notification handling here + + // Change to your preferred presentation option + return [.alert, .sound] + } + + // Receive direct universal link or app scheme deep link + // from push notification while app isn't running, + // in background, or in foreground and user interacts + // with push notification. + // Replace "{deepLink}" with your custom key name. + func userNotificationCenter( + _ center: UNUserNotificationCenter, + didReceive response: UNNotificationResponse) async { + let userInfo = response.notification.request.content + .userInfo + + // Add your push notification handling here + + if let deepLinkURLString = userInfo["{deepLink}"] + as? String, + let incomingLink = URL( + string: deepLinkURLString) { + // Update DeeplinkManager with incoming deep link + self.deeplinkManager.updateDeeplinkState(with: incomingLink) + } + } + } + + // Receive direct universal link while app isn't running, + // or in background. + func application( + _ application: UIApplication, + continue userActivity: NSUserActivity, + restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool { + if userActivity.activityType == NSUserActivityTypeBrowsingWeb, + let incomingLink = userActivity.webpageURL { + // Update DeeplinkManager with incoming universal link + self.deeplinkManager.updateDeeplinkState(with: incomingLink) + } + } + return true + } + + // Receive direct app scheme deep link while app isn't running, + // or in background. + func application( + _ application: UIApplication, + open incomingLink: URL, + options: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool { + + // Update DeeplinkManager with incoming app scheme deep link + self.deeplinkManager.updateDeeplinkState(with: incomingLink) + + return true + } + + // Receive deferred deep link via AdjustDelegate method + func adjustDeeplinkResponse(_ deeplink: URL?) -> Bool { + if let incomingLink = deeplink { + // Store incoming deferred deep link to invoke after + // onboarding screens and login. + UserDefaults.standard.set( + incomingLink.absoluteString, + forKey: "DeferredDeeplinkURL") + } + + // Return true to try to open deep link immediately upon receipt. + // Otherwise, return false. + return false + } +} +``` + + + + +Update your SceneDelegate to implement direct linking. + + + + +```swift +// SceneDelegate.swift + +import SwiftUI +import UIKit + +class SceneDelegate: UIResponder, UIWindowSceneDelegate, AdjustDelegate { + + var window: UIWindow? + let deeplinkManager = DeeplinkManager.shared + + // Receive direct universal link or app scheme deep link + // while app isn't running, or when new scene is created + func scene(_ scene: UIScene, + willConnectTo session: UISceneSession, + options connectionOptions: UIScene.ConnectionOptions) { + // Initialize and make window visible + let contentView = ContentView().environmentObject(deeplinkManager) + if let windowScene = scene as? UIWindowScene { + let window = UIWindow(windowScene: windowScene) + window.rootViewController = UIHostingController(rootView: contentView) + self.window = window + window.makeKeyAndVisible() + } + + if let urlContext = connectionOptions.urlContexts.first { + let incomingLink = urlContext.url + + // Update DeeplinkManager with incoming deep link + deeplinkManager.updateDeeplinkState(with: incomingLink) + } + } + + // Receive direct universal links while app is in background + func scene(_ scene: UIScene, continue userActivity: NSUserActivity) { + if userActivity.activityType == NSUserActivityTypeBrowsingWeb, + let incomingLink = userActivity.webpageURL { + // Update DeeplinkManager with incoming universal link + deeplinkManager.updateDeeplinkState(with: incomingLink) + } + } + + // Receive direct app scheme deep link in existing scene + // while app is in background. + func scene(_ scene: UIScene, openURLContexts URLContexts: Set) { + if let urlContext = URLContexts.first { + let incomingLink = urlContext.url + + // Update DeeplinkManager with incoming app scheme deep link + deeplinkManager.updateDeeplinkState(with: incomingLink) + } + } +} +``` + + + + +When your AppDelegate receives a direct deep link, your SwiftUI views have to observe the updated deep link state. To facilitate this, you can create a centralized class to manage deep link states in your app, such as the below `DeeplinkManager.swift` implementation that the above example invokes. + + + + +```swift +// DeeplinkManager.swift + +import SwiftUI + +class DeeplinkManager: ObservableObject { + static let shared = DeeplinkManager() + @Published var deeplink: URL? + + // Update current deep link state + func updateDeeplinkState(with url: URL) { + DispatchQueue.main.async { + self.deeplink = url + } + } + + // Remove current deep link + func RemoveDeeplink() { + DispatchQueue.main.async { + self.deeplink = nil + } + } +} +``` + + + + +Your SwiftUI views have to react to changes in the direct deep link state and open the deep link content. Below is an example `ContentView.swift` implementation that does this. + +On first app open, if your app displays onboarding screens and/or prompts the user to log in, afterward your app has to retrieve and handle the stored deferred deep link (for example: in `ContentView.swift` as shown below). If your app doesn't display onboarding screens or prompt the user to log in, and you want the Adjust SDK to open the deferred deep link immediately on first app open, then you can skip the deferred deep linking logic shown below. + +The below code also shows the ATT popup during the onboarding process. After the user responds to the ATT popup (or the ATT waiting interval expires, whichever comes first), the SDK sends the /session and /attribution requests to Adjust's servers. Adjust's servers then respond with the deferred deep link. This roundtrip takes about 2.5 - 3.0 seconds in total. + + + + +```swift +// ContentView.swift + +import AppTrackingTransparency +import SwiftUI + +struct ContentView: View { + @EnvironmentObject var deeplinkManager: DeeplinkManager + @State private var hasCompletedOnboarding: Bool = false + + var body: some View { + NavigationView { + // Check if onboarding has been completed + if !hasCompletedOnboarding { + // Show ATT popup + ATTrackingManager.requestTrackingAuthorization { _ in } + + // Show onboarding screens and user login here + + // On completion, set hasCompletedOnboarding to true + hasCompletedOnboarding = true + } + + // Check if there's a stored deferred deep link + if let deferredLinkString = UserDefaults.standard.string( + forKey: "DeferredDeeplinkURL"), + let deferredLink = URL(string: deferredLinkString) { + + // Handle deferred deep link + DeeplinkHandler.shared.handleDeeplink(deferredLink) + + // Remove stored URL to avoid handling it again later + UserDefaults.standard.removeObject( + forKey: "DeferredDeeplinkURL") + } else { + // Show main content + } + } + + // Receive direct universal link or app scheme deep link + // from deeplinkManager. + .onReceive(deeplinkManager.$deeplink) { deeplink in + if let incomingLink = deeplink { + // Send incoming deep link to Adjust for attribution, + // and resolve short link, if applicable. + Adjust.processDeeplink(incomingLink) { resolvedLinkString in + guard + let resolvedLink = URL(string: resolvedLinkString ?? "") + else { + print("Invalid resolved link") + return + } + + // Handle resolved deep link + DeeplinkHandler.shared.handleDeeplink(resolvedLink) + } + + // Remove deep link to avoid handling it again later + deeplinkManager.RemoveDeeplink() + } + } + + // Receive direct universal link or app scheme deep link + // while app is in foreground. + .onOpenURL { incomingLink in + Adjust.processDeeplink(incomingLink) { resolvedLinkString in + guard let resolvedLink = URL(string: resolvedLinkString ?? "") + else { + print("Invalid resolved link") + return + } + + // Handle resolved deep link + DeeplinkHandler.shared.handleDeeplink(resolvedLink) + } + } + } +} +``` + + + + +## SwiftUI apps using SceneDelegate lifecycle + +"App" + +Update your AppDelegate to initialize the Adjust SDK, and to implement direct deep linking for push notifications and deferred deep linking. + + + + +```swift +// AppDelegate.swift + +import Adjust +import UIKit + +@UIApplicationMain +class AppDelegate: UIResponder, UIApplicationDelegate, + UNUserNotificationCenterDelegate, AdjustDelegate { + + let deeplinkManager = DeeplinkManager.shared + + func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: + [UIApplication.LaunchOptionsKey: Any]?) -> Bool { + // Set delegate for push notifications + UNUserNotificationCenter.current().delegate = self + + // Configure Adjust SDK + // Replace {YourAppToken} with your Adjust app token + let appToken = "{YourAppToken}" + let adjustConfig: ADJConfig? + + #if DEBUG + adjustConfig = ADJConfig( + appToken: appToken, + environment: ADJEnvironmentSandbox) + adjustConfig?.logLevel = ADJLogLevelVerbose + #else + adjustConfig = ADJConfig( + appToken: appToken, + environment: ADJEnvironmentProduction, + allowSuppressLogLevel: true) + adjustConfig?.logLevel = ADJLogLevelSuppress + #endif + + // Wait up to 30 seconds after app open for user to respond to ATT + // before sending install session to Adjust backend. + adjustConfig.attConsentWaitingInterval = 30 + + // Create delegate for deferred deep linking + adjustConfig.delegate = self + + // Initialize Adjust SDK + Adjust.appDidLaunch(adjustConfig) + + return true + } + + // Receive push notification while app is in foreground + func userNotificationCenter( + _ center: UNUserNotificationCenter, + willPresent notification: UNNotification) async -> + UNNotificationPresentationOptions { + + // Add your push notification handling here + + // Change to your preferred presentation option + return [.alert, .sound] + } + + // Receive direct universal link or app scheme deep link + // from push notification while app isn't running, + // in background, or in foreground and user interacts + // with push notification. + // Replace "{deepLink}" with your custom key name. + func userNotificationCenter( + _ center: UNUserNotificationCenter, + didReceive response: UNNotificationResponse) async { + let userInfo = response.notification.request.content + .userInfo + + // Add your push notification handling here + + if let deepLinkURLString = userInfo["{deepLink}"] + as? String, + let incomingLink = URL( + string: deepLinkURLString) { + // Update DeeplinkManager with incoming deep link + self.deeplinkManager.updateDeeplinkState( + with: incomingLink) + } + } + + // Receive deferred deep link via AdjustDelegate method + func adjustDeeplinkResponse(_ deeplink: URL?) -> Bool { + if let incomingLink = deeplink { + // Store incoming deferred deep link to invoke after + // onboarding screens and login. + UserDefaults.standard.set( + incomingLink.absoluteString, + forKey: "DeferredDeeplinkURL") + } + + // Return true to try to open deep link immediately upon receipt. + // Otherwise, return false. + return false + } +} +``` + + + + +Update your SceneDelegate to implement direct linking. + + + + +```swift +// SceneDelegate.swift + +import SwiftUI +import UIKit + +class SceneDelegate: UIResponder, UIWindowSceneDelegate, AdjustDelegate { + + var window: UIWindow? + let deeplinkManager = DeeplinkManager.shared + + // Receive direct universal link or app scheme deep link + // while app isn't running, or when new scene is created + func scene(_ scene: UIScene, + willConnectTo session: UISceneSession, + options connectionOptions: UIScene.ConnectionOptions) { + // Initialize and make window visible + let contentView = ContentView().environmentObject(deeplinkManager) + if let windowScene = scene as? UIWindowScene { + let window = UIWindow(windowScene: windowScene) + window.rootViewController = UIHostingController(rootView: contentView) + self.window = window + window.makeKeyAndVisible() + } + + if let urlContext = connectionOptions.urlContexts.first { + let incomingLink = urlContext.url + + // Update DeeplinkManager with incoming deep link + deeplinkManager.updateDeeplinkState(with: incomingLink) + } + } + + // Receive direct universal links while app is in background + func scene(_ scene: UIScene, continue userActivity: NSUserActivity) { + if userActivity.activityType == NSUserActivityTypeBrowsingWeb, + let incomingLink = userActivity.webpageURL { + // Update DeeplinkManager with incoming universal link + deeplinkManager.updateDeeplinkState(with: incomingLink) + } + } + + // Receive direct app scheme deep link in existing scene + // while app is in background. + func scene(_ scene: UIScene, openURLContexts URLContexts: Set) { + if let urlContext = URLContexts.first { + let incomingLink = urlContext.url + + // Update DeeplinkManager with incoming app scheme deep link + deeplinkManager.updateDeeplinkState(with: incomingLink) + } + } +} +``` + + + + +"Deeplink Manager" + +"Content View" + +## Deep link handling + +Your app has to implement its own logic for handling deep links and opening the corresponding content. The specific navigation logic depends on your app's structure and isn't part of the Adjust SDK implementation. + +The preceding implementations use the example DeeplinkHandler class below that performs the following tasks: + +1. The class uses Adjust's `processDeeplink` method, which accomplishes two things: +
+ - It passes the deep link to Adjust's servers for attribution. - It resolves + short links into full URLs, if applicable. +
+2. After processing, the class parses the link and navigates to the appropriate screen. + +
+ Your deep link handling has to meet the following key requirements: + +3. Your app should treat Adjust universal links (for example: `example.go.link`) the same as other universal links (for example: `example.com`). Adjust recommends implementing domain-agnostic deep link handling logic to meet this requirement. +4. In cases where iOS doesn't support universal links, Adjust automatically converts them to app scheme deep links. Therefore it's crucial for the app to handle universal links and app scheme deep links equivalently. For example, the following links should navigate to the same destination in your app: +
- Universal link: `https://example.go.link/summer-clothes?promo=beach` + - App scheme deep link: `example://summer-clothes?promo=beach` + + + + +```swift +// DeeplinkHandler.swift + +import Adjust +import Foundation +import UIKit + +class DeeplinkHandler { + + func handleDeeplink( + _ incomingDeeplink: URL, + navigationController: UINavigationController?) { + // Send incoming deep link to Adjust for attribution, + // and resolve short link, if applicable. + Adjust.processDeeplink(incomingDeeplink) { resolvedLinkString in + guard let resolvedLink = URL(string: resolvedLinkString) else { + print("Invalid resolved link") + return + } + + // Extract path, query items, and fragment from the + // resolved link. + let components = URLComponents( + url: resolvedLink, + resolvingAgainstBaseURL: true) + let path = components?.path ?? "" + let queryItems = components?.queryItems + let fragment = components?.fragment + + // Implement the navigation or other app-specific + // logic based on the deep link components. + // Example: Navigate to a specific view controller within the + // navigationController. + DispatchQueue.main.async { + // Example of navigating based on the path or + // other components. This is a placeholder. + // Replace with your actual navigation logic. + if path == "/somePath" { + let viewController = SomeViewController() + navigationController?.pushViewController( + viewController, animated: true) + } + // Handle other paths, query items, or fragments + // as needed. + } + } + } +} +``` + + + + +```objc +// DeeplinkHandler.m + +// ... + +@implementation DeeplinkHandler + ++ (void)handleDeeplink:(NSURL *)incomingDeeplink + navigationController:(UINavigationController *)navigationController { + // Send incoming deep link to Adjust for attribution, and resolve + // short link, if applicable. + [Adjust processDeeplink:incomingDeeplink + completion:^(NSString * _Nullable resolvedLinkString) { + NSURL *resolvedLink = [NSURL URLWithString:resolvedLinkString]; + if (!resolvedLink) { + NSLog(@"Invalid resolved link"); + return; + } + + // Extract path, query items, and fragment from the resolved link + NSURLComponents *components = + [[NSURLComponents alloc] + initWithURL:resolvedLink + resolvingAgainstBaseURL:YES]; + NSString *path = components.path ?: @""; + NSArray *queryItems = components.queryItems; + NSString *fragment = components.fragment; + + // Implement the navigation or other app-specific logic based on + // the deep link components. Example: Navigate to a specific view + // controller within the navigationController. + dispatch_async(dispatch_get_main_queue(), ^{ + // Example of navigating based on the path or other components. + // This is a placeholder. Replace with your actual navigation logic. + if ([path isEqualToString:@"/somePath"]) { + SomeViewController *viewController = + [[SomeViewController alloc] init]; + [navigationController + pushViewController:viewController animated:YES]; + } + // Handle other paths, query items, or fragments as needed. + }); + }]; +} + +@end +``` + + + From cf78d4b68ef6fc0686c043bc56c122696d80a87b Mon Sep 17 00:00:00 2001 From: Dave Mead Date: Tue, 8 Oct 2024 02:24:14 -0700 Subject: [PATCH 03/27] Rewrite paragraphs --- .../deep-links/set-up-deep-linking.mdx | 413 +++++++----------- 1 file changed, 170 insertions(+), 243 deletions(-) diff --git a/src/content/docs/sdk/ios/v4/features/deep-links/set-up-deep-linking.mdx b/src/content/docs/sdk/ios/v4/features/deep-links/set-up-deep-linking.mdx index 12b92a2ab..36853ef21 100644 --- a/src/content/docs/sdk/ios/v4/features/deep-links/set-up-deep-linking.mdx +++ b/src/content/docs/sdk/ios/v4/features/deep-links/set-up-deep-linking.mdx @@ -20,34 +20,27 @@ Please follow the steps in the section that corresponds to the type of app you h - [SwiftUI apps not using SceneDelegate lifecycle](#swiftui-apps-not-using-scenedelegate-lifecycle) - [SwiftUI apps using SceneDelegate Lifecycle](#swiftui-apps-using-scenedelegate-lifecycle) -
- In addition, implement deep link handling, as described in the below section: - -- [Deep link handling](#deep-link-handling) - -If you're using the Facebook SDK for deferred deep linking, disable or remove [this feature](https://developers.facebook.com/docs/ios/deep-linking#deferred-deep-linking) to avoid conflicts with Adjust SDK's deferred deep link handling. +If you're using the Facebook SDK for deferred deep linking, disable or remove the [deferred deep linking code](https://developers.facebook.com/docs/ios/deep-linking#deferred-deep-linking) to avoid conflicts with Adjust SDK's deferred deep link handling. ## UIKit apps using AppDelegate lifecycle -Update your AppDelegate to initialize the Adjust SDK, and to implement direct and deferred deep linking. +Update your AppDelegate to implement direct and deferred deep linking. -```swift +```swift {38-39,54-101} // AppDelegate.swift - import Adjust -import UserNotifications import UIKit @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate, - UNUserNotificationCenterDelegate, AdjustDelegate { + AdjustDelegate { var window: UIWindow? @@ -68,13 +61,15 @@ class AppDelegate: UIResponder, UIApplicationDelegate, #else adjustConfig = ADJConfig( appToken: appToken, - environment: ADJEnvironmentProduction) + environment: ADJEnvironmentProduction, + allowSuppressLogLevel: true) + adjustConfig?.logLevel = ADJLogLevelSuppress) #endif - // Wait up to X seconds after app open for user to respond to ATT + // Wait up to 120 seconds after app open for user to respond to ATT // before sending install session to Adjust's servers. // Ensure this interval is long enough for user to respond. - adjustConfig?.attConsentWaitingInterval = 30 + adjustConfig?.attConsentWaitingInterval = 120 // Create delegate for deferred deep linking adjustConfig?.delegate = self @@ -82,21 +77,18 @@ class AppDelegate: UIResponder, UIApplicationDelegate, // Initialize Adjust SDK Adjust.appDidLaunch(adjustConfig) - // Initialize window and root view controller + // Example: initialize window and root view controller window = UIWindow(frame: UIScreen.main.bounds) let rootViewController = ViewController() let navigationController = UINavigationController(rootViewController: rootViewController) window?.rootViewController = navigationController window?.makeKeyAndVisible() - // Set the delegate for UNUserNotificationCenter - UNUserNotificationCenter.current().delegate = self - return true } // Receive direct universal link while app isn't running, - // or in background. + // in background, or in foreground. func application( _ application: UIApplication, continue userActivity: NSUserActivity, @@ -129,40 +121,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate, return true } - // Optionally show push notification while app is in foreground - func userNotificationCenter( - _ center: UNUserNotificationCenter, - willPresent notification: UNNotification) async -> - UNNotificationPresentationOptions { - // Add push notification handling here, if applicable - - // Change to your preferred presentation option - return [.banner, .sound, .badge] - } - - // Receive direct universal link or app scheme deep link - // from push notification while app isn't running, - // in background, or in foreground and user interacts - // with push notification. - // Replace "{deepLink}" with your custom key name - // in push payload that contains the deeplink. - func userNotificationCenter( - _ center: UNUserNotificationCenter, - didReceive response: UNNotificationResponse) async { - let userInfo = response.notification.request.content.userInfo - - if let deepLinkURLString = userInfo["{deepLink}"] as? String, - let incomingLink = URL(string: deepLinkURLString) { - - let navigationController = self.window?.rootViewController as? UINavigationController { - // Handle incoming deep link - DeeplinkHandler.handleDeeplink( - incomingLink, - navigationController: navigationController) - } - } - } - // Receive deferred deep link via AdjustDelegate method func adjustDeeplinkResponse(_ deeplink: URL?) -> Bool { if let incomingLink = deeplink { @@ -170,7 +128,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, // onboarding screens and login. UserDefaults.standard.set( incomingLink.absoluteString, - forKey: "lastDeferredDeeplink") + forKey: "lastDeferredLink") } // Return true to try to open deep link immediately upon receipt. @@ -178,8 +136,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate, return false } } - -// ... ``` @@ -208,7 +164,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, adjustConfig.allowSuppressLogLevel = YES; #endif - // Wait up to X seconds after app open for user to respond to ATT + // Wait up to 120 seconds after app open for user to respond to ATT // before sending install session to Adjust's servers. // Ensure this interval is long enough for user to respond. adjustConfig.attConsentWaitingInterval = 30; @@ -223,7 +179,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, } // Receive direct universal link while app isn't running, -// or in background. +// in background, or in foreground. - (BOOL)application:(UIApplication *)application continueUserActivity:(NSUserActivity *)userActivity restorationHandler: @@ -252,45 +208,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate, return YES; } -// Optionally show push notification while app is in foreground -- (void)userNotificationCenter:(UNUserNotificationCenter *)center - willPresentNotification:(UNNotification *)notification - withCompletionHandler: - (void (^)(UNNotificationPresentationOptions options)) - completionHandler { - - // Add push notification handling here, if applicable - - // Change to your preferred presentation option - completionHandler(UNNotificationPresentationOptionBanner | - UNNotificationPresentationOptionSound | - UNNotificationPresentationOptionBadge); -} - -// Receive direct universal link or app scheme deep link -// from push notification while app isn't running, -// in background, or in foreground and user interacts -// with push notification. -// Replace "{deepLink}" with your custom key name -// in push payload that contains the deeplink. -- (void)userNotificationCenter:(UNUserNotificationCenter *)center - didReceiveNotificationResponse:(UNNotificationResponse *)response - withCompletionHandler:(void (^)(void))completionHandler { - NSDictionary *userInfo = response.notification.request.content.userInfo; - - NSString *deepLinkURLString = userInfo[@"{deepLink}"]; - if (deepLinkURLString) { - NSURL *incomingLink = [NSURL URLWithString:deepLinkURLString]; - // Handle incoming deep link - [DeeplinkHandler handleDeeplink:incomingLink - navigationController:navigationController]; - } - - // Add other push notification handling here, if applicable - - completionHandler(); -} - // Receive deferred deep link via AdjustDelegate method - (BOOL)adjustDeeplinkResponse:(NSURL *)incomingLink { if (incomingLink) { @@ -298,7 +215,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, // onboarding screens and login. [[NSUserDefaults standardUserDefaults] setObject:incomingLink.absoluteString - forKey:@"DeferredDeeplinkURL"]; + forKey:@"lastDeferredLink"]; } // Return true to try to open deep link immediately upon receipt. @@ -312,9 +229,15 @@ class AppDelegate: UIResponder, UIApplicationDelegate, -On first app open, if your app displays onboarding screens and/or prompts the user to log in, afterward your app has to retrieve and handle the stored deferred deep link (for example: in `ViewController` as shown below). If your app doesn't display onboarding screens or prompt the user to log in, and you want the Adjust SDK to open the deferred deep link immediately on first app open, then you can skip the deferred deep linking logic shown below. +For deferred deep linking, this is the sequence for most apps: -The below code also shows the ATT popup during the onboarding process. After the user responds to the ATT popup (or the ATT waiting interval expires, whichever comes first), the SDK sends the /session and /attribution requests to Adjust's servers. Adjust's servers then respond with the deferred deep link. This roundtrip takes about 2.5 - 3.0 seconds total. +1. A user who doesn't have the app installed clicks an Adjust deep link, which redirects them to the app store. +2. The user installs and opens the app. +3. The app begins its onboarding process (for example: ATT prompt, onboarding screens, login prompt). +4. After the ATT prompt response or timeout, the Adjust SDK sends session and attribution requests to Adjust's servers. +5. Adjust's servers respond with attribution data, including the deep link the user clicked on ("deferred deep link"). +6. The Adjust SDK triggers a deferred deep link callback in your app, which stores the deferred deep link (shown in the code example above). +7. Once onboarding completes, the app retrieves the stored deep link and navigates the user accordingly (shown in the code example below). @@ -322,7 +245,7 @@ The below code also shows the ATT popup during the onboarding process. After the ```swift // ViewController.swift -import AppTrackingTransparency +import Adjust import UIKit class ViewController: UIViewController { @@ -342,9 +265,9 @@ class ViewController: UIViewController { // Check if onboarding has been completed if !hasCompletedOnboarding { // Show ATT dialog - ATTrackingManager.requestTrackingAuthorization { _ in } + Adjust.requestTrackingAuthorization(completionHandler: nil) - // Show onboarding screens and user login + // Show onboarding screens and login prompt // On completion, set hasCompletedOnboarding to true hasCompletedOnboarding = true @@ -375,7 +298,7 @@ class ViewController: UIViewController { ```objc // ViewController.m -// ... +#import @implementation ViewController @@ -384,8 +307,8 @@ class ViewController: UIViewController { // Check if onboarding has been completed if (!self.hasCompletedOnboarding) { - // Show ATT popup - [ATTrackingManager + // Show ATT prompt + [Adjust requestTrackingAuthorizationWithCompletionHandler:nil]; // Show onboarding screens and user login @@ -396,7 +319,7 @@ class ViewController: UIViewController { // Check if there's a stored deferred deep link NSString *deferredLinkString = [[NSUserDefaults standardUserDefaults] - stringForKey:@"DeferredDeeplinkURL"]; + stringForKey:@"lastDeferredLink"]; NSURL *deferredLink = [NSURL URLWithString:deferredLinkString]; if (deferredLink) { // Handle deferred deep link @@ -404,14 +327,146 @@ class ViewController: UIViewController { // Remove the stored URL to avoid handling it again later [[NSUserDefaults standardUserDefaults] - removeObjectForKey:@"DeferredDeeplinkURL"]; + removeObjectForKey:@"lastDeferredLink"]; } else { // Show main content } } +``` + + + + +The preceding code examples use the example DeeplinkHandler class below. This code example handles all types of links: + +- Adjust branded links (full go.link links) +- Adjust short branded links (short go.link links) +- Adjust universal links (adj.st links) +- Non-Adjust universal links (example.com links) +- App scheme deep links (example:// links) + +The class performs the following tasks: + +1. The class uses Adjust's `processDeeplink` method, which sends the deep link to Adjust's servers to accomplish two things: + +- Record the deep link for attribution. +- Resolve [Adjust short branded links](https://www.help.adjust.com/en/article/short-branded-links) and respond with full URLs, if applicable. + +2. After processing, the class parses the link and navigates to the appropriate screen. This part of the code is specific to each app. Your app has to implement its own logic for handling deep links and opening the corresponding content. Your deep link handling has to meet the following key requirements: + +- Your app should treat Adjust branded links the same as other universal links, such as your own. Adjust recommends implementing domain-agnostic deep link handling logic to meet this requirement. For example, the following links should navigate to the same screen in your app: + + - Adjust branded link: `https://example.go.link/summer-clothes?promo=beach` + - Your universal link: `https://www.example.com/summer-clothes?promo=beach` + +- In cases where iOS doesn't support universal links, Adjust automatically converts them to app scheme deep links. Additionally, Adjust's servers convert all deferred deep links to app scheme deep link format. Therefore it's crucial for the app to handle universal links and app scheme deep links equivalently. For example, the following links should navigate to the same screen in your app: + - Adjust branded link: `https://example.go.link/summer-clothes?promo=beach` + - App scheme deep link: `example://summer-clothes?promo=beach` + + + + +```swift +// DeeplinkHandler.swift + +import Adjust +import Foundation +import UIKit + +class DeeplinkHandler { + + func handleDeeplink( + _ incomingDeeplink: URL, + navigationController: UINavigationController?) { + // Send incoming deep link to Adjust for attribution, + // and resolve short link, if applicable. + Adjust.processDeeplink(incomingDeeplink) { resolvedLinkString in + guard let resolvedLink = URL(string: resolvedLinkString) else { + print("Invalid resolved link") + return + } + + // Extract path, query items, and fragment from the + // resolved link. + let components = URLComponents( + url: resolvedLink, + resolvingAgainstBaseURL: true) + let path = components?.path ?? "" + let queryItems = components?.queryItems + let fragment = components?.fragment + + // Implement the navigation or other app-specific + // logic based on the deep link components. + // Example: Navigate to a specific view controller within the + // navigationController. + DispatchQueue.main.async { + // Example of navigating based on the path or + // other components. This is a placeholder. + // Replace with your actual navigation logic. + // Handle paths, query items, and fragments + // as needed. + if path == "/somePath" { + let viewController = SomeViewController() + navigationController?.pushViewController( + viewController, animated: true) + } + } + } + } +} +``` + + + + +```objc +// DeeplinkHandler.m // ... +@implementation DeeplinkHandler + ++ (void)handleDeeplink:(NSURL *)incomingDeeplink + navigationController:(UINavigationController *)navigationController { + // Send incoming deep link to Adjust for attribution, and resolve + // short link, if applicable. + [Adjust processDeeplink:incomingDeeplink + completion:^(NSString * _Nullable resolvedLinkString) { + NSURL *resolvedLink = [NSURL URLWithString:resolvedLinkString]; + if (!resolvedLink) { + NSLog(@"Invalid resolved link"); + return; + } + + // Extract path, query items, and fragment from the resolved link + NSURLComponents *components = + [[NSURLComponents alloc] + initWithURL:resolvedLink + resolvingAgainstBaseURL:YES]; + NSString *path = components.path ?: @""; + NSArray *queryItems = components.queryItems; + NSString *fragment = components.fragment; + + // Implement the navigation or other app-specific logic based on + // the deep link components. Example: Navigate to a specific view + // controller within the navigationController. + dispatch_async(dispatch_get_main_queue(), ^{ + // Example of navigating based on the path or + // other components. This is a placeholder. + // Replace with your actual navigation logic. + // Handle paths, query items, and fragments + // as needed. + if ([path isEqualToString:@"/somePath"]) { + SomeViewController *viewController = + [[SomeViewController alloc] init]; + [navigationController + pushViewController:viewController animated:YES]; + } + }); + }]; +} + +@end ``` @@ -419,7 +474,7 @@ class ViewController: UIViewController { ## UIKit apps using SceneDelegate lifecycle -Update your AppDelegate to initialize the Adjust SDK, and to implement direct deep linking for push notifications and deferred deep linking. +Update your AppDelegate to implement deferred deep linking. @@ -456,7 +511,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, adjustConfig?.logLevel = ADJLogLevelSuppress #endif - // Wait up to X seconds after app open for user to respond to ATT + // Wait up to 120 seconds after app open for user to respond to ATT // before sending install session to Adjust backend. // Ensure this interval is long enough for user to respond. adjustConfig.attConsentWaitingInterval = 30 @@ -553,7 +608,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, adjustConfig.logLevel = ADJLogLevelSuppress; #endif - // Wait up to X seconds after app open for user to respond to ATT + // Wait up to 120 seconds after app open for user to respond to ATT // before sending install session to Adjust backend. // Ensure this interval is long enough for user to respond. adjustConfig.attConsentWaitingInterval = 30; @@ -780,7 +835,7 @@ struct MyApp: App { -Update your AppDelegate to initialize the Adjust SDK, and to implement direct deep linking for push notifications and deferred deep linking. +Update your AppDelegate to implement direct deep linking and deferred deep linking. @@ -1029,7 +1084,7 @@ Your SwiftUI views have to react to changes in the direct deep link state and op On first app open, if your app displays onboarding screens and/or prompts the user to log in, afterward your app has to retrieve and handle the stored deferred deep link (for example: in `ContentView.swift` as shown below). If your app doesn't display onboarding screens or prompt the user to log in, and you want the Adjust SDK to open the deferred deep link immediately on first app open, then you can skip the deferred deep linking logic shown below. -The below code also shows the ATT popup during the onboarding process. After the user responds to the ATT popup (or the ATT waiting interval expires, whichever comes first), the SDK sends the /session and /attribution requests to Adjust's servers. Adjust's servers then respond with the deferred deep link. This roundtrip takes about 2.5 - 3.0 seconds in total. +The below code also shows the ATT prompt, which may or may not be applicable for your app's onboarding process. After the user responds to the ATT prompt (or the ATT waiting interval expires, whichever comes first), the SDK sends the /session and /attribution requests to Adjust's servers. Adjust's servers then respond with the deferred deep link in the /attribution response. @@ -1048,7 +1103,7 @@ struct ContentView: View { NavigationView { // Check if onboarding has been completed if !hasCompletedOnboarding { - // Show ATT popup + // Show ATT prompt ATTrackingManager.requestTrackingAuthorization { _ in } // Show onboarding screens and user login here @@ -1121,7 +1176,7 @@ struct ContentView: View { "App" -Update your AppDelegate to initialize the Adjust SDK, and to implement direct deep linking for push notifications and deferred deep linking. +Update your AppDelegate to implement deferred deep linking. @@ -1297,131 +1352,3 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate, AdjustDelegate { "Deeplink Manager" "Content View" - -## Deep link handling - -Your app has to implement its own logic for handling deep links and opening the corresponding content. The specific navigation logic depends on your app's structure and isn't part of the Adjust SDK implementation. - -The preceding implementations use the example DeeplinkHandler class below that performs the following tasks: - -1. The class uses Adjust's `processDeeplink` method, which accomplishes two things: -
- - It passes the deep link to Adjust's servers for attribution. - It resolves - short links into full URLs, if applicable. -
-2. After processing, the class parses the link and navigates to the appropriate screen. - -
- Your deep link handling has to meet the following key requirements: - -3. Your app should treat Adjust universal links (for example: `example.go.link`) the same as other universal links (for example: `example.com`). Adjust recommends implementing domain-agnostic deep link handling logic to meet this requirement. -4. In cases where iOS doesn't support universal links, Adjust automatically converts them to app scheme deep links. Therefore it's crucial for the app to handle universal links and app scheme deep links equivalently. For example, the following links should navigate to the same destination in your app: -
- Universal link: `https://example.go.link/summer-clothes?promo=beach` - - App scheme deep link: `example://summer-clothes?promo=beach` - - - - -```swift -// DeeplinkHandler.swift - -import Adjust -import Foundation -import UIKit - -class DeeplinkHandler { - - func handleDeeplink( - _ incomingDeeplink: URL, - navigationController: UINavigationController?) { - // Send incoming deep link to Adjust for attribution, - // and resolve short link, if applicable. - Adjust.processDeeplink(incomingDeeplink) { resolvedLinkString in - guard let resolvedLink = URL(string: resolvedLinkString) else { - print("Invalid resolved link") - return - } - - // Extract path, query items, and fragment from the - // resolved link. - let components = URLComponents( - url: resolvedLink, - resolvingAgainstBaseURL: true) - let path = components?.path ?? "" - let queryItems = components?.queryItems - let fragment = components?.fragment - - // Implement the navigation or other app-specific - // logic based on the deep link components. - // Example: Navigate to a specific view controller within the - // navigationController. - DispatchQueue.main.async { - // Example of navigating based on the path or - // other components. This is a placeholder. - // Replace with your actual navigation logic. - if path == "/somePath" { - let viewController = SomeViewController() - navigationController?.pushViewController( - viewController, animated: true) - } - // Handle other paths, query items, or fragments - // as needed. - } - } - } -} -``` - - - - -```objc -// DeeplinkHandler.m - -// ... - -@implementation DeeplinkHandler - -+ (void)handleDeeplink:(NSURL *)incomingDeeplink - navigationController:(UINavigationController *)navigationController { - // Send incoming deep link to Adjust for attribution, and resolve - // short link, if applicable. - [Adjust processDeeplink:incomingDeeplink - completion:^(NSString * _Nullable resolvedLinkString) { - NSURL *resolvedLink = [NSURL URLWithString:resolvedLinkString]; - if (!resolvedLink) { - NSLog(@"Invalid resolved link"); - return; - } - - // Extract path, query items, and fragment from the resolved link - NSURLComponents *components = - [[NSURLComponents alloc] - initWithURL:resolvedLink - resolvingAgainstBaseURL:YES]; - NSString *path = components.path ?: @""; - NSArray *queryItems = components.queryItems; - NSString *fragment = components.fragment; - - // Implement the navigation or other app-specific logic based on - // the deep link components. Example: Navigate to a specific view - // controller within the navigationController. - dispatch_async(dispatch_get_main_queue(), ^{ - // Example of navigating based on the path or other components. - // This is a placeholder. Replace with your actual navigation logic. - if ([path isEqualToString:@"/somePath"]) { - SomeViewController *viewController = - [[SomeViewController alloc] init]; - [navigationController - pushViewController:viewController animated:YES]; - } - // Handle other paths, query items, or fragments as needed. - }); - }]; -} - -@end -``` - - - From 4038f5e875c3ee4658521b69cecffb18c57ce6fb Mon Sep 17 00:00:00 2001 From: Dave Mead Date: Wed, 9 Oct 2024 23:50:18 -0700 Subject: [PATCH 04/27] Refine implementation --- .../deep-links/set-up-deep-linking.mdx | 1020 ++++++++--------- 1 file changed, 482 insertions(+), 538 deletions(-) diff --git a/src/content/docs/sdk/ios/v4/features/deep-links/set-up-deep-linking.mdx b/src/content/docs/sdk/ios/v4/features/deep-links/set-up-deep-linking.mdx index 36853ef21..73af1350b 100644 --- a/src/content/docs/sdk/ios/v4/features/deep-links/set-up-deep-linking.mdx +++ b/src/content/docs/sdk/ios/v4/features/deep-links/set-up-deep-linking.mdx @@ -18,7 +18,7 @@ Please follow the steps in the section that corresponds to the type of app you h - [UIKit apps using AppDelegate lifecycle](#uikit-apps-using-appdelegate-lifecycle) - [UIKit apps using SceneDelegate lifecycle](#uikit-apps-using-scenedelegate-lifecycle) - [SwiftUI apps not using SceneDelegate lifecycle](#swiftui-apps-not-using-scenedelegate-lifecycle) -- [SwiftUI apps using SceneDelegate Lifecycle](#swiftui-apps-using-scenedelegate-lifecycle) +- [SwiftUI apps using SceneDelegate lifecycle](#swiftui-apps-using-scenedelegate-lifecycle) @@ -33,199 +33,261 @@ Update your AppDelegate to implement direct and deferred deep linking. + + ```swift {38-39,54-101} -// AppDelegate.swift -import Adjust +import Foundation import UIKit +import Adjust @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate, - AdjustDelegate { - - var window: UIWindow? - - func application( - _ application: UIApplication, - didFinishLaunchingWithOptions launchOptions: - [UIApplication.LaunchOptionsKey: Any]?) -> Bool { - // Configure Adjust SDK - // Replace {YourAppToken} with your Adjust app token - let appToken = "{YourAppToken}" - var adjustConfig: ADJConfig? - - #if DEBUG - adjustConfig = ADJConfig( - appToken: appToken, - environment: ADJEnvironmentSandbox) - adjustConfig?.logLevel = ADJLogLevelVerbose - #else - adjustConfig = ADJConfig( - appToken: appToken, - environment: ADJEnvironmentProduction, - allowSuppressLogLevel: true) - adjustConfig?.logLevel = ADJLogLevelSuppress) - #endif - - // Wait up to 120 seconds after app open for user to respond to ATT - // before sending install session to Adjust's servers. - // Ensure this interval is long enough for user to respond. - adjustConfig?.attConsentWaitingInterval = 120 - - // Create delegate for deferred deep linking - adjustConfig?.delegate = self - - // Initialize Adjust SDK - Adjust.appDidLaunch(adjustConfig) - - // Example: initialize window and root view controller - window = UIWindow(frame: UIScreen.main.bounds) - let rootViewController = ViewController() - let navigationController = UINavigationController(rootViewController: rootViewController) - window?.rootViewController = navigationController - window?.makeKeyAndVisible() - - return true - } - - // Receive direct universal link while app isn't running, - // in background, or in foreground. - func application( - _ application: UIApplication, - continue userActivity: NSUserActivity, - restorationHandler: - @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool { - if userActivity.activityType == NSUserActivityTypeBrowsingWeb, - let incomingLink = userActivity.webpageURL { - let navigationController = self.window?.rootViewController as? UINavigationController { - // Handle incoming universal link - DeeplinkHandler.handleDeeplink( - incomingLink, - navigationController: navigationController) - } + AdjustDelegate +{ + + var window: UIWindow? + + func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: + [UIApplication.LaunchOptionsKey: Any]? + ) -> Bool { + // Configure Adjust SDK + // Replace {YourAppToken} with your Adjust app token + let appToken = "{YourAppToken}" + var adjustConfig: ADJConfig? + + #if DEBUG + adjustConfig = ADJConfig( + appToken: appToken, + environment: ADJEnvironmentSandbox) + adjustConfig?.logLevel = ADJLogLevelVerbose + #else + adjustConfig = ADJConfig( + appToken: appToken, + environment: ADJEnvironmentProduction, + allowSuppressLogLevel: true) + adjustConfig?.logLevel = ADJLogLevelSuppress + #endif + + // Wait up to 120 seconds after app open for user to respond to ATT + // before sending install session to Adjust's servers. + // Ensure this interval is long enough for user to respond. + adjustConfig?.attConsentWaitingInterval = 120 + + // Create delegate for deferred deep linking + adjustConfig?.delegate = self + + // Initialize Adjust SDK + Adjust.appDidLaunch(adjustConfig) + + // Example: initialize window and root view controller + window = UIWindow(frame: UIScreen.main.bounds) + let rootViewController = ViewController() + let navigationController = UINavigationController( + rootViewController: rootViewController) + window?.rootViewController = navigationController + window?.makeKeyAndVisible() + + return true } - return true - } - // Receive direct app scheme deep link while app isn't running, - // in background, or in foreground. - func application( - _ application: UIApplication, - open incomingLink: URL, - options: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool { - let navigationController = self.window?.rootViewController as? UINavigationController { - // Handle incoming app scheme deep link - DeeplinkHandler.handleDeeplink( - incomingLink, - navigationController: navigationController) + // Receive universal link when app is installed + // and user clicks link to open app + // from one of these states: + // not running, background, or foreground. + func application( + _ application: UIApplication, + continue userActivity: NSUserActivity, + restorationHandler: + @escaping ([UIUserActivityRestoring]?) -> Void + ) -> Bool { + if userActivity.activityType == NSUserActivityTypeBrowsingWeb, + let incomingLink = userActivity.webpageURL + { + let navigationController = + self.window?.rootViewController as? UINavigationController + { + // Handle incoming universal link + DeeplinkHandler.handleDeeplink( + incomingLink, + navigationController: navigationController) + } + } + return true } - return true - } - // Receive deferred deep link via AdjustDelegate method - func adjustDeeplinkResponse(_ deeplink: URL?) -> Bool { - if let incomingLink = deeplink { - // Store incoming deferred deep link to invoke after - // onboarding screens and login. - UserDefaults.standard.set( - incomingLink.absoluteString, - forKey: "lastDeferredLink") + // Receive app scheme deep link when app is installed + // and user clicks link to open app + // from one of these states: + // not running, background, or foreground. + // + // Also receive universal link or app scheme deep link + // when app programmatically opens link + // using UIApplication.shared.open. + func application( + _ application: UIApplication, + open incomingLink: URL, + options: [UIApplication.OpenURLOptionsKey: Any] = [:] + ) -> Bool { + let navigationController = + self.window?.rootViewController as? UINavigationController + { + // Handle incoming deep link + DeeplinkHandler.handleDeeplink( + incomingLink, + navigationController: navigationController) + } + return true } - // Return true to try to open deep link immediately upon receipt. - // Otherwise, return false. - return false - } + // Receive deferred deep link via AdjustDelegate method + func adjustDeeplinkResponse(_ deeplink: URL?) -> Bool { + if let incomingLink = deeplink { + // Store incoming deferred deep link to invoke after + // onboarding screens and login. + UserDefaults.standard.set( + incomingLink.absoluteString, + forKey: "lastDeferredLink") + } + + // Return true to try to open deep link immediately + // upon receipt (for example: app has no ATT, onboarding screens, or login). + // Otherwise, return false. + return false + } } ``` + + -```objc -// AppDelegate.m + -// ... +```objc {34-35, 53-103} +#import +#import +#import + +@class DeeplinkHandler; +@class ViewController; + +@interface AppDelegate : UIResponder +@property(strong, nonatomic) UIWindow *window; +@end + +@implementation AppDelegate - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { - // Configure Adjust SDK - // Replace {YourAppToken} with your Adjust app token - NSString *appToken = @"{YourAppToken}"; - ADJConfig *adjustConfig; + // Configure Adjust SDK + // Replace {YourAppToken} with your Adjust app token + NSString *appToken = @"{YourAppToken}"; + ADJConfig *adjustConfig; #ifdef DEBUG - adjustConfig = [ADJConfig configWithAppToken:appToken - environment:ADJEnvironmentSandbox]; - [adjustConfig setLogLevel:ADJLogLevelVerbose]; + adjustConfig = [ADJConfig configWithAppToken:appToken + environment:ADJEnvironmentSandbox]; + [adjustConfig setLogLevel:ADJLogLevelVerbose]; #else - adjustConfig = [ADJConfig configWithAppToken:appToken - environment:ADJEnvironmentProduction]; - [adjustConfig setLogLevel:ADJLogLevelSuppress]; - adjustConfig.allowSuppressLogLevel = YES; + adjustConfig = [ADJConfig configWithAppToken:appToken + environment:ADJEnvironmentProduction + allowSuppressLogLevel:YES]; + [adjustConfig setLogLevel:ADJLogLevelSuppress]; #endif - // Wait up to 120 seconds after app open for user to respond to ATT - // before sending install session to Adjust's servers. - // Ensure this interval is long enough for user to respond. - adjustConfig.attConsentWaitingInterval = 30; + // Wait up to 120 seconds after app open for user to respond to ATT + // before sending install session to Adjust's servers. + // Ensure this interval is long enough for user to respond. + adjustConfig.attConsentWaitingInterval = 120; - // Create delegate for deferred deep linking - adjustConfig.delegate = self; + // Create delegate for deferred deep linking + adjustConfig.delegate = self; - // Initialize Adjust SDK - [Adjust appDidLaunch:adjustConfig]; + // Initialize Adjust SDK + [Adjust appDidLaunch:adjustConfig]; - return YES; + // Example: initialize window and root view controller + self.window = + [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; + UIViewController *rootViewController = [[UIViewController alloc] init]; + UINavigationController *navigationController = + [[UINavigationController alloc] + initWithRootViewController:rootViewController]; + self.window.rootViewController = navigationController; + [self.window makeKeyAndVisible]; + + return YES; } -// Receive direct universal link while app isn't running, -// in background, or in foreground. +// Receive universal link when app is installed +// and user clicks link to open app +// from one of these states: +// not running, background, or foreground. - (BOOL)application:(UIApplication *)application continueUserActivity:(NSUserActivity *)userActivity restorationHandler: (void (^)(NSArray> *_Nullable)) restorationHandler { - if ([userActivity.activityType - isEqualToString:NSUserActivityTypeBrowsingWeb]) { - NSURL *incomingLink = userActivity.webpageURL; - // Handle incoming universal link - [DeeplinkHandler handleDeeplink:incomingLink - navigationController:navigationController]; - } - return YES; + if ([userActivity.activityType + isEqualToString:NSUserActivityTypeBrowsingWeb]) { + NSURL *incomingLink = userActivity.webpageURL; + UINavigationController *navigationController = + (UINavigationController *)self.window.rootViewController; + if ([navigationController + isKindOfClass:[UINavigationController class]]) { + // Handle incoming universal link + [DeeplinkHandler handleDeeplink:incomingLink + navigationController:navigationController]; + } + } + return YES; } -// Receive direct app scheme deep link while app isn't running, -// in background, or in foreground. +// Receive app scheme deep link when app is installed +// and user clicks link to open app +// from one of these states: +// not running, background, or foreground. +// +// Also receive universal link or app scheme deep link +// when app programmatically opens link +// using [[UIApplication sharedApplication] openURL:...]. - (BOOL)application:(UIApplication *)app openURL:(NSURL *)incomingLink - options:(NSDictionary *) - options { - // Handle incoming app scheme deep link - [DeeplinkHandler handleDeeplink:incomingLink - navigationController:navigationController]; - - return YES; + options: + (NSDictionary *)options { + UINavigationController *navigationController = + (UINavigationController *)self.window.rootViewController; + if ([navigationController isKindOfClass:[UINavigationController class]]) { + // Handle incoming app scheme deep link + [DeeplinkHandler handleDeeplink:incomingLink + navigationController:navigationController]; + } + return YES; } // Receive deferred deep link via AdjustDelegate method -- (BOOL)adjustDeeplinkResponse:(NSURL *)incomingLink { - if (incomingLink) { - // Store incoming deferred deep link to invoke after - // onboarding screens and login. - [[NSUserDefaults standardUserDefaults] - setObject:incomingLink.absoluteString - forKey:@"lastDeferredLink"]; - } - - // Return true to try to open deep link immediately upon receipt. - // Otherwise, return false. - return NO; +- (BOOL)adjustDeeplinkResponse:(NSURL *)deeplink { + if (deeplink) { + // Store incoming deferred deep link to invoke after + // onboarding screens and login. + [[NSUserDefaults standardUserDefaults] + setObject:[deeplink absoluteString] + forKey:@"lastDeferredLink"]; + } + // Return YES to try to open deep link immediately + // upon receipt (for example: app has no ATT, onboarding screens, or login). + // Otherwise, return NO. + return NO; } -// ... +@end ``` + + @@ -242,98 +304,131 @@ For deferred deep linking, this is the sequence for most apps: -```swift -// ViewController.swift + -import Adjust +```swift +import Foundation import UIKit +import Adjust class ViewController: UIViewController { - var hasCompletedOnboarding: Bool { - get { - UserDefaults.standard.bool(forKey: "HasCompletedOnboarding") - } - set { - UserDefaults.standard.set(newValue, forKey: "HasCompletedOnboarding") + var hasCompletedOnboarding: Bool { + get { + UserDefaults.standard.bool(forKey: "HasCompletedOnboarding") + } + set { + UserDefaults.standard.set( + newValue, forKey: "HasCompletedOnboarding") + } } - } - override func viewDidAppear() { - super.viewDidAppear() + override func viewDidAppear() { + super.viewDidAppear() - // Check if onboarding has been completed - if !hasCompletedOnboarding { - // Show ATT dialog - Adjust.requestTrackingAuthorization(completionHandler: nil) + // Check if onboarding has been completed + if !hasCompletedOnboarding { + // Show ATT dialog + Adjust.requestTrackingAuthorization(completionHandler: nil) - // Show onboarding screens and login prompt + // Show onboarding screens and login prompt - // On completion, set hasCompletedOnboarding to true - hasCompletedOnboarding = true - } + // On completion, set hasCompletedOnboarding to true + hasCompletedOnboarding = true + } - // Check if there's a stored deferred deep link - if let deferredLinkString = UserDefaults.standard.string( - forKey: "lastDeferredLink"), - let deferredLink = URL(string: deferredLinkString) { - // Remove the stored URL to avoid handling it again later - UserDefaults.standard.removeObject(forKey: "lastDeferredLink") + // Check if there's a stored deferred deep link + if let deferredLinkString = UserDefaults.standard.string( + forKey: "lastDeferredLink"), + let deferredLink = URL(string: deferredLinkString) + { + // Remove the stored URL to avoid handling it again later + UserDefaults.standard.removeObject(forKey: "lastDeferredLink") - // Handle deferred deep link - DeeplinkHandler.handleDeeplink(deferredLink, navigationController: self.navigationController) + // Handle deferred deep link + DeeplinkHandler.handleDeeplink( + deferredLink, navigationController: self.navigationController) - } else { - // Show main content + } + else { + // Show main content + } } - } } - -// ... ``` + + -```objc -// ViewController.m + +```objc +#import +#import #import +@class DeeplinkHandler; + +@interface ViewController : UIViewController + +@end + +@interface ViewController () + +@property(nonatomic, assign) BOOL hasCompletedOnboarding; + +@end + @implementation ViewController -- (void)viewDidLoad { - [super viewDidLoad]; +- (BOOL)hasCompletedOnboarding { + return [[NSUserDefaults standardUserDefaults] + boolForKey:@"HasCompletedOnboarding"]; +} - // Check if onboarding has been completed - if (!self.hasCompletedOnboarding) { - // Show ATT prompt - [Adjust - requestTrackingAuthorizationWithCompletionHandler:nil]; +- (void)setHasCompletedOnboarding:(BOOL)hasCompletedOnboarding { + [[NSUserDefaults standardUserDefaults] setBool:hasCompletedOnboarding + forKey:@"HasCompletedOnboarding"]; +} - // Show onboarding screens and user login +- (void)viewDidAppear { + [super viewDidAppear]; - // On completion, set hasCompletedOnboarding to true - self.hasCompletedOnboarding = YES; - } + // Check if onboarding has been completed + if (!self.hasCompletedOnboarding) { + // Show ATT prompt + [Adjust requestTrackingAuthorizationWithCompletionHandler:nil]; - // Check if there's a stored deferred deep link - NSString *deferredLinkString = [[NSUserDefaults standardUserDefaults] - stringForKey:@"lastDeferredLink"]; - NSURL *deferredLink = [NSURL URLWithString:deferredLinkString]; - if (deferredLink) { - // Handle deferred deep link - [[DeeplinkHandler shared] handleDeeplink:deferredLink]; - - // Remove the stored URL to avoid handling it again later - [[NSUserDefaults standardUserDefaults] - removeObjectForKey:@"lastDeferredLink"]; - } else { - // Show main content - } + // Show onboarding screens and login prompt + // On completion, set hasCompletedOnboarding to true + self.hasCompletedOnboarding = YES; + } + + // Check if there's a stored deferred deep link + NSString *deferredLinkString = [[NSUserDefaults standardUserDefaults] + stringForKey:@"lastDeferredLink"]; + NSURL *deferredLink = [NSURL URLWithString:deferredLinkString]; + + if (deferredLink) { + // Remove the stored URL to avoid handling it again later + [[NSUserDefaults standardUserDefaults] + removeObjectForKey:@"lastDeferredLink"]; + + // Handle deferred deep link + [DeeplinkHandler handleDeeplink:deferredLink + navigationController:self.navigationController]; + } else { + // Show main content + } } + +@end ``` + + @@ -350,15 +445,13 @@ The class performs the following tasks: 1. The class uses Adjust's `processDeeplink` method, which sends the deep link to Adjust's servers to accomplish two things: - Record the deep link for attribution. -- Resolve [Adjust short branded links](https://www.help.adjust.com/en/article/short-branded-links) and respond with full URLs, if applicable. +- Resolve [Adjust short branded link](https://www.help.adjust.com/en/article/short-branded-links) and respond with full URL, if applicable. 2. After processing, the class parses the link and navigates to the appropriate screen. This part of the code is specific to each app. Your app has to implement its own logic for handling deep links and opening the corresponding content. Your deep link handling has to meet the following key requirements: - Your app should treat Adjust branded links the same as other universal links, such as your own. Adjust recommends implementing domain-agnostic deep link handling logic to meet this requirement. For example, the following links should navigate to the same screen in your app: - - Adjust branded link: `https://example.go.link/summer-clothes?promo=beach` - Your universal link: `https://www.example.com/summer-clothes?promo=beach` - - In cases where iOS doesn't support universal links, Adjust automatically converts them to app scheme deep links. Additionally, Adjust's servers convert all deferred deep links to app scheme deep link format. Therefore it's crucial for the app to handle universal links and app scheme deep links equivalently. For example, the following links should navigate to the same screen in your app: - Adjust branded link: `https://example.go.link/summer-clothes?promo=beach` - App scheme deep link: `example://summer-clothes?promo=beach` @@ -366,342 +459,159 @@ The class performs the following tasks: -```swift -// DeeplinkHandler.swift + -import Adjust +```swift import Foundation import UIKit +import Adjust class DeeplinkHandler { - func handleDeeplink( - _ incomingDeeplink: URL, - navigationController: UINavigationController?) { - // Send incoming deep link to Adjust for attribution, - // and resolve short link, if applicable. - Adjust.processDeeplink(incomingDeeplink) { resolvedLinkString in - guard let resolvedLink = URL(string: resolvedLinkString) else { - print("Invalid resolved link") - return - } - - // Extract path, query items, and fragment from the - // resolved link. - let components = URLComponents( - url: resolvedLink, - resolvingAgainstBaseURL: true) - let path = components?.path ?? "" - let queryItems = components?.queryItems - let fragment = components?.fragment - - // Implement the navigation or other app-specific - // logic based on the deep link components. - // Example: Navigate to a specific view controller within the - // navigationController. - DispatchQueue.main.async { - // Example of navigating based on the path or - // other components. This is a placeholder. - // Replace with your actual navigation logic. - // Handle paths, query items, and fragments - // as needed. - if path == "/somePath" { - let viewController = SomeViewController() - navigationController?.pushViewController( - viewController, animated: true) + static func handleDeeplink( + _ incomingLink: URL, + navigationController: UINavigationController? + ) { + // Send incoming deep link to Adjust for attribution, + // and resolve short link, if applicable. + Adjust.processDeeplink(incomingLink) { processedLinkString in + guard let processedLink = URL(string: processedLinkString) else { + print("Invalid processed link") + return + } + + // Extract path, query items, and fragment from the + // resolved link. + let components = URLComponents( + url: processedLink, + resolvingAgainstBaseURL: true) + let path = components?.path ?? "" + let queryItems = components?.queryItems + let fragment = components?.fragment + + // Implement the navigation or other app-specific + // logic based on the deep link components. + // Example: Navigate to a specific view controller within the + // navigationController. + DispatchQueue.main.async { + // Example of navigating based on the path or + // other components. This is a placeholder. + // Replace with your actual navigation logic. + // Handle paths, query items, and fragments + // as needed. + if path == "/somePath" { + let viewController = SomeViewController() + navigationController?.pushViewController( + viewController, animated: true) + } + } } - } } - } } ``` + + + + ```objc -// DeeplinkHandler.m +#import +#import +#import -// ... +@interface DeeplinkHandler : NSObject -@implementation DeeplinkHandler ++ (void)handleDeeplink:(NSURL *)incomingLink + navigationController:(UINavigationController *)navigationController; -+ (void)handleDeeplink:(NSURL *)incomingDeeplink - navigationController:(UINavigationController *)navigationController { - // Send incoming deep link to Adjust for attribution, and resolve - // short link, if applicable. - [Adjust processDeeplink:incomingDeeplink - completion:^(NSString * _Nullable resolvedLinkString) { - NSURL *resolvedLink = [NSURL URLWithString:resolvedLinkString]; - if (!resolvedLink) { - NSLog(@"Invalid resolved link"); - return; - } +@end - // Extract path, query items, and fragment from the resolved link - NSURLComponents *components = - [[NSURLComponents alloc] - initWithURL:resolvedLink - resolvingAgainstBaseURL:YES]; - NSString *path = components.path ?: @""; - NSArray *queryItems = components.queryItems; - NSString *fragment = components.fragment; - - // Implement the navigation or other app-specific logic based on - // the deep link components. Example: Navigate to a specific view - // controller within the navigationController. - dispatch_async(dispatch_get_main_queue(), ^{ - // Example of navigating based on the path or - // other components. This is a placeholder. - // Replace with your actual navigation logic. - // Handle paths, query items, and fragments - // as needed. - if ([path isEqualToString:@"/somePath"]) { - SomeViewController *viewController = - [[SomeViewController alloc] init]; - [navigationController - pushViewController:viewController animated:YES]; - } - }); - }]; +@implementation DeeplinkHandler + ++ (void)handleDeeplink:(NSURL *)incomingLink + navigationController:(UINavigationController *)navigationController { + // Send incoming deep link to Adjust for attribution, + // and resolve short link, if applicable. + [Adjust + processDeeplink:incomingLink + completion:^(NSString *_Nullable processedLinkString) { + NSURL *processedLink = [NSURL URLWithString:processedLinkString]; + if (!processedLink) { + NSLog(@"Invalid processed link"); + return; + } + + // Extract path, query items, and fragment from the + // resolved link. + NSURLComponents *components = + [NSURLComponents componentsWithURL:processedLink + resolvingAgainstBaseURL:YES]; + NSString *path = components.path ?: @""; + NSArray *queryItems = components.queryItems; + NSString *fragment = components.fragment; + + // Implement the navigation or other app-specific + // logic based on the deep link components. + // Example: Navigate to a specific view controller within the + // navigationController. + dispatch_async(dispatch_get_main_queue(), ^{ + // Example of navigating based on the path or + // other components. This is a placeholder. + // Replace with your actual navigation logic. + // Handle paths, query items, and fragments + // as needed. + if ([path isEqualToString:@"/somePath"]) { + UIViewController *viewController = + [[UIViewController alloc] init]; + [navigationController pushViewController:viewController + animated:YES]; + } + }); + }]; } @end ``` + + ## UIKit apps using SceneDelegate lifecycle -Update your AppDelegate to implement deferred deep linking. +Follow the steps in the [UIKit apps using AppDelegate lifecycle section](#uikit-apps-using-appdelegate-lifecycle), except instead of implementing the `application(_:continue:restorationHandler:)` and `application(_:open:options:)` methods in your AppDelegate for direct deep linking, implement the following methods in your SceneDelegate. -```swift -// AppDelegate.swift - -// ... - -@UIApplicationMain -class AppDelegate: UIResponder, UIApplicationDelegate, - UNUserNotificationCenterDelegate, AdjustDelegate { - - func application( - _ application: UIApplication, - didFinishLaunchingWithOptions launchOptions: - [UIApplication.LaunchOptionsKey: Any]?) -> Bool { - - // Configure Adjust SDK - // Replace {YourAppToken} with your Adjust app token - let appToken = "{YourAppToken}" - var adjustConfig: ADJConfig? - - #if DEBUG - adjustConfig = ADJConfig( - appToken: appToken, - environment: ADJEnvironmentSandbox) - adjustConfig?.logLevel = ADJLogLevelVerbose - #else - adjustConfig = ADJConfig( - appToken: appToken, - environment: ADJEnvironmentProduction, - allowSuppressLogLevel: true) - adjustConfig?.logLevel = ADJLogLevelSuppress - #endif - - // Wait up to 120 seconds after app open for user to respond to ATT - // before sending install session to Adjust backend. - // Ensure this interval is long enough for user to respond. - adjustConfig.attConsentWaitingInterval = 30 - - // Create delegate for deferred deep linking - adjustConfig.delegate = self - - // Initialize Adjust SDK - Adjust.appDidLaunch(adjustConfig) - - return true - } - - // Receive push notification while app is in foreground - func userNotificationCenter( - _ center: UNUserNotificationCenter, - willPresent notification: UNNotification) async -> - UNNotificationPresentationOptions { - - // Add your push notification handling here - - // Change to your preferred presentation option - return [.banner, .sound, .badge] - } - - // Receive direct universal link or app scheme deep link - // from push notification while app isn't running, - // in background, or in foreground and user interacts - // with push notification. - // Replace "{deepLink}" with your custom key name. - func userNotificationCenter( - _ center: UNUserNotificationCenter, - didReceive response: UNNotificationResponse) async { - let userInfo = response.notification.request.content - .userInfo - - // Add your push notification handling here - - if let deepLinkURLString = userInfo["{deepLink}"] - as? String, - let incomingLink = URL( - string: deepLinkURLString) { - // Handle incoming deep link - DeeplinkHandler.shared.handleDeeplink( - incomingLink) - } - } - - // Receive deferred deep link via AdjustDelegate method - func adjustDeeplinkResponse(_ deeplink: URL?) -> Bool { - if let incomingLink = deeplink { - // Store incoming deferred deep link to invoke after - // onboarding screens and login. - UserDefaults.standard.set( - incomingLink.absoluteString, - forKey: "DeferredDeeplinkURL") - } - - // Return true to try to open deep link immediately upon receipt. - // Otherwise, return false. - return false - } -} - -// ... -``` - - - - -```objc -// AppDelegate.m - -// ... - -@implementation AppDelegate - -- (BOOL)application:(UIApplication *)application - didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { - - // Configure Adjust SDK - // Replace {YourAppToken} with your Adjust app token - NSString *appToken = @"{YourAppToken}"; - ADJConfig *adjustConfig; - -#ifdef DEBUG - adjustConfig = [ADJConfig configWithAppToken:appToken - environment:ADJEnvironmentSandbox]; - adjustConfig.logLevel = ADJLogLevelVerbose; -#else - adjustConfig = [ADJConfig configWithAppToken:appToken - environment:ADJEnvironmentProduction - allowSuppressLogLevel:YES]; - adjustConfig.logLevel = ADJLogLevelSuppress; -#endif - - // Wait up to 120 seconds after app open for user to respond to ATT - // before sending install session to Adjust backend. - // Ensure this interval is long enough for user to respond. - adjustConfig.attConsentWaitingInterval = 30; - - // Create delegate for deferred deep linking - adjustConfig.delegate = self; - - // Initialize Adjust SDK - [Adjust appDidLaunch:adjustConfig]; - - return YES; -} - -// Receive push notification while app is in foreground -- (void)userNotificationCenter:(UNUserNotificationCenter *)center - willPresentNotification:(UNNotification *)notification - withCompletionHandler: - (void (^)(UNNotificationPresentationOptions))completionHandler { - // Add your push notification handling here - - // Change to your preferred presentation option - completionHandler(UNNotificationPresentationOptionBanner | - UNNotificationPresentationOptionSound | - UNNotificationPresentationOptionBadge); -} - -// Receive direct universal link or app scheme deep link -// from push notification while app isn't running, -// in background, or in foreground and user interacts -// with push notification. -// Replace "{deepLink}" with your custom key name. -- (void)userNotificationCenter:(UNUserNotificationCenter *)center - didReceiveNotificationResponse:(UNNotificationResponse *)response - withCompletionHandler:(void (^)(void))completionHandler { - NSDictionary *userInfo = response.notification.request.content.userInfo; - - // Add your push notification handling here - - NSString *deepLinkURLString = userInfo[@"{deepLink}"]; - if (deepLinkURLString) { - NSURL *incomingLink = [NSURL URLWithString:deepLinkURLString]; - // Handle incoming deep link - [DeeplinkHandler.shared handleDeeplink:incomingLink]; - } - completionHandler(); -} - -// Receive deferred deep link via AdjustDelegate method -- (BOOL)adjustDeeplinkResponse:(NSURL *)deeplink { - if (deeplink) { - // Store incoming deferred deep link to invoke after - // onboarding screens and login. - [[NSUserDefaults standardUserDefaults] - setObject:[deeplink absoluteString] - forKey:@"DeferredDeeplinkURL"]; - } - - // Return true to try to open deep link immediately upon receipt. - // Otherwise, return false. - return NO; -} - -// ... -``` - - - - -Update your SceneDelegate to implement direct deep linking. - - - + ```swift -// SceneDelegate.swift - -// ... +import Foundation +import UIKit class SceneDelegate: UIResponder, UIWindowSceneDelegate { var window: UIWindow? - // Receive direct universal link or app scheme deep link - // while app isn't running, or when new scene is created. + // Receive universal link or app scheme deep link + // when app is installed and user clicks link to + // open app from "not running" state or to create new scene. func scene( _ scene: UIScene, willConnectTo session: UISceneSession, - options connectionOptions: UIScene.ConnectionOptions) { - // Initialize and make the window visible + options connectionOptions: UIScene.ConnectionOptions + ) { + // Example: initialize and make the window visible guard let windowScene = (scene as? UIWindowScene) else { return } window = UIWindow(windowScene: windowScene) let rootViewController = ViewController() - let navigationController = UINavigationController(rootViewController: rootViewController) + let navigationController = UINavigationController( + rootViewController: rootViewController) window?.rootViewController = navigationController window?.makeKeyAndVisible() @@ -715,11 +625,13 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { } } - // Receive direct universal link in existing scene - // while app is in background. + // Receive universal link when app is installed + // and user clicks link to open app to existing scene + // from background or foreground state. func scene(_ scene: UIScene, continue userActivity: NSUserActivity) { if userActivity.activityType == NSUserActivityTypeBrowsingWeb, - let incomingLink = userActivity.webpageURL { + let incomingLink = userActivity.webpageURL + { // Handle incoming universal link DeeplinkHandler.handleDeeplink( incomingLink, @@ -727,86 +639,118 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { } } - // Receive direct app scheme deep link in existing scene - // while app is in background or in foreground. + // Receive app scheme deep link when app is installed + // and user clicks link to open app to existing scene + // from background or foreground state. + // + // Also receive universal link or app scheme deep link + // when app programmatically opens link + // using UIApplication.shared.open. func scene( - _ scene: UIScene, openURLContexts URLContexts: Set) { + _ scene: UIScene, openURLContexts URLContexts: Set + ) { if let urlContext = URLContexts.first { let incomingLink = urlContext.url - // Handle incoming app scheme deep link + // Handle incoming deep link DeeplinkHandler.handleDeeplink( incomingLink, navigationController: navigationController) } } } - -// ... ``` + + + + ```objc -// SceneDelegate.m +#import +#import -// ... +@class DeeplinkHandler; +@class ViewController; + +@interface SceneDelegate : UIResponder + +@property(strong, nonatomic) UIWindow *window; + +@end @implementation SceneDelegate -// Receive direct universal link or app scheme deep link -// while app isn't running, or when new scene is created. +// Receive universal link or app scheme deep link +// when app is installed and user clicks link to +// open app from "not running" state or to create new scene. - (void)scene:(UIScene *)scene - willConnectToSession:(UISceneSession *)session - options:(UISceneConnectionOptions *)connectionOptions { + willConnectToSession:(UISceneSession *)session + options:(UISceneConnectionOptions *)connectionOptions { + // Example: initialize and make the window visible + UIWindowScene *windowScene = (UIWindowScene *)scene; + self.window = [[UIWindow alloc] initWithWindowScene:windowScene]; + ViewController *rootViewController = [[ViewController alloc] init]; + UINavigationController *navigationController = [[UINavigationController alloc] + initWithRootViewController:rootViewController]; + self.window.rootViewController = navigationController; + [self.window makeKeyAndVisible]; + if (connectionOptions.URLContexts.count > 0) { UIOpenURLContext *urlContext = - connectionOptions.URLContexts.allObjects.firstObject; + connectionOptions.URLContexts.allObjects.firstObject; NSURL *incomingLink = urlContext.URL; // Handle incoming deep link [DeeplinkHandler handleDeeplink:incomingLink - navigationController:navigationController]; + navigationController:navigationController]; } } -// Receive direct universal link in existing scene -// while app is in background. +// Receive universal link when app is installed +// and user clicks link to open app to existing scene +// from background or foreground state. - (void)scene:(UIScene *)scene - continueUserActivity:(NSUserActivity *)userActivity { + continueUserActivity:(NSUserActivity *)userActivity { if ([userActivity.activityType - isEqualToString:NSUserActivityTypeBrowsingWeb]) { + isEqualToString:NSUserActivityTypeBrowsingWeb]) { NSURL *incomingLink = userActivity.webpageURL; // Handle incoming universal link [DeeplinkHandler handleDeeplink:incomingLink - navigationController:navigationController]; + navigationController:self.window.rootViewController]; } } -// Receive direct app scheme deep link in existing scene -// while app is in background or in foreground. +// Receive app scheme deep link when app is installed +// and user clicks link to open app to existing scene +// from background or foreground state. +// +// Also receive universal link or app scheme deep link +// when app programmatically opens link +// using [[UIApplication sharedApplication] openURL:...]. - (void)scene:(UIScene *)scene - openURLContexts:(NSSet *)URLContexts { + openURLContexts:(NSSet *)URLContexts { UIOpenURLContext *urlContext = URLContexts.allObjects.firstObject; if (urlContext) { NSURL *incomingLink = urlContext.URL; - // Handle incoming app scheme deep link + // Handle incoming deep link [DeeplinkHandler handleDeeplink:incomingLink - navigationController:navigationController]; + navigationController:self.window.rootViewController]; } } -// ... +@end ``` + + -"ViewController" - ## SwiftUI apps not using SceneDelegate lifecycle Create an `AppDelegate.swift` file in your project's main directory and reference it in your main application file (for example: `App.swift` as shown below). This is required to handle app lifecycle events and Adjust SDK integration. @@ -1134,16 +1078,16 @@ struct ContentView: View { if let incomingLink = deeplink { // Send incoming deep link to Adjust for attribution, // and resolve short link, if applicable. - Adjust.processDeeplink(incomingLink) { resolvedLinkString in + Adjust.processDeeplink(incomingLink) { processedLinkString in guard - let resolvedLink = URL(string: resolvedLinkString ?? "") + let processedLink = URL(string: processedLinkString ?? "") else { - print("Invalid resolved link") + print("Invalid processed link") return } // Handle resolved deep link - DeeplinkHandler.shared.handleDeeplink(resolvedLink) + DeeplinkHandler.shared.handleDeeplink(processedLink) } // Remove deep link to avoid handling it again later @@ -1154,15 +1098,15 @@ struct ContentView: View { // Receive direct universal link or app scheme deep link // while app is in foreground. .onOpenURL { incomingLink in - Adjust.processDeeplink(incomingLink) { resolvedLinkString in - guard let resolvedLink = URL(string: resolvedLinkString ?? "") + Adjust.processDeeplink(incomingLink) { processedLinkString in + guard let processedLink = URL(string: processedLinkString ?? "") else { - print("Invalid resolved link") + print("Invalid processed link") return } // Handle resolved deep link - DeeplinkHandler.shared.handleDeeplink(resolvedLink) + DeeplinkHandler.shared.handleDeeplink(processedLink) } } } From 8a22a1b501b235d974853c1a86a751e9be39f062 Mon Sep 17 00:00:00 2001 From: Dave Mead Date: Fri, 11 Oct 2024 00:38:09 -0700 Subject: [PATCH 05/27] Finish UIKit sections --- .../deep-links/set-up-deep-linking.mdx | 376 ++++++++++++------ 1 file changed, 244 insertions(+), 132 deletions(-) diff --git a/src/content/docs/sdk/ios/v4/features/deep-links/set-up-deep-linking.mdx b/src/content/docs/sdk/ios/v4/features/deep-links/set-up-deep-linking.mdx index 73af1350b..0b90143aa 100644 --- a/src/content/docs/sdk/ios/v4/features/deep-links/set-up-deep-linking.mdx +++ b/src/content/docs/sdk/ios/v4/features/deep-links/set-up-deep-linking.mdx @@ -35,7 +35,7 @@ Update your AppDelegate to implement direct and deferred deep linking. -```swift {38-39,54-101} +```swift {40-41, 58-117} import Foundation import UIKit import Adjust @@ -81,9 +81,10 @@ class AppDelegate: UIResponder, UIApplicationDelegate, // Initialize Adjust SDK Adjust.appDidLaunch(adjustConfig) - // Example: initialize window and root view controller + // Example: initialize app's main window + // and set up the initial view hierarchy window = UIWindow(frame: UIScreen.main.bounds) - let rootViewController = ViewController() + let rootViewController = OnboardingViewController() let navigationController = UINavigationController( rootViewController: rootViewController) window?.rootViewController = navigationController @@ -109,9 +110,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, self.window?.rootViewController as? UINavigationController { // Handle incoming universal link - DeeplinkHandler.handleDeeplink( - incomingLink, - navigationController: navigationController) + DeeplinkHandler.handleDeeplink(incomingLink) } } return true @@ -134,9 +133,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, self.window?.rootViewController as? UINavigationController { // Handle incoming deep link - DeeplinkHandler.handleDeeplink( - incomingLink, - navigationController: navigationController) + DeeplinkHandler.handleDeeplink(incomingLink) } return true } @@ -166,13 +163,13 @@ class AppDelegate: UIResponder, UIApplicationDelegate, -```objc {34-35, 53-103} +```objc {37-38, 58-115} #import #import #import @class DeeplinkHandler; -@class ViewController; +@class OnboardingViewController; @interface AppDelegate : UIResponder @property(strong, nonatomic) UIWindow *window; @@ -209,10 +206,12 @@ class AppDelegate: UIResponder, UIApplicationDelegate, // Initialize Adjust SDK [Adjust appDidLaunch:adjustConfig]; - // Example: initialize window and root view controller + // Example: initialize app's main window + // and set up the initial view hierarchy self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; - UIViewController *rootViewController = [[UIViewController alloc] init]; + OnboardingViewController *rootViewController = + [[OnboardingViewController alloc] init]; UINavigationController *navigationController = [[UINavigationController alloc] initWithRootViewController:rootViewController]; @@ -239,8 +238,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, if ([navigationController isKindOfClass:[UINavigationController class]]) { // Handle incoming universal link - [DeeplinkHandler handleDeeplink:incomingLink - navigationController:navigationController]; + [DeeplinkHandler handleDeeplink:incomingLink]; } } return YES; @@ -262,8 +260,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, (UINavigationController *)self.window.rootViewController; if ([navigationController isKindOfClass:[UINavigationController class]]) { // Handle incoming app scheme deep link - [DeeplinkHandler handleDeeplink:incomingLink - navigationController:navigationController]; + [DeeplinkHandler handleDeeplink:incomingLink]; } return YES; } @@ -298,20 +295,20 @@ For deferred deep linking, this is the sequence for most apps: 3. The app begins its onboarding process (for example: ATT prompt, onboarding screens, login prompt). 4. After the ATT prompt response or timeout, the Adjust SDK sends session and attribution requests to Adjust's servers. 5. Adjust's servers respond with attribution data, including the deep link the user clicked on ("deferred deep link"). -6. The Adjust SDK triggers a deferred deep link callback in your app, which stores the deferred deep link (shown in the code example above). -7. Once onboarding completes, the app retrieves the stored deep link and navigates the user accordingly (shown in the code example below). +6. The Adjust SDK triggers a deferred deep link callback in your app, which stores the deferred deep link (shown in the AppDelegate implementation above). +7. Once onboarding completes, the app retrieves the stored deep link and navigates the user accordingly (shown in the OnboardingViewController example class below). - + ```swift import Foundation import UIKit import Adjust -class ViewController: UIViewController { +class OnboardingViewController: UIViewController { var hasCompletedOnboarding: Bool { get { @@ -346,9 +343,7 @@ class ViewController: UIViewController { UserDefaults.standard.removeObject(forKey: "lastDeferredLink") // Handle deferred deep link - DeeplinkHandler.handleDeeplink( - deferredLink, navigationController: self.navigationController) - + DeeplinkHandler.handleDeeplink(deferredLink) } else { // Show main content @@ -362,7 +357,7 @@ class ViewController: UIViewController { - + ```objc #import @@ -371,17 +366,17 @@ class ViewController: UIViewController { @class DeeplinkHandler; -@interface ViewController : UIViewController +@interface OnboardingViewController : UIViewController @end -@interface ViewController () +@interface OnboardingViewController () @property(nonatomic, assign) BOOL hasCompletedOnboarding; @end -@implementation ViewController +@implementation OnboardingViewController - (BOOL)hasCompletedOnboarding { return [[NSUserDefaults standardUserDefaults] @@ -417,8 +412,7 @@ class ViewController: UIViewController { removeObjectForKey:@"lastDeferredLink"]; // Handle deferred deep link - [DeeplinkHandler handleDeeplink:deferredLink - navigationController:self.navigationController]; + [DeeplinkHandler handleDeeplink:deferredLink]; } else { // Show main content } @@ -432,7 +426,7 @@ class ViewController: UIViewController { -The preceding code examples use the example DeeplinkHandler class below. This code example handles all types of links: +The preceding code examples use an example DeeplinkHandler class. This example class is shown below and handles all types of links: - Adjust branded links (full go.link links) - Adjust short branded links (short go.link links) @@ -467,46 +461,104 @@ import UIKit import Adjust class DeeplinkHandler { + static func handleDeeplink(_ incomingLink: URL) { + guard let navigationController = getNavigationController() else { + return + } - static func handleDeeplink( - _ incomingLink: URL, - navigationController: UINavigationController? - ) { - // Send incoming deep link to Adjust for attribution, - // and resolve short link, if applicable. - Adjust.processDeeplink(incomingLink) { processedLinkString in - guard let processedLink = URL(string: processedLinkString) else { - print("Invalid processed link") - return - } + // Send incoming deep link to Adjust's servers for attribution + // and to resolve short link, if applicable. + Adjust.processDeeplink(incomingLink) { processedLinkString in + guard let processedLink = URL(string: processedLinkString) else { + print("Invalid processed link") + return + } - // Extract path, query items, and fragment from the - // resolved link. - let components = URLComponents( - url: processedLink, - resolvingAgainstBaseURL: true) - let path = components?.path ?? "" - let queryItems = components?.queryItems - let fragment = components?.fragment - - // Implement the navigation or other app-specific - // logic based on the deep link components. - // Example: Navigate to a specific view controller within the - // navigationController. - DispatchQueue.main.async { - // Example of navigating based on the path or - // other components. This is a placeholder. - // Replace with your actual navigation logic. - // Handle paths, query items, and fragments - // as needed. - if path == "/somePath" { - let viewController = SomeViewController() - navigationController?.pushViewController( - viewController, animated: true) - } - } + // Extract path, query items, and fragment from the resolved link. + let components = URLComponents( + url: processedLink, + resolvingAgainstBaseURL: true) + let path = components?.path ?? "" + let queryItems = components?.queryItems ?? [] + + // Parse query parameters into a dictionary for easier access + let params = queryItems.reduce(into: [String: String]()) { + result, item in + result[item.name] = item.value + } + let fragment = components?.fragment + + // Implement the navigation or other app-specific logic based on + // the deep link components. + DispatchQueue.main.async { + // Example of navigating based on the path or other components. + // Replace with your actual navigation logic. + // Handle paths, query items, and fragments as needed. + switch path { + case "/product": + if let productId = params["id"] { + let productVC = ProductViewController( + productId: productId + ) + navigationController.pushViewController( + productVC, + animated: true + ) + } + case "/category": + if let categoryName = params["name"] { + let categoryVC = CategoryViewController( + category: categoryName + ) + navigationController.pushViewController( + categoryVC, + animated: true + ) + } + case "/search": + if let query = params["q"] { + let searchVC = SearchViewController( + searchQuery: query + ) + navigationController.pushViewController( + searchVC, + animated: true + ) + } + default: + print("Unhandled deep link path: \(path)") } + } + } + } + + static func getNavigationController() -> UINavigationController? { + // For UIKit apps using AppDelegate lifecycle, get the navigation + // controller: This allows us to navigate to the appropriate view + // controller based on the deep link. + if let navigationController = UIApplication.shared.windows + .first(where: { $0.isKeyWindow })? + .rootViewController as? UINavigationController + { + return navigationController } + + // For UIKit apps using SceneDelegate lifecycle, use this instead to + // get the navigation controller: + /* + guard let sceneDelegate = UIApplication.shared.connectedScenes + .compactMap({ $0.delegate as? SceneDelegate }) + .first, + let navigationController = sceneDelegate.window? + .rootViewController as? UINavigationController + else { + return nil + } + return navigationController + */ + + return nil + } } ``` @@ -523,54 +575,119 @@ class DeeplinkHandler { #import @interface DeeplinkHandler : NSObject - -+ (void)handleDeeplink:(NSURL *)incomingLink - navigationController:(UINavigationController *)navigationController; - ++ (void)handleDeeplink:(NSURL *)incomingLink; @end @implementation DeeplinkHandler -+ (void)handleDeeplink:(NSURL *)incomingLink - navigationController:(UINavigationController *)navigationController { - // Send incoming deep link to Adjust for attribution, - // and resolve short link, if applicable. - [Adjust - processDeeplink:incomingLink - completion:^(NSString *_Nullable processedLinkString) { - NSURL *processedLink = [NSURL URLWithString:processedLinkString]; - if (!processedLink) { - NSLog(@"Invalid processed link"); - return; - } - - // Extract path, query items, and fragment from the - // resolved link. - NSURLComponents *components = - [NSURLComponents componentsWithURL:processedLink - resolvingAgainstBaseURL:YES]; - NSString *path = components.path ?: @""; - NSArray *queryItems = components.queryItems; - NSString *fragment = components.fragment; - - // Implement the navigation or other app-specific - // logic based on the deep link components. - // Example: Navigate to a specific view controller within the - // navigationController. - dispatch_async(dispatch_get_main_queue(), ^{ - // Example of navigating based on the path or - // other components. This is a placeholder. - // Replace with your actual navigation logic. - // Handle paths, query items, and fragments - // as needed. - if ([path isEqualToString:@"/somePath"]) { - UIViewController *viewController = - [[UIViewController alloc] init]; - [navigationController pushViewController:viewController - animated:YES]; - } - }); - }]; ++ (void)handleDeeplink:(NSURL *)incomingLink { + UINavigationController *navigationController = [self getNavigationController]; + + if (!navigationController) { + return; + } + + // Send incoming deep link to Adjust's servers for attribution + // and to resolve short link, if applicable. + [Adjust processDeeplink:incomingLink + completionHandler:^(NSString * _Nonnull processedLinkString) { + NSURL *processedLink = [NSURL URLWithString:processedLinkString]; + if (!processedLink) { + NSLog(@"Invalid processed link"); + return; + } + + // Extract path, query items, and fragment from the resolved link. + NSURLComponents *components = + [NSURLComponents componentsWithURL:processedLink + resolvingAgainstBaseURL:YES]; + NSString *path = components.path ?: @""; + NSArray *queryItems = components.queryItems ?: @[]; + + // Parse query parameters into a dictionary for easier access + NSMutableDictionary *params = + [NSMutableDictionary dictionary]; + for (NSURLQueryItem *item in queryItems) { + params[item.name] = item.value; + } + NSString *fragment = components.fragment; + + // Implement the navigation or other app-specific logic based on + // the deep link components. + dispatch_async(dispatch_get_main_queue(), ^{ + // Example of navigating based on the path or other components. + // Replace with your actual navigation logic. + // Handle paths, query items, and fragments as needed. + if ([path isEqualToString:@"/product"]) { + NSString *productId = params[@"id"]; + if (productId) { + ProductViewController *productVC = + [[ProductViewController alloc] + initWithProductId:productId]; + [navigationController pushViewController:productVC + animated:YES]; + } + } else if ([path isEqualToString:@"/category"]) { + NSString *categoryName = params[@"name"]; + if (categoryName) { + CategoryViewController *categoryVC = + [[CategoryViewController alloc] + initWithCategory:categoryName]; + [navigationController pushViewController:categoryVC + animated:YES]; + } + } else if ([path isEqualToString:@"/search"]) { + NSString *query = params[@"q"]; + if (query) { + SearchViewController *searchVC = + [[SearchViewController alloc] + initWithSearchQuery:query]; + [navigationController pushViewController:searchVC + animated:YES]; + } + } else { + NSLog(@"Unhandled deep link path: %@", path); + } + }); + }]; +} + ++ (UINavigationController *)getNavigationController { + // For UIKit apps using AppDelegate lifecycle, get the navigation + // controller: This allows us to navigate to the appropriate view + // controller based on the deep link. + UINavigationController *navigationController = nil; + for (UIWindow *window in UIApplication.sharedApplication.windows) { + if (window.isKeyWindow) { + navigationController = + (UINavigationController *)window.rootViewController; + break; + } + } + if (!navigationController) { + return nil; + } + + // For UIKit apps using SceneDelegate lifecycle, use this instead to + // get the navigation controller: + /* + for (UIWindowScene *scene in + UIApplication.sharedApplication.connectedScenes) { + id sceneDelegate = + (id)scene.delegate; + if ([sceneDelegate isKindOfClass:[SceneDelegate class]]) { + SceneDelegate *sceneDel = (SceneDelegate *)sceneDelegate; + navigationController = + (UINavigationController *)sceneDel.window.rootViewController; + break; + } + } + if (!navigationController) { + return nil; + } + */ + + return navigationController; } @end @@ -606,10 +723,11 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions ) { - // Example: initialize and make the window visible + // Example: configure the scene's window + // and set up the initial view hierarchy guard let windowScene = (scene as? UIWindowScene) else { return } window = UIWindow(windowScene: windowScene) - let rootViewController = ViewController() + let rootViewController = OnboardingViewController() let navigationController = UINavigationController( rootViewController: rootViewController) window?.rootViewController = navigationController @@ -619,9 +737,7 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { let incomingLink = urlContext.url // Handle incoming deep link - DeeplinkHandler.handleDeeplink( - incomingLink, - navigationController: navigationController) + DeeplinkHandler.handleDeeplink(incomingLink) } } @@ -633,9 +749,7 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { let incomingLink = userActivity.webpageURL { // Handle incoming universal link - DeeplinkHandler.handleDeeplink( - incomingLink, - navigationController: navigationController) + DeeplinkHandler.handleDeeplink(incomingLink) } } @@ -653,9 +767,7 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { let incomingLink = urlContext.url // Handle incoming deep link - DeeplinkHandler.handleDeeplink( - incomingLink, - navigationController: navigationController) + DeeplinkHandler.handleDeeplink(incomingLink) } } } @@ -673,7 +785,7 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { #import @class DeeplinkHandler; -@class ViewController; +@class OnboardingViewController; @interface SceneDelegate : UIResponder @@ -689,11 +801,14 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { - (void)scene:(UIScene *)scene willConnectToSession:(UISceneSession *)session options:(UISceneConnectionOptions *)connectionOptions { - // Example: initialize and make the window visible + // Example: configure the scene's window + // and set up the initial view hierarchy UIWindowScene *windowScene = (UIWindowScene *)scene; self.window = [[UIWindow alloc] initWithWindowScene:windowScene]; - ViewController *rootViewController = [[ViewController alloc] init]; - UINavigationController *navigationController = [[UINavigationController alloc] + OnboardingViewController *rootViewController = + [[OnboardingViewController alloc] init]; + UINavigationController *navigationController = + [[UINavigationController alloc] initWithRootViewController:rootViewController]; self.window.rootViewController = navigationController; [self.window makeKeyAndVisible]; @@ -704,8 +819,7 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { NSURL *incomingLink = urlContext.URL; // Handle incoming deep link - [DeeplinkHandler handleDeeplink:incomingLink - navigationController:navigationController]; + [DeeplinkHandler handleDeeplink:incomingLink]; } } @@ -719,8 +833,7 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { NSURL *incomingLink = userActivity.webpageURL; // Handle incoming universal link - [DeeplinkHandler handleDeeplink:incomingLink - navigationController:self.window.rootViewController]; + [DeeplinkHandler handleDeeplink:incomingLink]; } } @@ -738,8 +851,7 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { NSURL *incomingLink = urlContext.URL; // Handle incoming deep link - [DeeplinkHandler handleDeeplink:incomingLink - navigationController:self.window.rootViewController]; + [DeeplinkHandler handleDeeplink:incomingLink]; } } From d1b9478b1ef146b89249a95fd18e8e64b40c49fa Mon Sep 17 00:00:00 2001 From: Dave Mead Date: Mon, 14 Oct 2024 22:57:09 -0700 Subject: [PATCH 06/27] Fix bullet formatting --- .../features/deep-links/configure-deep-link-settings.mdx | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/content/docs/sdk/ios/v4/features/deep-links/configure-deep-link-settings.mdx b/src/content/docs/sdk/ios/v4/features/deep-links/configure-deep-link-settings.mdx index 6b1ce23d2..8a08a8062 100644 --- a/src/content/docs/sdk/ios/v4/features/deep-links/configure-deep-link-settings.mdx +++ b/src/content/docs/sdk/ios/v4/features/deep-links/configure-deep-link-settings.mdx @@ -66,7 +66,14 @@ If your app already has an app scheme, determine which of the below configuratio | Build setting variable, such as `$(APP_SCHEME)` | Static values, such as `com.example.app` and `com.example.debug`, respectively | **Build Settings** tab -> search for "APP_SCHEME" -> values for Release and Debug are the "Release App Scheme" and "Debug App Scheme," respectively | | Build setting variable, such as `$(APP_SCHEME)` | Build setting variable, such as `$(PRODUCT_BUNDLE_IDENTIFIER)` | **Build Settings** tab -> search for "APP_SCHEME" -> values for Release and Debug are the "Release App Scheme" and "Debug App Scheme," respectively | -If you need to set up an app scheme, follow these steps: - Select the **+** button to add a new URL Type. - Fill in the following fields: - **Identifier**: `$(PRODUCT_BUNDLE_IDENTIFIER)` - **Role**: Editor - **URL Schemes**: Enter your desired app scheme (for example: enter `example` to represent `example://`). Don't use `http`, `https`, or reserved iOS schemes like `mailto`, `tel`, `sms`, or `facetime`. Entering a static value here will create a single app scheme used for both release and debug builds. - Once created, remember to note the app scheme for configuration in the Adjust dashboard later. +If you need to set up an app scheme, follow these steps: + +- Select the **+** button to add a new URL Type. +- Fill in the following fields: + - **Identifier**: `$(PRODUCT_BUNDLE_IDENTIFIER)` + - **Role**: Editor + - **URL Schemes**: Enter your desired app scheme (for example: enter `example` to represent `example://`). Don't use `http`, `https`, or reserved iOS schemes like `mailto`, `tel`, `sms`, or `facetime`. Entering a static value here will create a single app scheme used for both release and debug builds. +- Once created, remember to note the app scheme for configuration in the Adjust dashboard later.
## Retrieve App ID Prefix from Apple Developer Portal From b0707d5e86213d74691a921b6b1f3dbb2cb56841 Mon Sep 17 00:00:00 2001 From: Dave Mead Date: Tue, 15 Oct 2024 01:04:29 -0700 Subject: [PATCH 07/27] Revise code examples --- .../deep-links/set-up-deep-linking.mdx | 550 +++++++----------- 1 file changed, 197 insertions(+), 353 deletions(-) diff --git a/src/content/docs/sdk/ios/v4/features/deep-links/set-up-deep-linking.mdx b/src/content/docs/sdk/ios/v4/features/deep-links/set-up-deep-linking.mdx index 0b90143aa..b4ed8b0ca 100644 --- a/src/content/docs/sdk/ios/v4/features/deep-links/set-up-deep-linking.mdx +++ b/src/content/docs/sdk/ios/v4/features/deep-links/set-up-deep-linking.mdx @@ -36,123 +36,105 @@ Update your AppDelegate to implement direct and deferred deep linking. ```swift {40-41, 58-117} +import Adjust import Foundation import UIKit -import Adjust @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate, - AdjustDelegate + AdjustDelegate { + func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: + [UIApplication.LaunchOptionsKey: Any]? + ) -> Bool { + // Configure Adjust SDK + // Replace {YourAppToken} with your Adjust app token + let appToken = "{YourAppToken}" + var adjustConfig: ADJConfig? - var window: UIWindow? - - func application( - _ application: UIApplication, - didFinishLaunchingWithOptions launchOptions: - [UIApplication.LaunchOptionsKey: Any]? - ) -> Bool { - // Configure Adjust SDK - // Replace {YourAppToken} with your Adjust app token - let appToken = "{YourAppToken}" - var adjustConfig: ADJConfig? - - #if DEBUG - adjustConfig = ADJConfig( - appToken: appToken, - environment: ADJEnvironmentSandbox) - adjustConfig?.logLevel = ADJLogLevelVerbose - #else - adjustConfig = ADJConfig( - appToken: appToken, - environment: ADJEnvironmentProduction, - allowSuppressLogLevel: true) - adjustConfig?.logLevel = ADJLogLevelSuppress - #endif - - // Wait up to 120 seconds after app open for user to respond to ATT - // before sending install session to Adjust's servers. - // Ensure this interval is long enough for user to respond. - adjustConfig?.attConsentWaitingInterval = 120 - - // Create delegate for deferred deep linking - adjustConfig?.delegate = self - - // Initialize Adjust SDK - Adjust.appDidLaunch(adjustConfig) - - // Example: initialize app's main window - // and set up the initial view hierarchy - window = UIWindow(frame: UIScreen.main.bounds) - let rootViewController = OnboardingViewController() - let navigationController = UINavigationController( - rootViewController: rootViewController) - window?.rootViewController = navigationController - window?.makeKeyAndVisible() - - return true - } + #if DEBUG + adjustConfig = ADJConfig( + appToken: appToken, + environment: ADJEnvironmentSandbox) + adjustConfig?.logLevel = ADJLogLevelVerbose + #else + adjustConfig = ADJConfig( + appToken: appToken, + environment: ADJEnvironmentProduction, + allowSuppressLogLevel: true) + adjustConfig?.logLevel = ADJLogLevelSuppress + #endif - // Receive universal link when app is installed - // and user clicks link to open app - // from one of these states: - // not running, background, or foreground. - func application( - _ application: UIApplication, - continue userActivity: NSUserActivity, - restorationHandler: - @escaping ([UIUserActivityRestoring]?) -> Void - ) -> Bool { - if userActivity.activityType == NSUserActivityTypeBrowsingWeb, - let incomingLink = userActivity.webpageURL - { - let navigationController = - self.window?.rootViewController as? UINavigationController - { - // Handle incoming universal link - DeeplinkHandler.handleDeeplink(incomingLink) - } - } - return true - } + // Wait up to 120 seconds after app open for user to respond to ATT + // before sending install session to Adjust's servers. + // Ensure this interval is long enough for user to respond. + adjustConfig?.attConsentWaitingInterval = 120 - // Receive app scheme deep link when app is installed - // and user clicks link to open app - // from one of these states: - // not running, background, or foreground. - // - // Also receive universal link or app scheme deep link - // when app programmatically opens link - // using UIApplication.shared.open. - func application( - _ application: UIApplication, - open incomingLink: URL, - options: [UIApplication.OpenURLOptionsKey: Any] = [:] - ) -> Bool { - let navigationController = - self.window?.rootViewController as? UINavigationController - { - // Handle incoming deep link - DeeplinkHandler.handleDeeplink(incomingLink) - } - return true + // Create delegate for deferred deep linking + adjustConfig?.delegate = self + + // Initialize Adjust SDK + Adjust.appDidLaunch(adjustConfig) + + return true + } + + // Receive universal link when app is installed + // and user clicks link to open app + // from one of these states: + // not running, background, or foreground. + func application( + _ application: UIApplication, + continue userActivity: NSUserActivity, + restorationHandler: + @escaping ([UIUserActivityRestoring]?) -> Void + ) -> Bool { + if userActivity.activityType == NSUserActivityTypeBrowsingWeb, + let incomingLink = userActivity.webpageURL + { + // Handle incoming universal link + DeeplinkHandler.handleDeeplink(incomingLink) } + return true + } - // Receive deferred deep link via AdjustDelegate method - func adjustDeeplinkResponse(_ deeplink: URL?) -> Bool { - if let incomingLink = deeplink { - // Store incoming deferred deep link to invoke after - // onboarding screens and login. - UserDefaults.standard.set( - incomingLink.absoluteString, - forKey: "lastDeferredLink") - } + // Receive app scheme deep link when app is installed + // and user clicks link to open app + // from one of these states: + // not running, background, or foreground. + // + // Also receive universal link or app scheme deep link + // when app programmatically opens link + // using UIApplication.shared.open. + func application( + _ application: UIApplication, + open incomingLink: URL, + options: [UIApplication.OpenURLOptionsKey: Any] = [:] + ) -> Bool { + { + // Handle incoming deep link + DeeplinkHandler.handleDeeplink(incomingLink) + } + return true + } - // Return true to try to open deep link immediately - // upon receipt (for example: app has no ATT, onboarding screens, or login). - // Otherwise, return false. - return false + // Receive deferred deep link via AdjustDelegate method + func adjustDeeplinkResponse(_ deeplink: URL?) -> Bool { + if let incomingLink = deeplink { + // Store incoming deferred deep link to invoke after + // onboarding screens and login. + UserDefaults.standard.set( + incomingLink.absoluteString, + forKey: "lastDeferredLink") } + + // Return true to try to open deep link immediately + // upon receipt (for example: app has no ATT, onboarding screens, or login). + // Otherwise, return false. + return false + } } ``` @@ -164,61 +146,47 @@ class AppDelegate: UIResponder, UIApplicationDelegate, ```objc {37-38, 58-115} +#import #import #import -#import @class DeeplinkHandler; -@class OnboardingViewController; @interface AppDelegate : UIResponder -@property(strong, nonatomic) UIWindow *window; @end @implementation AppDelegate - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { - // Configure Adjust SDK - // Replace {YourAppToken} with your Adjust app token - NSString *appToken = @"{YourAppToken}"; - ADJConfig *adjustConfig; + // Configure Adjust SDK + // Replace {YourAppToken} with your Adjust app token + NSString *appToken = @"{YourAppToken}"; + ADJConfig *adjustConfig; #ifdef DEBUG - adjustConfig = [ADJConfig configWithAppToken:appToken - environment:ADJEnvironmentSandbox]; - [adjustConfig setLogLevel:ADJLogLevelVerbose]; + adjustConfig = [ADJConfig configWithAppToken:appToken + environment:ADJEnvironmentSandbox]; + [adjustConfig setLogLevel:ADJLogLevelVerbose]; #else - adjustConfig = [ADJConfig configWithAppToken:appToken - environment:ADJEnvironmentProduction - allowSuppressLogLevel:YES]; - [adjustConfig setLogLevel:ADJLogLevelSuppress]; + adjustConfig = [ADJConfig configWithAppToken:appToken + environment:ADJEnvironmentProduction + allowSuppressLogLevel:YES]; + [adjustConfig setLogLevel:ADJLogLevelSuppress]; #endif - // Wait up to 120 seconds after app open for user to respond to ATT - // before sending install session to Adjust's servers. - // Ensure this interval is long enough for user to respond. - adjustConfig.attConsentWaitingInterval = 120; + // Wait up to 120 seconds after app open for user to respond to ATT + // before sending install session to Adjust's servers. + // Ensure this interval is long enough for user to respond. + adjustConfig.attConsentWaitingInterval = 120; - // Create delegate for deferred deep linking - adjustConfig.delegate = self; + // Create delegate for deferred deep linking + adjustConfig.delegate = self; - // Initialize Adjust SDK - [Adjust appDidLaunch:adjustConfig]; - - // Example: initialize app's main window - // and set up the initial view hierarchy - self.window = - [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; - OnboardingViewController *rootViewController = - [[OnboardingViewController alloc] init]; - UINavigationController *navigationController = - [[UINavigationController alloc] - initWithRootViewController:rootViewController]; - self.window.rootViewController = navigationController; - [self.window makeKeyAndVisible]; - - return YES; + // Initialize Adjust SDK + [Adjust appDidLaunch:adjustConfig]; + + return YES; } // Receive universal link when app is installed @@ -230,18 +198,13 @@ class AppDelegate: UIResponder, UIApplicationDelegate, restorationHandler: (void (^)(NSArray> *_Nullable)) restorationHandler { - if ([userActivity.activityType - isEqualToString:NSUserActivityTypeBrowsingWeb]) { - NSURL *incomingLink = userActivity.webpageURL; - UINavigationController *navigationController = - (UINavigationController *)self.window.rootViewController; - if ([navigationController - isKindOfClass:[UINavigationController class]]) { - // Handle incoming universal link - [DeeplinkHandler handleDeeplink:incomingLink]; - } - } - return YES; + if ([userActivity.activityType + isEqualToString:NSUserActivityTypeBrowsingWeb]) { + NSURL *incomingLink = userActivity.webpageURL; + // Handle incoming universal link + [DeeplinkHandler handleDeeplink:incomingLink]; + } + return YES; } // Receive app scheme deep link when app is installed @@ -256,28 +219,23 @@ class AppDelegate: UIResponder, UIApplicationDelegate, openURL:(NSURL *)incomingLink options: (NSDictionary *)options { - UINavigationController *navigationController = - (UINavigationController *)self.window.rootViewController; - if ([navigationController isKindOfClass:[UINavigationController class]]) { - // Handle incoming app scheme deep link - [DeeplinkHandler handleDeeplink:incomingLink]; - } - return YES; + // Handle incoming app scheme deep link + [DeeplinkHandler handleDeeplink:incomingLink]; + return YES; } // Receive deferred deep link via AdjustDelegate method - (BOOL)adjustDeeplinkResponse:(NSURL *)deeplink { - if (deeplink) { - // Store incoming deferred deep link to invoke after - // onboarding screens and login. - [[NSUserDefaults standardUserDefaults] - setObject:[deeplink absoluteString] - forKey:@"lastDeferredLink"]; - } - // Return YES to try to open deep link immediately - // upon receipt (for example: app has no ATT, onboarding screens, or login). - // Otherwise, return NO. - return NO; + if (deeplink) { + // Store incoming deferred deep link to invoke after + // onboarding screens and login. + [[NSUserDefaults standardUserDefaults] setObject:[deeplink absoluteString] + forKey:@"lastDeferredLink"]; + } + // Return YES to try to open deep link immediately + // upon receipt (for example: app has no ATT, onboarding screens, or login). + // Otherwise, return NO. + return NO; } @end @@ -304,9 +262,9 @@ For deferred deep linking, this is the sequence for most apps: ```swift +import Adjust import Foundation import UIKit -import Adjust class OnboardingViewController: UIViewController { @@ -360,9 +318,9 @@ class OnboardingViewController: UIViewController { ```objc +#import #import #import -#import @class DeeplinkHandler; @@ -456,16 +414,12 @@ The class performs the following tasks: ```swift +import Adjust import Foundation import UIKit -import Adjust class DeeplinkHandler { static func handleDeeplink(_ incomingLink: URL) { - guard let navigationController = getNavigationController() else { - return - } - // Send incoming deep link to Adjust's servers for attribution // and to resolve short link, if applicable. Adjust.processDeeplink(incomingLink) { processedLinkString in @@ -476,16 +430,15 @@ class DeeplinkHandler { // Extract path, query items, and fragment from the resolved link. let components = URLComponents( - url: processedLink, - resolvingAgainstBaseURL: true) + url: processedLink, resolvingAgainstBaseURL: true) let path = components?.path ?? "" let queryItems = components?.queryItems ?? [] // Parse query parameters into a dictionary for easier access - let params = queryItems.reduce(into: [String: String]()) { - result, item in + let params = queryItems.reduce(into: [String: String]()) { result, item in result[item.name] = item.value } + let fragment = components?.fragment // Implement the navigation or other app-specific logic based on @@ -493,37 +446,21 @@ class DeeplinkHandler { DispatchQueue.main.async { // Example of navigating based on the path or other components. // Replace with your actual navigation logic. - // Handle paths, query items, and fragments as needed. switch path { case "/product": if let productId = params["id"] { - let productVC = ProductViewController( - productId: productId - ) - navigationController.pushViewController( - productVC, - animated: true - ) + let productVC = ProductViewController(productId: productId) + navigateTo(viewController: productVC) } case "/category": if let categoryName = params["name"] { - let categoryVC = CategoryViewController( - category: categoryName - ) - navigationController.pushViewController( - categoryVC, - animated: true - ) + let categoryVC = CategoryViewController(category: categoryName) + navigateTo(viewController: categoryVC) } case "/search": if let query = params["q"] { - let searchVC = SearchViewController( - searchQuery: query - ) - navigationController.pushViewController( - searchVC, - animated: true - ) + let searchVC = SearchViewController(searchQuery: query) + navigateTo(viewController: productVC) } default: print("Unhandled deep link path: \(path)") @@ -532,32 +469,10 @@ class DeeplinkHandler { } } - static func getNavigationController() -> UINavigationController? { - // For UIKit apps using AppDelegate lifecycle, get the navigation - // controller: This allows us to navigate to the appropriate view - // controller based on the deep link. - if let navigationController = UIApplication.shared.windows - .first(where: { $0.isKeyWindow })? - .rootViewController as? UINavigationController - { - return navigationController - } - - // For UIKit apps using SceneDelegate lifecycle, use this instead to - // get the navigation controller: - /* - guard let sceneDelegate = UIApplication.shared.connectedScenes - .compactMap({ $0.delegate as? SceneDelegate }) - .first, - let navigationController = sceneDelegate.window? - .rootViewController as? UINavigationController - else { - return nil - } - return navigationController - */ - - return nil + private static func navigateTo(viewController: UIViewController) { + // Implement your navigation logic here + // For example: + // navigationController.pushViewController(viewController, animated: true) } } ``` @@ -570,9 +485,9 @@ class DeeplinkHandler { ```objc +#import #import #import -#import @interface DeeplinkHandler : NSObject + (void)handleDeeplink:(NSURL *)incomingLink; @@ -581,113 +496,73 @@ class DeeplinkHandler { @implementation DeeplinkHandler + (void)handleDeeplink:(NSURL *)incomingLink { - UINavigationController *navigationController = [self getNavigationController]; - - if (!navigationController) { - return; - } - - // Send incoming deep link to Adjust's servers for attribution - // and to resolve short link, if applicable. - [Adjust processDeeplink:incomingLink - completionHandler:^(NSString * _Nonnull processedLinkString) { - NSURL *processedLink = [NSURL URLWithString:processedLinkString]; - if (!processedLink) { + // Send incoming deep link to Adjust's servers for attribution + // and to resolve short link, if applicable. + [Adjust processDeeplink:incomingLink + completionHandler:^(NSString *_Nonnull processedLinkString) { + NSURL *processedLink = [NSURL URLWithString:processedLinkString]; + if (!processedLink) { NSLog(@"Invalid processed link"); return; - } + } - // Extract path, query items, and fragment from the resolved link. - NSURLComponents *components = - [NSURLComponents componentsWithURL:processedLink - resolvingAgainstBaseURL:YES]; - NSString *path = components.path ?: @""; - NSArray *queryItems = components.queryItems ?: @[]; - - // Parse query parameters into a dictionary for easier access - NSMutableDictionary *params = - [NSMutableDictionary dictionary]; - for (NSURLQueryItem *item in queryItems) { + // Extract path, query items, and fragment from the resolved link. + NSURLComponents *components = + [NSURLComponents componentsWithURL:processedLink + resolvingAgainstBaseURL:YES]; + NSString *path = components.path ?: @""; + NSArray *queryItems = components.queryItems ?: @[]; + + // Parse query parameters into a dictionary for easier access + NSMutableDictionary *params = + [NSMutableDictionary dictionary]; + for (NSURLQueryItem *item in queryItems) { params[item.name] = item.value; - } - NSString *fragment = components.fragment; + } + NSString *fragment = components.fragment; - // Implement the navigation or other app-specific logic based on - // the deep link components. - dispatch_async(dispatch_get_main_queue(), ^{ + // Implement the navigation or other app-specific logic based on + // the deep link components. + dispatch_async(dispatch_get_main_queue(), ^{ // Example of navigating based on the path or other components. // Replace with your actual navigation logic. // Handle paths, query items, and fragments as needed. if ([path isEqualToString:@"/product"]) { - NSString *productId = params[@"id"]; - if (productId) { - ProductViewController *productVC = - [[ProductViewController alloc] - initWithProductId:productId]; - [navigationController pushViewController:productVC - animated:YES]; - } + NSString *productId = params[@"id"]; + if (productId) { + ProductViewController *productVC = + [[ProductViewController alloc] initWithProductId:productId]; + [self navigateToViewController:productVC]; + } } else if ([path isEqualToString:@"/category"]) { - NSString *categoryName = params[@"name"]; - if (categoryName) { - CategoryViewController *categoryVC = - [[CategoryViewController alloc] - initWithCategory:categoryName]; - [navigationController pushViewController:categoryVC - animated:YES]; - } + NSString *categoryName = params[@"name"]; + if (categoryName) { + CategoryViewController *categoryVC = + [[CategoryViewController alloc] + initWithCategory:categoryName]; + [self navigateToViewController:categoryVC]; + } } else if ([path isEqualToString:@"/search"]) { - NSString *query = params[@"q"]; - if (query) { - SearchViewController *searchVC = - [[SearchViewController alloc] - initWithSearchQuery:query]; - [navigationController pushViewController:searchVC - animated:YES]; - } + NSString *query = params[@"q"]; + if (query) { + SearchViewController *searchVC = + [[SearchViewController alloc] initWithSearchQuery:query]; + [self navigateToViewController:searchVC]; + } } else { - NSLog(@"Unhandled deep link path: %@", path); + NSLog(@"Unhandled deep link path: %@", path); } - }); - }]; + }); + }]; } -+ (UINavigationController *)getNavigationController { - // For UIKit apps using AppDelegate lifecycle, get the navigation - // controller: This allows us to navigate to the appropriate view - // controller based on the deep link. - UINavigationController *navigationController = nil; - for (UIWindow *window in UIApplication.sharedApplication.windows) { - if (window.isKeyWindow) { - navigationController = - (UINavigationController *)window.rootViewController; - break; - } - } - if (!navigationController) { - return nil; - } - - // For UIKit apps using SceneDelegate lifecycle, use this instead to - // get the navigation controller: - /* - for (UIWindowScene *scene in - UIApplication.sharedApplication.connectedScenes) { - id sceneDelegate = - (id)scene.delegate; - if ([sceneDelegate isKindOfClass:[SceneDelegate class]]) { - SceneDelegate *sceneDel = (SceneDelegate *)sceneDelegate; - navigationController = - (UINavigationController *)sceneDel.window.rootViewController; - break; - } - } - if (!navigationController) { - return nil; - } - */ - - return navigationController; ++ (void)navigateToViewController:(UIViewController *)viewController { + // Implement your navigation logic here + // For example: + // UINavigationController *navigationController = + // [self getNavigationController]; + // [navigationController pushViewController:viewController + // animated:YES]; } @end @@ -712,9 +587,6 @@ import Foundation import UIKit class SceneDelegate: UIResponder, UIWindowSceneDelegate { - - var window: UIWindow? - // Receive universal link or app scheme deep link // when app is installed and user clicks link to // open app from "not running" state or to create new scene. @@ -723,16 +595,6 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions ) { - // Example: configure the scene's window - // and set up the initial view hierarchy - guard let windowScene = (scene as? UIWindowScene) else { return } - window = UIWindow(windowScene: windowScene) - let rootViewController = OnboardingViewController() - let navigationController = UINavigationController( - rootViewController: rootViewController) - window?.rootViewController = navigationController - window?.makeKeyAndVisible() - if let urlContext = connectionOptions.urlContexts.first { let incomingLink = urlContext.url @@ -784,13 +646,7 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { #import #import -@class DeeplinkHandler; -@class OnboardingViewController; - @interface SceneDelegate : UIResponder - -@property(strong, nonatomic) UIWindow *window; - @end @implementation SceneDelegate @@ -801,18 +657,6 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { - (void)scene:(UIScene *)scene willConnectToSession:(UISceneSession *)session options:(UISceneConnectionOptions *)connectionOptions { - // Example: configure the scene's window - // and set up the initial view hierarchy - UIWindowScene *windowScene = (UIWindowScene *)scene; - self.window = [[UIWindow alloc] initWithWindowScene:windowScene]; - OnboardingViewController *rootViewController = - [[OnboardingViewController alloc] init]; - UINavigationController *navigationController = - [[UINavigationController alloc] - initWithRootViewController:rootViewController]; - self.window.rootViewController = navigationController; - [self.window makeKeyAndVisible]; - if (connectionOptions.URLContexts.count > 0) { UIOpenURLContext *urlContext = connectionOptions.URLContexts.allObjects.firstObject; From e9336de0e266f12723f7db00bbae5168199b4a01 Mon Sep 17 00:00:00 2001 From: Dave Mead Date: Mon, 21 Oct 2024 22:33:49 -0700 Subject: [PATCH 08/27] Refine SwiftUI examples --- .../deep-links/set-up-deep-linking.mdx | 190 ++++-------------- 1 file changed, 42 insertions(+), 148 deletions(-) diff --git a/src/content/docs/sdk/ios/v4/features/deep-links/set-up-deep-linking.mdx b/src/content/docs/sdk/ios/v4/features/deep-links/set-up-deep-linking.mdx index b4ed8b0ca..e800da19f 100644 --- a/src/content/docs/sdk/ios/v4/features/deep-links/set-up-deep-linking.mdx +++ b/src/content/docs/sdk/ios/v4/features/deep-links/set-up-deep-linking.mdx @@ -35,7 +35,7 @@ Update your AppDelegate to implement direct and deferred deep linking. -```swift {40-41, 58-117} +```swift {37-38, 46-99} import Adjust import Foundation import UIKit @@ -145,7 +145,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, -```objc {37-38, 58-115} +```objc {35-36, 44-91} #import #import #import @@ -460,7 +460,7 @@ class DeeplinkHandler { case "/search": if let query = params["q"] { let searchVC = SearchViewController(searchQuery: query) - navigateTo(viewController: productVC) + navigateTo(viewController: searchVC) } default: print("Unhandled deep link path: \(path)") @@ -470,8 +470,8 @@ class DeeplinkHandler { } private static func navigateTo(viewController: UIViewController) { - // Implement your navigation logic here - // For example: + // Implement navigation logic here + // For example, use the following: // navigationController.pushViewController(viewController, animated: true) } } @@ -709,7 +709,7 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { ## SwiftUI apps not using SceneDelegate lifecycle -Create an `AppDelegate.swift` file in your project's main directory and reference it in your main application file (for example: `App.swift` as shown below). This is required to handle app lifecycle events and Adjust SDK integration. +If you haven't already done so, create an `AppDelegate.swift` file in your project's main directory and reference it in your main application file (for example: `App.swift` as shown below). This is required to handle app lifecycle events and Adjust SDK integration. @@ -735,34 +735,28 @@ struct MyApp: App { -Update your AppDelegate to implement direct deep linking and deferred deep linking. +Update your AppDelegate to implement direct and deferred deep linking. ```swift -// AppDelegate.swift - import Adjust +import Foundation import UIKit -@UIApplicationMain -class AppDelegate: UIResponder, UIApplicationDelegate, - UNUserNotificationCenterDelegate, AdjustDelegate { - +class AppDelegate: UIResponder, UIApplicationDelegate, AdjustDelegate { let deeplinkManager = DeeplinkManager.shared func application( _ application: UIApplication, - didFinishLaunchingWithOptions launchOptions: [UIApplication - .LaunchOptionsKey: Any]?) -> Bool { - // Set delegate for push notifications - UNUserNotificationCenter.current().delegate = self - + didFinishLaunchingWithOptions launchOptions: + [UIApplication.LaunchOptionsKey: Any]? + ) -> Bool { // Configure Adjust SDK // Replace {YourAppToken} with your Adjust app token let appToken = "{YourAppToken}" - let adjustConfig: ADJConfig? + var adjustConfig: ADJConfig? #if DEBUG adjustConfig = ADJConfig( @@ -777,90 +771,52 @@ class AppDelegate: UIResponder, UIApplicationDelegate, adjustConfig?.logLevel = ADJLogLevelSuppress #endif - // Wait up to 30 seconds after app open for user to respond to ATT - // before sending install session to Adjust backend. - adjustConfig.attConsentWaitingInterval = 30 + // Wait up to 120 seconds after app open for user to respond to ATT + // before sending install session to Adjust's servers. + // Ensure this interval is long enough for user to respond. + adjustConfig?.attConsentWaitingInterval = 120 // Create delegate for deferred deep linking - adjustConfig.delegate = self + adjustConfig?.delegate = self // Initialize Adjust SDK Adjust.appDidLaunch(adjustConfig) - // Initialize and make window visible - let contentView = ContentView().environmentObject(deeplinkManager) - if let windowScene = UIApplication.shared.connectedScenes.first - as? UIWindowScene { - let window = UIWindow(windowScene: windowScene) - window.rootViewController = UIHostingController( - rootView: contentView) - self.window = window - window.makeKeyAndVisible() - } - return true } - // Receive push notification while app is in foreground - func userNotificationCenter( - _ center: UNUserNotificationCenter, - willPresent notification: UNNotification) async -> - UNNotificationPresentationOptions { - - // Add your push notification handling here - - // Change to your preferred presentation option - return [.alert, .sound] - } - - // Receive direct universal link or app scheme deep link - // from push notification while app isn't running, - // in background, or in foreground and user interacts - // with push notification. - // Replace "{deepLink}" with your custom key name. - func userNotificationCenter( - _ center: UNUserNotificationCenter, - didReceive response: UNNotificationResponse) async { - let userInfo = response.notification.request.content - .userInfo - - // Add your push notification handling here - - if let deepLinkURLString = userInfo["{deepLink}"] - as? String, - let incomingLink = URL( - string: deepLinkURLString) { - // Update DeeplinkManager with incoming deep link - self.deeplinkManager.updateDeeplinkState(with: incomingLink) - } - } - } - - // Receive direct universal link while app isn't running, - // or in background. + // Receive universal link when app is installed + // and user clicks link to open app + // from "not running" or background state. func application( _ application: UIApplication, continue userActivity: NSUserActivity, - restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool { + restorationHandler: + @escaping ([UIUserActivityRestoring]?) -> Void + ) -> Bool { if userActivity.activityType == NSUserActivityTypeBrowsingWeb, - let incomingLink = userActivity.webpageURL { + let incomingLink = userActivity.webpageURL + { // Update DeeplinkManager with incoming universal link - self.deeplinkManager.updateDeeplinkState(with: incomingLink) - } + deeplinkManager.updateDeeplink(to: incomingLink) } return true } - // Receive direct app scheme deep link while app isn't running, - // or in background. + // Receive deep link when app is installed + // and user clicks link to open app + // from "not running" or background state. + // + // Also receive universal link or app scheme deep link + // when app programmatically opens link using either + // UIApplication.shared.open or SwiftUI's openURL. func application( _ application: UIApplication, open incomingLink: URL, - options: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool { - - // Update DeeplinkManager with incoming app scheme deep link - self.deeplinkManager.updateDeeplinkState(with: incomingLink) - + options: [UIApplication.OpenURLOptionsKey: Any] = [:] + ) -> Bool { + // Update DeeplinkManager with incoming deep link + deeplinkManager.updateDeeplink(to: incomingLink) return true } @@ -871,10 +827,11 @@ class AppDelegate: UIResponder, UIApplicationDelegate, // onboarding screens and login. UserDefaults.standard.set( incomingLink.absoluteString, - forKey: "DeferredDeeplinkURL") + forKey: "lastDeferredLink") } - // Return true to try to open deep link immediately upon receipt. + // Return true to try to open deep link immediately + // upon receipt (for example: app has no ATT, onboarding screens, or login). // Otherwise, return false. return false } @@ -884,69 +841,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate, -Update your SceneDelegate to implement direct linking. - - - - -```swift -// SceneDelegate.swift - -import SwiftUI -import UIKit - -class SceneDelegate: UIResponder, UIWindowSceneDelegate, AdjustDelegate { - - var window: UIWindow? - let deeplinkManager = DeeplinkManager.shared - - // Receive direct universal link or app scheme deep link - // while app isn't running, or when new scene is created - func scene(_ scene: UIScene, - willConnectTo session: UISceneSession, - options connectionOptions: UIScene.ConnectionOptions) { - // Initialize and make window visible - let contentView = ContentView().environmentObject(deeplinkManager) - if let windowScene = scene as? UIWindowScene { - let window = UIWindow(windowScene: windowScene) - window.rootViewController = UIHostingController(rootView: contentView) - self.window = window - window.makeKeyAndVisible() - } - - if let urlContext = connectionOptions.urlContexts.first { - let incomingLink = urlContext.url - - // Update DeeplinkManager with incoming deep link - deeplinkManager.updateDeeplinkState(with: incomingLink) - } - } - - // Receive direct universal links while app is in background - func scene(_ scene: UIScene, continue userActivity: NSUserActivity) { - if userActivity.activityType == NSUserActivityTypeBrowsingWeb, - let incomingLink = userActivity.webpageURL { - // Update DeeplinkManager with incoming universal link - deeplinkManager.updateDeeplinkState(with: incomingLink) - } - } - - // Receive direct app scheme deep link in existing scene - // while app is in background. - func scene(_ scene: UIScene, openURLContexts URLContexts: Set) { - if let urlContext = URLContexts.first { - let incomingLink = urlContext.url - - // Update DeeplinkManager with incoming app scheme deep link - deeplinkManager.updateDeeplinkState(with: incomingLink) - } - } -} -``` - - - - When your AppDelegate receives a direct deep link, your SwiftUI views have to observe the updated deep link state. To facilitate this, you can create a centralized class to manage deep link states in your app, such as the below `DeeplinkManager.swift` implementation that the above example invokes. @@ -1042,7 +936,7 @@ struct ContentView: View { return } - // Handle resolved deep link + // Handle processed deep link DeeplinkHandler.shared.handleDeeplink(processedLink) } From fa556253e777a8f1734d1d3c288c145fe72629ef Mon Sep 17 00:00:00 2001 From: Dave Mead Date: Tue, 12 Nov 2024 22:12:58 -0800 Subject: [PATCH 09/27] Update comments --- .../deep-links/set-up-deep-linking.mdx | 378 ++++++++++++------ 1 file changed, 247 insertions(+), 131 deletions(-) diff --git a/src/content/docs/sdk/ios/v4/features/deep-links/set-up-deep-linking.mdx b/src/content/docs/sdk/ios/v4/features/deep-links/set-up-deep-linking.mdx index e800da19f..fc9c99136 100644 --- a/src/content/docs/sdk/ios/v4/features/deep-links/set-up-deep-linking.mdx +++ b/src/content/docs/sdk/ios/v4/features/deep-links/set-up-deep-linking.mdx @@ -22,7 +22,7 @@ Please follow the steps in the section that corresponds to the type of app you h -If you're using the Facebook SDK for deferred deep linking, disable or remove the [deferred deep linking code](https://developers.facebook.com/docs/ios/deep-linking#deferred-deep-linking) to avoid conflicts with Adjust SDK's deferred deep link handling. +If you're using the Facebook SDK for deferred deep linking, disable or remove its [deferred deep linking code](https://developers.facebook.com/docs/ios/deep-linking#deferred-deep-linking) to avoid conflicts with Adjust SDK's deferred deep link handling. @@ -81,10 +81,10 @@ class AppDelegate: UIResponder, UIApplicationDelegate, return true } - // Receive universal link when app is installed - // and user clicks link to open app - // from one of these states: - // not running, background, or foreground. + // Receive universal link when app is installed, + // and app is in one of these states: + // not running, background, or foreground, + // and user clicks link to open app. func application( _ application: UIApplication, continue userActivity: NSUserActivity, @@ -100,14 +100,14 @@ class AppDelegate: UIResponder, UIApplicationDelegate, return true } - // Receive app scheme deep link when app is installed - // and user clicks link to open app - // from one of these states: - // not running, background, or foreground. + // Receive app scheme deep link when app is installed, + // and app is in one of these states: + // not running, background, or foreground, + // and link opens app when either: // - // Also receive universal link or app scheme deep link - // when app programmatically opens link - // using UIApplication.shared.open. + // - User clicks link, or + // - Your app/another app programmatically opens link + // using UIApplication.shared.open. func application( _ application: UIApplication, open incomingLink: URL, @@ -189,10 +189,10 @@ class AppDelegate: UIResponder, UIApplicationDelegate, return YES; } -// Receive universal link when app is installed -// and user clicks link to open app -// from one of these states: -// not running, background, or foreground. +// Receive universal link when app is installed, +// and app is in one of these states: +// not running, background, or foreground, +// and user clicks link to open app. - (BOOL)application:(UIApplication *)application continueUserActivity:(NSUserActivity *)userActivity restorationHandler: @@ -207,14 +207,14 @@ class AppDelegate: UIResponder, UIApplicationDelegate, return YES; } -// Receive app scheme deep link when app is installed -// and user clicks link to open app -// from one of these states: -// not running, background, or foreground. +// Receive app scheme deep link when app is installed, +// and app is in one of these states: +// not running, background, or foreground, +// and link opens app when either: // -// Also receive universal link or app scheme deep link -// when app programmatically opens link -// using [[UIApplication sharedApplication] openURL:...]. +// - User clicks link, or +// - Your app/another app programmatically opens link +// using [[UIApplication sharedApplication] openURL:]. - (BOOL)application:(UIApplication *)app openURL:(NSURL *)incomingLink options: @@ -297,7 +297,8 @@ class OnboardingViewController: UIViewController { forKey: "lastDeferredLink"), let deferredLink = URL(string: deferredLinkString) { - // Remove the stored URL to avoid handling it again later + // Remove the stored deferred deep link + // to avoid handling it again later UserDefaults.standard.removeObject(forKey: "lastDeferredLink") // Handle deferred deep link @@ -365,7 +366,8 @@ class OnboardingViewController: UIViewController { NSURL *deferredLink = [NSURL URLWithString:deferredLinkString]; if (deferredLink) { - // Remove the stored URL to avoid handling it again later + // Remove the stored deferred deep link + // to avoid handling it again later [[NSUserDefaults standardUserDefaults] removeObjectForKey:@"lastDeferredLink"]; @@ -396,8 +398,8 @@ The class performs the following tasks: 1. The class uses Adjust's `processDeeplink` method, which sends the deep link to Adjust's servers to accomplish two things: -- Record the deep link for attribution. -- Resolve [Adjust short branded link](https://www.help.adjust.com/en/article/short-branded-links) and respond with full URL, if applicable. +- Record the deep link click for attribution purposes. +- If the deep link is an [Adjust short branded link](https://www.help.adjust.com/en/article/short-branded-links), respond with the corresponding full URL. Otherwise, respond with the original link. 2. After processing, the class parses the link and navigates to the appropriate screen. This part of the code is specific to each app. Your app has to implement its own logic for handling deep links and opening the corresponding content. Your deep link handling has to meet the following key requirements: @@ -421,14 +423,15 @@ import UIKit class DeeplinkHandler { static func handleDeeplink(_ incomingLink: URL) { // Send incoming deep link to Adjust's servers for attribution - // and to resolve short link, if applicable. + // and retrieve full URL if short branded link. + // If not, return original link. Adjust.processDeeplink(incomingLink) { processedLinkString in guard let processedLink = URL(string: processedLinkString) else { print("Invalid processed link") return } - // Extract path, query items, and fragment from the resolved link. + // Extract path, query items, and fragment from the processed link. let components = URLComponents( url: processedLink, resolvingAgainstBaseURL: true) let path = components?.path ?? "" @@ -497,7 +500,8 @@ class DeeplinkHandler { + (void)handleDeeplink:(NSURL *)incomingLink { // Send incoming deep link to Adjust's servers for attribution - // and to resolve short link, if applicable. + // and retrieve full URL if short branded link. + // If not, return original link. [Adjust processDeeplink:incomingLink completionHandler:^(NSString *_Nonnull processedLinkString) { NSURL *processedLink = [NSURL URLWithString:processedLinkString]; @@ -506,7 +510,7 @@ class DeeplinkHandler { return; } - // Extract path, query items, and fragment from the resolved link. + // Extract path, query items, and fragment from the processed link. NSURLComponents *components = [NSURLComponents componentsWithURL:processedLink resolvingAgainstBaseURL:YES]; @@ -587,9 +591,15 @@ import Foundation import UIKit class SceneDelegate: UIResponder, UIWindowSceneDelegate { - // Receive universal link or app scheme deep link - // when app is installed and user clicks link to - // open app from "not running" state or to create new scene. + // Receive link when creating new scene, when either: + // - Launching app from "not running" state, or + // - Creating additional scene while app is + // in background or foreground state. + // + // Receive universal link when user clicks link. + // Receive app scheme deep link when user clicks link or + // your app/another app programmatically opens link + // using UIApplication.shared.open. func scene( _ scene: UIScene, willConnectTo session: UISceneSession, @@ -603,9 +613,9 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { } } - // Receive universal link when app is installed - // and user clicks link to open app to existing scene - // from background or foreground state. + // Receive universal link when app is installed, + // and app is in background or foreground state, + // and user clicks link to open app to existing scene. func scene(_ scene: UIScene, continue userActivity: NSUserActivity) { if userActivity.activityType == NSUserActivityTypeBrowsingWeb, let incomingLink = userActivity.webpageURL @@ -615,13 +625,12 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { } } - // Receive app scheme deep link when app is installed - // and user clicks link to open app to existing scene - // from background or foreground state. - // - // Also receive universal link or app scheme deep link - // when app programmatically opens link - // using UIApplication.shared.open. + // Receive app scheme deep link when app is installed, + // and app is in background or foreground state, + // and link opens app to existing scene when either: + // - User clicks link, or + // - Your app/another app programmatically opens link + // using UIApplication.shared.open. func scene( _ scene: UIScene, openURLContexts URLContexts: Set ) { @@ -651,9 +660,15 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { @implementation SceneDelegate -// Receive universal link or app scheme deep link -// when app is installed and user clicks link to -// open app from "not running" state or to create new scene. +// Receive link when creating new scene, when either: +// - Launching app from "not running" state, or +// - Creating additional scene while app is +// in background or foreground state. +// +// Receive universal link when user clicks link. +// Receive app scheme deep link when user clicks link or +// your app/another app programmatically opens link +// using [[UIApplication sharedApplication] openURL:]. - (void)scene:(UIScene *)scene willConnectToSession:(UISceneSession *)session options:(UISceneConnectionOptions *)connectionOptions { @@ -667,9 +682,9 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { } } -// Receive universal link when app is installed -// and user clicks link to open app to existing scene -// from background or foreground state. +// Receive universal link when app is installed, +// and app is in background or foreground state, +// and user clicks link to open app to existing scene. - (void)scene:(UIScene *)scene continueUserActivity:(NSUserActivity *)userActivity { if ([userActivity.activityType @@ -681,13 +696,12 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { } } -// Receive app scheme deep link when app is installed -// and user clicks link to open app to existing scene -// from background or foreground state. -// -// Also receive universal link or app scheme deep link -// when app programmatically opens link -// using [[UIApplication sharedApplication] openURL:...]. +// Receive app scheme deep link when app is installed, +// and app is in background or foreground state, +// and link opens app to existing scene when either: +// - User clicks link, or +// - Your app/another app programmatically opens link +// using [[UIApplication sharedApplication] openURL:]. - (void)scene:(UIScene *)scene openURLContexts:(NSSet *)URLContexts { UIOpenURLContext *urlContext = URLContexts.allObjects.firstObject; @@ -714,9 +728,9 @@ If you haven't already done so, create an `AppDelegate.swift` file in your proje -```swift -// App.swift + +```swift import SwiftUI @main @@ -725,13 +739,15 @@ struct MyApp: App { var body: some Scene { WindowGroup { - ContentView() + OnboardingView() .environmentObject(DeeplinkManager.shared) } } } ``` + + @@ -785,9 +801,9 @@ class AppDelegate: UIResponder, UIApplicationDelegate, AdjustDelegate { return true } - // Receive universal link when app is installed - // and user clicks link to open app - // from "not running" or background state. + // Receive universal link when app is installed, + // and app is in "not running" or background state, + // and user clicks link to open app. func application( _ application: UIApplication, continue userActivity: NSUserActivity, @@ -803,13 +819,13 @@ class AppDelegate: UIResponder, UIApplicationDelegate, AdjustDelegate { return true } - // Receive deep link when app is installed - // and user clicks link to open app - // from "not running" or background state. + // Receive app scheme deep link when app is installed, + // and app is in "not running" or background state, + // and link opens app when either: // - // Also receive universal link or app scheme deep link - // when app programmatically opens link using either - // UIApplication.shared.open or SwiftUI's openURL. + // - User clicks link, or + // - Your app/another app programmatically opens link + // using UIApplication.shared.open or SwiftUI's openURL. func application( _ application: UIApplication, open incomingLink: URL, @@ -841,128 +857,228 @@ class AppDelegate: UIResponder, UIApplicationDelegate, AdjustDelegate {
-When your AppDelegate receives a direct deep link, your SwiftUI views have to observe the updated deep link state. To facilitate this, you can create a centralized class to manage deep link states in your app, such as the below `DeeplinkManager.swift` implementation that the above example invokes. +SwiftUI views don't directly receive deep links from the AppDelegate. The `DeeplinkManager.swift` example class (referenced in preceding examples) publishes deep link changes to your SwiftUI views. This example class is shown below. -```swift -// DeeplinkManager.swift + +```swift import SwiftUI class DeeplinkManager: ObservableObject { - static let shared = DeeplinkManager() - @Published var deeplink: URL? + static let shared = DeeplinkManager() + @Published var deeplink: URL? - // Update current deep link state - func updateDeeplinkState(with url: URL) { - DispatchQueue.main.async { - self.deeplink = url - } - } + private init() {} - // Remove current deep link - func RemoveDeeplink() { - DispatchQueue.main.async { - self.deeplink = nil + func updateDeeplink(to incomingLink: URL?) { + DispatchQueue.main.async { + self.deeplink = incomingLink + } } - } } ``` + + -Your SwiftUI views have to react to changes in the direct deep link state and open the deep link content. Below is an example `ContentView.swift` implementation that does this. +Below is an example `OnboardingView.swift` implementation that does the following: -On first app open, if your app displays onboarding screens and/or prompts the user to log in, afterward your app has to retrieve and handle the stored deferred deep link (for example: in `ContentView.swift` as shown below). If your app doesn't display onboarding screens or prompt the user to log in, and you want the Adjust SDK to open the deferred deep link immediately on first app open, then you can skip the deferred deep linking logic shown below. +- Handles onboarding flow and deferred deep linking. +- Receives deep links from the AppDelegate implementation via DeepLinkManager. +- Receives deep links when app is in the foreground. -The below code also shows the ATT prompt, which may or may not be applicable for your app's onboarding process. After the user responds to the ATT prompt (or the ATT waiting interval expires, whichever comes first), the SDK sends the /session and /attribution requests to Adjust's servers. Adjust's servers then respond with the deferred deep link in the /attribution response. +For deferred deep linking, this is the sequence for most apps: + +1. A user who doesn't have the app installed clicks an Adjust deep link, which redirects them to the app store. +2. The user installs and opens the app. +3. The app begins its onboarding process (for example: ATT prompt, onboarding screens, login prompt). +4. After the ATT prompt response or timeout, the Adjust SDK sends session and attribution requests to Adjust's servers. +5. Adjust's servers respond with attribution data, including the deep link the user clicked on ("deferred deep link"). +6. The Adjust SDK triggers a deferred deep link callback in your app, which stores the deferred deep link (shown in the AppDelegate implementation above). +7. Once onboarding completes, the app retrieves the stored deep link and navigates the user accordingly (shown in the OnboardingView example class below). -```swift -// ContentView.swift + -import AppTrackingTransparency +```swift import SwiftUI -struct ContentView: View { +struct OnboardingView: View { @EnvironmentObject var deeplinkManager: DeeplinkManager - @State private var hasCompletedOnboarding: Bool = false + @State private var hasCompletedOnboarding: Bool = UserDefaults.standard.bool( + forKey: "HasCompletedOnboarding") var body: some View { - NavigationView { + VStack { // Check if onboarding has been completed if !hasCompletedOnboarding { - // Show ATT prompt - ATTrackingManager.requestTrackingAuthorization { _ in } + // Show ATT dialog + Adjust.requestTrackingAuthorization(completionHandler: nil) - // Show onboarding screens and user login here + // Show onboarding screens and login prompt // On completion, set hasCompletedOnboarding to true + UserDefaults.standard.set(true, forKey: "HasCompletedOnboarding") hasCompletedOnboarding = true } // Check if there's a stored deferred deep link if let deferredLinkString = UserDefaults.standard.string( - forKey: "DeferredDeeplinkURL"), - let deferredLink = URL(string: deferredLinkString) { + forKey: "lastDeferredLink"), + let deferredLink = URL(string: deferredLinkString) + { + // Remove the stored deferred deep link + // to avoid handling it again later + UserDefaults.standard.removeObject(forKey: "lastDeferredLink") // Handle deferred deep link - DeeplinkHandler.shared.handleDeeplink(deferredLink) + DeeplinkHandler.handleDeeplink(deferredLink) - // Remove stored URL to avoid handling it again later - UserDefaults.standard.removeObject( - forKey: "DeferredDeeplinkURL") } else { // Show main content } } - // Receive direct universal link or app scheme deep link - // from deeplinkManager. + // Receive link when app is in foreground state. + // + // Receive universal link when user clicks link. + // Receive app scheme deep link when user clicks link or + // your app/another app programmatically opens link + // using UIApplication.shared.open. + .onOpenURL { incomingLink in + DeeplinkHandler.handleDeeplink(incomingLink) + } + + // Receive universal link or app scheme deep link + // from DeeplinkManager. .onReceive(deeplinkManager.$deeplink) { deeplink in if let incomingLink = deeplink { - // Send incoming deep link to Adjust for attribution, - // and resolve short link, if applicable. - Adjust.processDeeplink(incomingLink) { processedLinkString in - guard - let processedLink = URL(string: processedLinkString ?? "") - else { - print("Invalid processed link") - return - } - - // Handle processed deep link - DeeplinkHandler.shared.handleDeeplink(processedLink) - } - // Remove deep link to avoid handling it again later - deeplinkManager.RemoveDeeplink() + deeplinkManager.updateDeeplink(to: nil) + + // Handle deep link + DeeplinkHandler.handleDeeplink(incomingLink) } } + } +} +``` - // Receive direct universal link or app scheme deep link - // while app is in foreground. - .onOpenURL { incomingLink in - Adjust.processDeeplink(incomingLink) { processedLinkString in - guard let processedLink = URL(string: processedLinkString ?? "") - else { - print("Invalid processed link") - return - } + + + + + +The preceding code examples use an example DeeplinkHandler class. This example class is shown below and handles all types of links: + +- Adjust branded links (full go.link links) +- Adjust short branded links (short go.link links) +- Adjust universal links (adj.st links) +- Non-Adjust universal links (example.com links) +- App scheme deep links (example:// links) + +The class performs the following tasks: + +1. The class uses Adjust's `processDeeplink` method, which sends the deep link to Adjust's servers to accomplish two things: + +- Record the deep link click for attribution purposes. +- If the deep link is an [Adjust short branded link](https://www.help.adjust.com/en/article/short-branded-links), respond with the corresponding full URL. Otherwise, respond with the original link. - // Handle resolved deep link - DeeplinkHandler.shared.handleDeeplink(processedLink) +2. After processing, the class parses the link and navigates to the appropriate screen. This part of the code is specific to each app. Your app has to implement its own logic for handling deep links and opening the corresponding content. Your deep link handling has to meet the following key requirements: + +- Your app should treat Adjust branded links the same as other universal links, such as your own. Adjust recommends implementing domain-agnostic deep link handling logic to meet this requirement. For example, the following links should navigate to the same screen in your app: + - Adjust branded link: `https://example.go.link/summer-clothes?promo=beach` + - Your universal link: `https://www.example.com/summer-clothes?promo=beach` +- In cases where iOS doesn't support universal links, Adjust automatically converts them to app scheme deep links. Additionally, Adjust's servers convert all deferred deep links to app scheme deep link format. Therefore it's crucial for the app to handle universal links and app scheme deep links equivalently. For example, the following links should navigate to the same screen in your app: + - Adjust branded link: `https://example.go.link/summer-clothes?promo=beach` + - App scheme deep link: `example://summer-clothes?promo=beach` + + + + + + +```swift +import Adjust +import Foundation +import SwiftUI + +class DeeplinkHandler { + static func handleDeeplink(_ incomingLink: URL) { + // Send incoming deep link to Adjust's servers for attribution + // and to resolve short link, if applicable. + Adjust.processDeeplink(incomingLink) { processedLinkString in + guard let processedLink = URL(string: processedLinkString) else { + print("Invalid processed link") + return + } + + // Extract path, query items, and fragment from the resolved link. + let components = URLComponents( + url: processedLink, resolvingAgainstBaseURL: true) + let path = components?.path ?? "" + let queryItems = components?.queryItems ?? [] + + // Parse query parameters into a dictionary for easier access + let params = queryItems.reduce(into: [String: String]()) { result, item in + result[item.name] = item.value + } + + let fragment = components?.fragment + + // Implement the navigation or other app-specific logic based on + // the deep link components. + DispatchQueue.main.async { + // Example of navigating based on the path or other components. + // Replace with your actual navigation logic. + switch path { + case "/product": + if let productId = params["id"] { + let productView = ProductView(productId: productId) + navigateTo(view: productView) + } + case "/category": + if let categoryName = params["name"] { + let categoryView = CategoryView(category: categoryName) + navigateTo(view: categoryView) + } + case "/search": + if let query = params["q"] { + let searchView = SearchView(searchQuery: query) + navigateTo(view: searchView) + } + default: + print("Unhandled deep link path: \(path)") + } } } } + + private static func navigateTo(view: T) { + // Implement navigation logic + // using one of these common patterns, for example: + // 1. Using NavigationPath: + // navigationPath.append(view) + // + // 2. Using navigation state: + // isProductViewActive = true + // selectedProductId = productId + // + // 3. Using custom router: + // router.navigate(to: view) + } } ``` + + From 861e768d322e03312deb6db1498623399db1fa1e Mon Sep 17 00:00:00 2001 From: Dave Mead Date: Thu, 14 Nov 2024 23:57:44 -0800 Subject: [PATCH 10/27] Update SwiftUI implementations --- .../deep-links/set-up-deep-linking.mdx | 774 ++++++------------ 1 file changed, 249 insertions(+), 525 deletions(-) diff --git a/src/content/docs/sdk/ios/v4/features/deep-links/set-up-deep-linking.mdx b/src/content/docs/sdk/ios/v4/features/deep-links/set-up-deep-linking.mdx index fc9c99136..59b1b0db1 100644 --- a/src/content/docs/sdk/ios/v4/features/deep-links/set-up-deep-linking.mdx +++ b/src/content/docs/sdk/ios/v4/features/deep-links/set-up-deep-linking.mdx @@ -20,6 +20,10 @@ Please follow the steps in the section that corresponds to the type of app you h - [SwiftUI apps not using SceneDelegate lifecycle](#swiftui-apps-not-using-scenedelegate-lifecycle) - [SwiftUI apps using SceneDelegate lifecycle](#swiftui-apps-using-scenedelegate-lifecycle) +In addition, implement deep link handling logic: + +- [Deep Link Handler](#deep-link-handler) + If you're using the Facebook SDK for deferred deep linking, disable or remove its [deferred deep linking code](https://developers.facebook.com/docs/ios/deep-linking#deferred-deep-linking) to avoid conflicts with Adjust SDK's deferred deep link handling. @@ -35,7 +39,7 @@ Update your AppDelegate to implement direct and deferred deep linking. -```swift {37-38, 46-99} +```swift import Adjust import Foundation import UIKit @@ -113,10 +117,8 @@ class AppDelegate: UIResponder, UIApplicationDelegate, open incomingLink: URL, options: [UIApplication.OpenURLOptionsKey: Any] = [:] ) -> Bool { - { // Handle incoming deep link DeeplinkHandler.handleDeeplink(incomingLink) - } return true } @@ -145,7 +147,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, -```objc {35-36, 44-91} +```objc #import #import #import @@ -254,19 +256,19 @@ For deferred deep linking, this is the sequence for most apps: 4. After the ATT prompt response or timeout, the Adjust SDK sends session and attribution requests to Adjust's servers. 5. Adjust's servers respond with attribution data, including the deep link the user clicked on ("deferred deep link"). 6. The Adjust SDK triggers a deferred deep link callback in your app, which stores the deferred deep link (shown in the AppDelegate implementation above). -7. Once onboarding completes, the app retrieves the stored deep link and navigates the user accordingly (shown in the OnboardingViewController example class below). +7. Once onboarding completes, the app retrieves the stored deep link and navigates the user accordingly (shown in the ContentViewController example class below). - + ```swift import Adjust import Foundation import UIKit -class OnboardingViewController: UIViewController { +class ContentViewController: UIViewController { var hasCompletedOnboarding: Bool { get { @@ -316,7 +318,7 @@ class OnboardingViewController: UIViewController { - + ```objc #import @@ -325,17 +327,17 @@ class OnboardingViewController: UIViewController { @class DeeplinkHandler; -@interface OnboardingViewController : UIViewController +@interface ContentViewController : UIViewController @end -@interface OnboardingViewController () +@interface ContentViewController () @property(nonatomic, assign) BOOL hasCompletedOnboarding; @end -@implementation OnboardingViewController +@implementation ContentViewController - (BOOL)hasCompletedOnboarding { return [[NSUserDefaults standardUserDefaults] @@ -386,196 +388,9 @@ class OnboardingViewController: UIViewController { -The preceding code examples use an example DeeplinkHandler class. This example class is shown below and handles all types of links: - -- Adjust branded links (full go.link links) -- Adjust short branded links (short go.link links) -- Adjust universal links (adj.st links) -- Non-Adjust universal links (example.com links) -- App scheme deep links (example:// links) - -The class performs the following tasks: - -1. The class uses Adjust's `processDeeplink` method, which sends the deep link to Adjust's servers to accomplish two things: - -- Record the deep link click for attribution purposes. -- If the deep link is an [Adjust short branded link](https://www.help.adjust.com/en/article/short-branded-links), respond with the corresponding full URL. Otherwise, respond with the original link. - -2. After processing, the class parses the link and navigates to the appropriate screen. This part of the code is specific to each app. Your app has to implement its own logic for handling deep links and opening the corresponding content. Your deep link handling has to meet the following key requirements: - -- Your app should treat Adjust branded links the same as other universal links, such as your own. Adjust recommends implementing domain-agnostic deep link handling logic to meet this requirement. For example, the following links should navigate to the same screen in your app: - - Adjust branded link: `https://example.go.link/summer-clothes?promo=beach` - - Your universal link: `https://www.example.com/summer-clothes?promo=beach` -- In cases where iOS doesn't support universal links, Adjust automatically converts them to app scheme deep links. Additionally, Adjust's servers convert all deferred deep links to app scheme deep link format. Therefore it's crucial for the app to handle universal links and app scheme deep links equivalently. For example, the following links should navigate to the same screen in your app: - - Adjust branded link: `https://example.go.link/summer-clothes?promo=beach` - - App scheme deep link: `example://summer-clothes?promo=beach` - - - - - - -```swift -import Adjust -import Foundation -import UIKit +Lastly, implement your deep link handling logic: -class DeeplinkHandler { - static func handleDeeplink(_ incomingLink: URL) { - // Send incoming deep link to Adjust's servers for attribution - // and retrieve full URL if short branded link. - // If not, return original link. - Adjust.processDeeplink(incomingLink) { processedLinkString in - guard let processedLink = URL(string: processedLinkString) else { - print("Invalid processed link") - return - } - - // Extract path, query items, and fragment from the processed link. - let components = URLComponents( - url: processedLink, resolvingAgainstBaseURL: true) - let path = components?.path ?? "" - let queryItems = components?.queryItems ?? [] - - // Parse query parameters into a dictionary for easier access - let params = queryItems.reduce(into: [String: String]()) { result, item in - result[item.name] = item.value - } - - let fragment = components?.fragment - - // Implement the navigation or other app-specific logic based on - // the deep link components. - DispatchQueue.main.async { - // Example of navigating based on the path or other components. - // Replace with your actual navigation logic. - switch path { - case "/product": - if let productId = params["id"] { - let productVC = ProductViewController(productId: productId) - navigateTo(viewController: productVC) - } - case "/category": - if let categoryName = params["name"] { - let categoryVC = CategoryViewController(category: categoryName) - navigateTo(viewController: categoryVC) - } - case "/search": - if let query = params["q"] { - let searchVC = SearchViewController(searchQuery: query) - navigateTo(viewController: searchVC) - } - default: - print("Unhandled deep link path: \(path)") - } - } - } - } - - private static func navigateTo(viewController: UIViewController) { - // Implement navigation logic here - // For example, use the following: - // navigationController.pushViewController(viewController, animated: true) - } -} -``` - - - - - - - - -```objc -#import -#import -#import - -@interface DeeplinkHandler : NSObject -+ (void)handleDeeplink:(NSURL *)incomingLink; -@end - -@implementation DeeplinkHandler - -+ (void)handleDeeplink:(NSURL *)incomingLink { - // Send incoming deep link to Adjust's servers for attribution - // and retrieve full URL if short branded link. - // If not, return original link. - [Adjust processDeeplink:incomingLink - completionHandler:^(NSString *_Nonnull processedLinkString) { - NSURL *processedLink = [NSURL URLWithString:processedLinkString]; - if (!processedLink) { - NSLog(@"Invalid processed link"); - return; - } - - // Extract path, query items, and fragment from the processed link. - NSURLComponents *components = - [NSURLComponents componentsWithURL:processedLink - resolvingAgainstBaseURL:YES]; - NSString *path = components.path ?: @""; - NSArray *queryItems = components.queryItems ?: @[]; - - // Parse query parameters into a dictionary for easier access - NSMutableDictionary *params = - [NSMutableDictionary dictionary]; - for (NSURLQueryItem *item in queryItems) { - params[item.name] = item.value; - } - NSString *fragment = components.fragment; - - // Implement the navigation or other app-specific logic based on - // the deep link components. - dispatch_async(dispatch_get_main_queue(), ^{ - // Example of navigating based on the path or other components. - // Replace with your actual navigation logic. - // Handle paths, query items, and fragments as needed. - if ([path isEqualToString:@"/product"]) { - NSString *productId = params[@"id"]; - if (productId) { - ProductViewController *productVC = - [[ProductViewController alloc] initWithProductId:productId]; - [self navigateToViewController:productVC]; - } - } else if ([path isEqualToString:@"/category"]) { - NSString *categoryName = params[@"name"]; - if (categoryName) { - CategoryViewController *categoryVC = - [[CategoryViewController alloc] - initWithCategory:categoryName]; - [self navigateToViewController:categoryVC]; - } - } else if ([path isEqualToString:@"/search"]) { - NSString *query = params[@"q"]; - if (query) { - SearchViewController *searchVC = - [[SearchViewController alloc] initWithSearchQuery:query]; - [self navigateToViewController:searchVC]; - } - } else { - NSLog(@"Unhandled deep link path: %@", path); - } - }); - }]; -} - -+ (void)navigateToViewController:(UIViewController *)viewController { - // Implement your navigation logic here - // For example: - // UINavigationController *navigationController = - // [self getNavigationController]; - // [navigationController pushViewController:viewController - // animated:YES]; -} - -@end -``` - - - - - +[Deep Link Handler](#deep-link-handler) ## UIKit apps using SceneDelegate lifecycle @@ -591,7 +406,7 @@ import Foundation import UIKit class SceneDelegate: UIResponder, UIWindowSceneDelegate { - // Receive link when creating new scene, when either: + // Receive deep link when creating new scene, when either: // - Launching app from "not running" state, or // - Creating additional scene while app is // in background or foreground state. @@ -601,16 +416,21 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { // your app/another app programmatically opens link // using UIApplication.shared.open. func scene( - _ scene: UIScene, - willConnectTo session: UISceneSession, - options connectionOptions: UIScene.ConnectionOptions + _ scene: UIScene, + willConnectTo session: UISceneSession, + options connectionOptions: UIScene.ConnectionOptions ) { - if let urlContext = connectionOptions.urlContexts.first { - let incomingLink = urlContext.url - - // Handle incoming deep link - DeeplinkHandler.handleDeeplink(incomingLink) - } + // Handle incoming universal link + if let userActivity = connectionOptions.userActivities.first, + userActivity.activityType == NSUserActivityTypeBrowsingWeb, + let incomingLink = userActivity.webpageURL { + DeeplinkHandler.handleDeeplink(incomingLink) + } + // Handle incoming app scheme deep link + else if let urlContext = connectionOptions.urlContexts.first, + let incomingLink = urlContext.url { + DeeplinkHandler.handleDeeplink(incomingLink) + } } // Receive universal link when app is installed, @@ -660,7 +480,7 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { @implementation SceneDelegate -// Receive link when creating new scene, when either: +// Receive deep link when creating new scene, when either: // - Launching app from "not running" state, or // - Creating additional scene while app is // in background or foreground state. @@ -739,8 +559,7 @@ struct MyApp: App { var body: some Scene { WindowGroup { - OnboardingView() - .environmentObject(DeeplinkManager.shared) + ContentView() } } } @@ -751,19 +570,19 @@ struct MyApp: App {
-Update your AppDelegate to implement direct and deferred deep linking. +Update your AppDelegate to implement deferred deep linking. + + ```swift import Adjust import Foundation import UIKit class AppDelegate: UIResponder, UIApplicationDelegate, AdjustDelegate { - let deeplinkManager = DeeplinkManager.shared - func application( _ application: UIApplication, didFinishLaunchingWithOptions launchOptions: @@ -801,41 +620,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate, AdjustDelegate { return true } - // Receive universal link when app is installed, - // and app is in "not running" or background state, - // and user clicks link to open app. - func application( - _ application: UIApplication, - continue userActivity: NSUserActivity, - restorationHandler: - @escaping ([UIUserActivityRestoring]?) -> Void - ) -> Bool { - if userActivity.activityType == NSUserActivityTypeBrowsingWeb, - let incomingLink = userActivity.webpageURL - { - // Update DeeplinkManager with incoming universal link - deeplinkManager.updateDeeplink(to: incomingLink) - } - return true - } - - // Receive app scheme deep link when app is installed, - // and app is in "not running" or background state, - // and link opens app when either: - // - // - User clicks link, or - // - Your app/another app programmatically opens link - // using UIApplication.shared.open or SwiftUI's openURL. - func application( - _ application: UIApplication, - open incomingLink: URL, - options: [UIApplication.OpenURLOptionsKey: Any] = [:] - ) -> Bool { - // Update DeeplinkManager with incoming deep link - deeplinkManager.updateDeeplink(to: incomingLink) - return true - } - // Receive deferred deep link via AdjustDelegate method func adjustDeeplinkResponse(_ deeplink: URL?) -> Bool { if let incomingLink = deeplink { @@ -854,43 +638,15 @@ class AppDelegate: UIResponder, UIApplicationDelegate, AdjustDelegate { } ``` - - - -SwiftUI views don't directly receive deep links from the AppDelegate. The `DeeplinkManager.swift` example class (referenced in preceding examples) publishes deep link changes to your SwiftUI views. This example class is shown below. - - - - - - -```swift -import SwiftUI - -class DeeplinkManager: ObservableObject { - static let shared = DeeplinkManager() - @Published var deeplink: URL? - - private init() {} - - func updateDeeplink(to incomingLink: URL?) { - DispatchQueue.main.async { - self.deeplink = incomingLink - } - } -} -``` - -Below is an example `OnboardingView.swift` implementation that does the following: +Below is an example `ContentView.swift` implementation that does the following: +- Receives direct deep links. - Handles onboarding flow and deferred deep linking. -- Receives deep links from the AppDelegate implementation via DeepLinkManager. -- Receives deep links when app is in the foreground. For deferred deep linking, this is the sequence for most apps: @@ -900,18 +656,18 @@ For deferred deep linking, this is the sequence for most apps: 4. After the ATT prompt response or timeout, the Adjust SDK sends session and attribution requests to Adjust's servers. 5. Adjust's servers respond with attribution data, including the deep link the user clicked on ("deferred deep link"). 6. The Adjust SDK triggers a deferred deep link callback in your app, which stores the deferred deep link (shown in the AppDelegate implementation above). -7. Once onboarding completes, the app retrieves the stored deep link and navigates the user accordingly (shown in the OnboardingView example class below). +7. Once onboarding completes, the app retrieves the stored deep link and navigates the user accordingly (shown in the ContentView example class below). - + ```swift +import Adjust import SwiftUI -struct OnboardingView: View { - @EnvironmentObject var deeplinkManager: DeeplinkManager +struct ContentView: View { @State private var hasCompletedOnboarding: Bool = UserDefaults.standard.bool( forKey: "HasCompletedOnboarding") @@ -919,11 +675,11 @@ struct OnboardingView: View { VStack { // Check if onboarding has been completed if !hasCompletedOnboarding { - // Show ATT dialog - Adjust.requestTrackingAuthorization(completionHandler: nil) + .onAppear { + Adjust.requestTrackingAuthorization(completionHandler: nil) + } // Show onboarding screens and login prompt - // On completion, set hasCompletedOnboarding to true UserDefaults.standard.set(true, forKey: "HasCompletedOnboarding") hasCompletedOnboarding = true @@ -937,35 +693,75 @@ struct OnboardingView: View { // Remove the stored deferred deep link // to avoid handling it again later UserDefaults.standard.removeObject(forKey: "lastDeferredLink") - // Handle deferred deep link DeeplinkHandler.handleDeeplink(deferredLink) - } else { // Show main content } } - - // Receive link when app is in foreground state. + // Receive deep link when app is installed, + // and app is in one of these states: + // not running, background, or foreground. // // Receive universal link when user clicks link. // Receive app scheme deep link when user clicks link or // your app/another app programmatically opens link - // using UIApplication.shared.open. + // using UIApplication.shared.open or SwiftUI's openURL. .onOpenURL { incomingLink in DeeplinkHandler.handleDeeplink(incomingLink) } + } +} +``` + + - // Receive universal link or app scheme deep link - // from DeeplinkManager. - .onReceive(deeplinkManager.$deeplink) { deeplink in - if let incomingLink = deeplink { - // Remove deep link to avoid handling it again later - deeplinkManager.updateDeeplink(to: nil) + + - // Handle deep link - DeeplinkHandler.handleDeeplink(incomingLink) - } +Lastly, implement your deep link handling logic: + +[Deep Link Handler](#deep-link-handler) + +## SwiftUI apps using SceneDelegate lifecycle + +Follow the steps in the [SwiftUI apps not using SceneDelegate lifecycle](#swiftui-apps-not-using-scenedelegate-lifecycle). In addition, implement the following method in your SceneDelegate. + + + + + + +```swift +import UIKit + +class SceneDelegate: UIResponder, UIWindowSceneDelegate { + // Receive deep link when creating new scene, when either: + // - Launching app from "not running" state, or + // - Creating additional scene while app is + // in background or foreground state. + // + // Receive universal link when user clicks link. + // Receive app scheme deep link when user clicks link or + // your app/another app programmatically opens link + // using UIApplication.shared.open or SwiftUI's openURL. + func scene( + _ scene: UIScene, + willConnectTo session: UISceneSession, + options connectionOptions: UIScene.ConnectionOptions + ) { + // Handle incoming universal link + if let userActivity = connectionOptions.userActivities.first, + userActivity.activityType == NSUserActivityTypeBrowsingWeb, + let incomingLink = userActivity.webpageURL + { + DeeplinkHandler.handleDeeplink(incomingLink) + } + // Handle incoming app scheme deep link + else if let urlContext = connectionOptions.urlContexts.first, + let incomingLink = urlContext.url + { + DeeplinkHandler.handleDeeplink(incomingLink) } } } @@ -976,6 +772,8 @@ struct OnboardingView: View { +## Deep link handler + The preceding code examples use an example DeeplinkHandler class. This example class is shown below and handles all types of links: - Adjust branded links (full go.link links) @@ -1008,257 +806,183 @@ The class performs the following tasks: ```swift import Adjust import Foundation -import SwiftUI +// import UIKit and/or SwiftUI class DeeplinkHandler { - static func handleDeeplink(_ incomingLink: URL) { - // Send incoming deep link to Adjust's servers for attribution - // and to resolve short link, if applicable. - Adjust.processDeeplink(incomingLink) { processedLinkString in - guard let processedLink = URL(string: processedLinkString) else { - print("Invalid processed link") - return - } - - // Extract path, query items, and fragment from the resolved link. - let components = URLComponents( - url: processedLink, resolvingAgainstBaseURL: true) - let path = components?.path ?? "" - let queryItems = components?.queryItems ?? [] + static func handleDeeplink(_ incomingLink: URL) { + // Send incoming deep link to Adjust's servers for attribution + // and retrieve full URL if short branded link. + // If not, return original link. + Adjust.processDeeplink(incomingLink) { processedLinkString in + guard let processedLink = URL(string: processedLinkString) else { + print("Invalid processed link") + return + } - // Parse query parameters into a dictionary for easier access - let params = queryItems.reduce(into: [String: String]()) { result, item in - result[item.name] = item.value - } + // Extract path, query items, and fragment from the processed link. + let components = URLComponents( + url: processedLink, + resolvingAgainstBaseURL: true + ) + let path = components?.path ?? "" + let queryItems = components?.queryItems ?? [] + + // Parse query parameters into a dictionary for easier access + let params = queryItems.reduce(into: [String: String]()) { result, item in + result[item.name] = item.value + } - let fragment = components?.fragment - - // Implement the navigation or other app-specific logic based on - // the deep link components. - DispatchQueue.main.async { - // Example of navigating based on the path or other components. - // Replace with your actual navigation logic. - switch path { - case "/product": - if let productId = params["id"] { - let productView = ProductView(productId: productId) - navigateTo(view: productView) - } - case "/category": - if let categoryName = params["name"] { - let categoryView = CategoryView(category: categoryName) - navigateTo(view: categoryView) - } - case "/search": - if let query = params["q"] { - let searchView = SearchView(searchQuery: query) - navigateTo(view: searchView) - } - default: - print("Unhandled deep link path: \(path)") + let fragment = components?.fragment + + // Implement the navigation or other app-specific logic based on + // the deep link components. + DispatchQueue.main.async { + // Example of navigating based on the path or other components. + // Replace with your actual navigation logic. + switch path { + case "/product": + if let productId = params["id"] { + handleProduct(productId: productId) + } + case "/category": + if let categoryName = params["name"] { + handleCategory(category: categoryName) + } + case "/search": + if let query = params["q"] { + handleSearch(query: query) + } + default: + print("Unhandled deep link path: \(path)") + } + } } - } } - } - - private static func navigateTo(view: T) { - // Implement navigation logic - // using one of these common patterns, for example: - // 1. Using NavigationPath: - // navigationPath.append(view) - // - // 2. Using navigation state: - // isProductViewActive = true - // selectedProductId = productId - // - // 3. Using custom router: - // router.navigate(to: view) - } -} -``` - - - -
-
- -## SwiftUI apps using SceneDelegate lifecycle - -"App" - -Update your AppDelegate to implement deferred deep linking. - - - - -```swift -// AppDelegate.swift - -import Adjust -import UIKit - -@UIApplicationMain -class AppDelegate: UIResponder, UIApplicationDelegate, - UNUserNotificationCenterDelegate, AdjustDelegate { - - let deeplinkManager = DeeplinkManager.shared - - func application( - _ application: UIApplication, - didFinishLaunchingWithOptions launchOptions: - [UIApplication.LaunchOptionsKey: Any]?) -> Bool { - // Set delegate for push notifications - UNUserNotificationCenter.current().delegate = self - - // Configure Adjust SDK - // Replace {YourAppToken} with your Adjust app token - let appToken = "{YourAppToken}" - let adjustConfig: ADJConfig? - - #if DEBUG - adjustConfig = ADJConfig( - appToken: appToken, - environment: ADJEnvironmentSandbox) - adjustConfig?.logLevel = ADJLogLevelVerbose - #else - adjustConfig = ADJConfig( - appToken: appToken, - environment: ADJEnvironmentProduction, - allowSuppressLogLevel: true) - adjustConfig?.logLevel = ADJLogLevelSuppress - #endif - - // Wait up to 30 seconds after app open for user to respond to ATT - // before sending install session to Adjust backend. - adjustConfig.attConsentWaitingInterval = 30 - - // Create delegate for deferred deep linking - adjustConfig.delegate = self - - // Initialize Adjust SDK - Adjust.appDidLaunch(adjustConfig) - return true - } + private static func handleProduct(productId: String) { + // Example UIKit implementation: + // let productVC = ProductViewController(productId: productId) + // navigationController.pushViewController(productVC, animated: true) - // Receive push notification while app is in foreground - func userNotificationCenter( - _ center: UNUserNotificationCenter, - willPresent notification: UNNotification) async -> - UNNotificationPresentationOptions { + // Example SwiftUI implementation: + // let productView = ProductView(productId: productId) + // navigationPath.append(productView) + // or: router.navigate(to: productView) + } - // Add your push notification handling here + private static func handleCategory(category: String) { + // Example UIKit implementation: + // let categoryVC = CategoryViewController(category: category) + // navigationController.pushViewController(categoryVC, animated: true) - // Change to your preferred presentation option - return [.alert, .sound] - } - - // Receive direct universal link or app scheme deep link - // from push notification while app isn't running, - // in background, or in foreground and user interacts - // with push notification. - // Replace "{deepLink}" with your custom key name. - func userNotificationCenter( - _ center: UNUserNotificationCenter, - didReceive response: UNNotificationResponse) async { - let userInfo = response.notification.request.content - .userInfo - - // Add your push notification handling here - - if let deepLinkURLString = userInfo["{deepLink}"] - as? String, - let incomingLink = URL( - string: deepLinkURLString) { - // Update DeeplinkManager with incoming deep link - self.deeplinkManager.updateDeeplinkState( - with: incomingLink) + // Example SwiftUI implementation: + // let categoryView = CategoryView(category: category) + // navigationPath.append(categoryView) + // or: router.navigate(to: categoryView) } - } - // Receive deferred deep link via AdjustDelegate method - func adjustDeeplinkResponse(_ deeplink: URL?) -> Bool { - if let incomingLink = deeplink { - // Store incoming deferred deep link to invoke after - // onboarding screens and login. - UserDefaults.standard.set( - incomingLink.absoluteString, - forKey: "DeferredDeeplinkURL") - } + private static func handleSearch(query: String) { + // Example UIKit implementation: + // let searchVC = SearchViewController(searchQuery: query) + // navigationController.pushViewController(searchVC, animated: true) - // Return true to try to open deep link immediately upon receipt. - // Otherwise, return false. - return false - } + // Example SwiftUI implementation: + // let searchView = SearchView(searchQuery: query) + // navigationPath.append(searchView) + // or: router.navigate(to: searchView) + } } ``` - - + -Update your SceneDelegate to implement direct linking. + + - - + -```swift -// SceneDelegate.swift +```objc +#import +#import +#import -import SwiftUI -import UIKit +@interface DeeplinkHandler : NSObject ++ (void)handleDeeplink:(NSURL *)incomingLink; +@end -class SceneDelegate: UIResponder, UIWindowSceneDelegate, AdjustDelegate { - - var window: UIWindow? - let deeplinkManager = DeeplinkManager.shared - - // Receive direct universal link or app scheme deep link - // while app isn't running, or when new scene is created - func scene(_ scene: UIScene, - willConnectTo session: UISceneSession, - options connectionOptions: UIScene.ConnectionOptions) { - // Initialize and make window visible - let contentView = ContentView().environmentObject(deeplinkManager) - if let windowScene = scene as? UIWindowScene { - let window = UIWindow(windowScene: windowScene) - window.rootViewController = UIHostingController(rootView: contentView) - self.window = window - window.makeKeyAndVisible() - } +@implementation DeeplinkHandler - if let urlContext = connectionOptions.urlContexts.first { - let incomingLink = urlContext.url ++ (void)handleDeeplink:(NSURL *)incomingLink { + // Send incoming deep link to Adjust's servers for attribution + // and retrieve full URL if short branded link. + // If not, return original link. + [Adjust processDeeplink:incomingLink + completionHandler:^(NSString *_Nonnull processedLinkString) { + NSURL *processedLink = [NSURL URLWithString:processedLinkString]; + if (!processedLink) { + NSLog(@"Invalid processed link"); + return; + } - // Update DeeplinkManager with incoming deep link - deeplinkManager.updateDeeplinkState(with: incomingLink) - } - } + // Extract path, query items, and fragment from the processed link. + NSURLComponents *components = + [NSURLComponents componentsWithURL:processedLink + resolvingAgainstBaseURL:YES]; + NSString *path = components.path ?: @""; + NSArray *queryItems = components.queryItems ?: @[]; + + // Parse query parameters into a dictionary for easier access + NSMutableDictionary *params = + [NSMutableDictionary dictionary]; + for (NSURLQueryItem *item in queryItems) { + params[item.name] = item.value; + } - // Receive direct universal links while app is in background - func scene(_ scene: UIScene, continue userActivity: NSUserActivity) { - if userActivity.activityType == NSUserActivityTypeBrowsingWeb, - let incomingLink = userActivity.webpageURL { - // Update DeeplinkManager with incoming universal link - deeplinkManager.updateDeeplinkState(with: incomingLink) - } - } + NSString *fragment = components.fragment; - // Receive direct app scheme deep link in existing scene - // while app is in background. - func scene(_ scene: UIScene, openURLContexts URLContexts: Set) { - if let urlContext = URLContexts.first { - let incomingLink = urlContext.url + // Implement the navigation or other app-specific logic based on + // the deep link components. + dispatch_async(dispatch_get_main_queue(), ^{ + // Example of navigating based on the path or other components. + // Replace with your actual navigation logic. + if ([path isEqualToString:@"/product"]) { + NSString *productId = params[@"id"]; + if (productId) { + ProductViewController *productVC = + [[ProductViewController alloc] initWithProductId:productId]; + [self navigateToViewController:productVC]; + } + } else if ([path isEqualToString:@"/category"]) { + NSString *categoryName = params[@"name"]; + if (categoryName) { + CategoryViewController *categoryVC = + [[CategoryViewController alloc] initWithCategory:categoryName]; + [self navigateToViewController:categoryVC]; + } + } else if ([path isEqualToString:@"/search"]) { + NSString *query = params[@"q"]; + if (query) { + SearchViewController *searchVC = + [[SearchViewController alloc] initWithSearchQuery:query]; + [self navigateToViewController:searchVC]; + } + } else { + NSLog(@"Unhandled deep link path: %@", path); + } + }); + }]; +} - // Update DeeplinkManager with incoming app scheme deep link - deeplinkManager.updateDeeplinkState(with: incomingLink) - } - } ++ (void)navigateToViewController:(UIViewController *)viewController { + // Example navigation implementation: + // UINavigationController *navigationController = [self getNavigationController]; + // [navigationController pushViewController:viewController animated:YES]; } + +@end ``` + + - -"Deeplink Manager" - -"Content View" From 82c2c5813bafe8577248b0de999da615feed8361 Mon Sep 17 00:00:00 2001 From: Dave Mead Date: Mon, 18 Nov 2024 18:22:33 -0800 Subject: [PATCH 11/27] Refine implementations --- .../deep-links/set-up-deep-linking.mdx | 628 +++++++++--------- 1 file changed, 320 insertions(+), 308 deletions(-) diff --git a/src/content/docs/sdk/ios/v4/features/deep-links/set-up-deep-linking.mdx b/src/content/docs/sdk/ios/v4/features/deep-links/set-up-deep-linking.mdx index 59b1b0db1..652cea772 100644 --- a/src/content/docs/sdk/ios/v4/features/deep-links/set-up-deep-linking.mdx +++ b/src/content/docs/sdk/ios/v4/features/deep-links/set-up-deep-linking.mdx @@ -17,7 +17,7 @@ Please follow the steps in the section that corresponds to the type of app you h - [UIKit apps using AppDelegate lifecycle](#uikit-apps-using-appdelegate-lifecycle) - [UIKit apps using SceneDelegate lifecycle](#uikit-apps-using-scenedelegate-lifecycle) -- [SwiftUI apps not using SceneDelegate lifecycle](#swiftui-apps-not-using-scenedelegate-lifecycle) +- [SwiftUI apps using AppDelegate lifecycle](#swiftui-apps-using-appdelegate-lifecycle) - [SwiftUI apps using SceneDelegate lifecycle](#swiftui-apps-using-scenedelegate-lifecycle) In addition, implement deep link handling logic: @@ -86,9 +86,9 @@ class AppDelegate: UIResponder, UIApplicationDelegate, } // Receive universal link when app is installed, - // and app is in one of these states: - // not running, background, or foreground, - // and user clicks link to open app. + // and app is in "not running" or background state, + // and user clicks link or another app opens link using + // UIApplication.open(_:options:completionHandler:). func application( _ application: UIApplication, continue userActivity: NSUserActivity, @@ -104,21 +104,18 @@ class AppDelegate: UIResponder, UIApplicationDelegate, return true } - // Receive app scheme deep link when app is installed, - // and app is in one of these states: - // not running, background, or foreground, - // and link opens app when either: - // - // - User clicks link, or - // - Your app/another app programmatically opens link - // using UIApplication.shared.open. + // Receive app scheme deep link when app is installed, and either: + // - App is in "not running" or background state, and user clicks link or + // another app opens link using UIApplication.open(_:options:completionHandler:), or + // - App is in foreground state and opens its own link using + // UIApplication.open(_:options:completionHandler:). func application( _ application: UIApplication, open incomingLink: URL, options: [UIApplication.OpenURLOptionsKey: Any] = [:] ) -> Bool { - // Handle incoming deep link - DeeplinkHandler.handleDeeplink(incomingLink) + // Handle incoming app scheme deep link + DeeplinkHandler.handleDeeplink(incomingLink) return true } @@ -192,9 +189,9 @@ class AppDelegate: UIResponder, UIApplicationDelegate, } // Receive universal link when app is installed, -// and app is in one of these states: -// not running, background, or foreground, -// and user clicks link to open app. +// and app is in "not running" or background state, +// and user clicks link or another app opens link using +// [UIApplication openURL:options:completionHandler:]. - (BOOL)application:(UIApplication *)application continueUserActivity:(NSUserActivity *)userActivity restorationHandler: @@ -209,14 +206,11 @@ class AppDelegate: UIResponder, UIApplicationDelegate, return YES; } -// Receive app scheme deep link when app is installed, -// and app is in one of these states: -// not running, background, or foreground, -// and link opens app when either: -// -// - User clicks link, or -// - Your app/another app programmatically opens link -// using [[UIApplication sharedApplication] openURL:]. +// Receive app scheme deep link when app is installed, and either: +// - App is in "not running" or background state, and user clicks link or +// another app opens link using [UIApplication openURL:options:completionHandler:], or +// - App is in foreground state and opens its own link using +// [UIApplication openURL:options:completionHandler:]. - (BOOL)application:(UIApplication *)app openURL:(NSURL *)incomingLink options: @@ -256,60 +250,59 @@ For deferred deep linking, this is the sequence for most apps: 4. After the ATT prompt response or timeout, the Adjust SDK sends session and attribution requests to Adjust's servers. 5. Adjust's servers respond with attribution data, including the deep link the user clicked on ("deferred deep link"). 6. The Adjust SDK triggers a deferred deep link callback in your app, which stores the deferred deep link (shown in the AppDelegate implementation above). -7. Once onboarding completes, the app retrieves the stored deep link and navigates the user accordingly (shown in the ContentViewController example class below). +7. Once onboarding completes, the app retrieves the stored deep link and navigates the user accordingly (shown in the ViewController example class below). - + ```swift import Adjust import Foundation import UIKit -class ContentViewController: UIViewController { +class ViewController: UIViewController { - var hasCompletedOnboarding: Bool { - get { - UserDefaults.standard.bool(forKey: "HasCompletedOnboarding") - } - set { - UserDefaults.standard.set( - newValue, forKey: "HasCompletedOnboarding") - } + var hasCompletedOnboarding: Bool { + get { + UserDefaults.standard.bool(forKey: "HasCompletedOnboarding") } + set { + UserDefaults.standard.set( + newValue, forKey: "HasCompletedOnboarding") + } + } - override func viewDidAppear() { - super.viewDidAppear() + override func viewDidAppear() { + super.viewDidAppear() - // Check if onboarding has been completed - if !hasCompletedOnboarding { - // Show ATT dialog - Adjust.requestTrackingAuthorization(completionHandler: nil) + // Check if onboarding has been completed + if !hasCompletedOnboarding { + // Show ATT dialog + Adjust.requestTrackingAuthorization(completionHandler: nil) - // Show onboarding screens and login prompt + // Show onboarding screens and login prompt - // On completion, set hasCompletedOnboarding to true - hasCompletedOnboarding = true - } + // On completion, set hasCompletedOnboarding to true + hasCompletedOnboarding = true + } - // Check if there's a stored deferred deep link - if let deferredLinkString = UserDefaults.standard.string( - forKey: "lastDeferredLink"), - let deferredLink = URL(string: deferredLinkString) - { - // Remove the stored deferred deep link - // to avoid handling it again later - UserDefaults.standard.removeObject(forKey: "lastDeferredLink") - - // Handle deferred deep link - DeeplinkHandler.handleDeeplink(deferredLink) - } - else { - // Show main content - } + // Check if there's a stored deferred deep link + if let deferredLinkString = UserDefaults.standard.string( + forKey: "lastDeferredLink"), + let deferredLink = URL(string: deferredLinkString) + { + // Remove the stored deferred deep link + // to avoid handling it again later + UserDefaults.standard.removeObject(forKey: "lastDeferredLink") + + // Handle deferred deep link + DeeplinkHandler.handleDeeplink(deferredLink) + } else { + // Show main content } + } } ``` @@ -318,7 +311,7 @@ class ContentViewController: UIViewController { - + ```objc #import @@ -327,57 +320,58 @@ class ContentViewController: UIViewController { @class DeeplinkHandler; -@interface ContentViewController : UIViewController +@interface ViewController : UIViewController @end -@interface ContentViewController () +@interface ViewController () @property(nonatomic, assign) BOOL hasCompletedOnboarding; @end -@implementation ContentViewController +@implementation ViewController - (BOOL)hasCompletedOnboarding { - return [[NSUserDefaults standardUserDefaults] - boolForKey:@"HasCompletedOnboarding"]; + return [[NSUserDefaults standardUserDefaults] + boolForKey:@"HasCompletedOnboarding"]; } - (void)setHasCompletedOnboarding:(BOOL)hasCompletedOnboarding { - [[NSUserDefaults standardUserDefaults] setBool:hasCompletedOnboarding - forKey:@"HasCompletedOnboarding"]; + [[NSUserDefaults standardUserDefaults] setBool:hasCompletedOnboarding + forKey:@"HasCompletedOnboarding"]; } - (void)viewDidAppear { - [super viewDidAppear]; + [super viewDidAppear]; - // Check if onboarding has been completed - if (!self.hasCompletedOnboarding) { - // Show ATT prompt - [Adjust requestTrackingAuthorizationWithCompletionHandler:nil]; + // Check if onboarding has been completed + if (!self.hasCompletedOnboarding) { + // Show ATT dialog + [Adjust requestTrackingAuthorizationWithCompletionHandler:nil]; - // Show onboarding screens and login prompt - // On completion, set hasCompletedOnboarding to true - self.hasCompletedOnboarding = YES; - } + // Show onboarding screens and login prompt - // Check if there's a stored deferred deep link - NSString *deferredLinkString = [[NSUserDefaults standardUserDefaults] - stringForKey:@"lastDeferredLink"]; - NSURL *deferredLink = [NSURL URLWithString:deferredLinkString]; - - if (deferredLink) { - // Remove the stored deferred deep link - // to avoid handling it again later - [[NSUserDefaults standardUserDefaults] - removeObjectForKey:@"lastDeferredLink"]; + // On completion, set hasCompletedOnboarding to true + self.hasCompletedOnboarding = YES; + } - // Handle deferred deep link - [DeeplinkHandler handleDeeplink:deferredLink]; - } else { - // Show main content - } + // Check if there's a stored deferred deep link + NSString *deferredLinkString = + [[NSUserDefaults standardUserDefaults] stringForKey:@"lastDeferredLink"]; + NSURL *deferredLink = [NSURL URLWithString:deferredLinkString]; + + if (deferredLink) { + // Remove the stored deferred deep link + // to avoid handling it again later + [[NSUserDefaults standardUserDefaults] + removeObjectForKey:@"lastDeferredLink"]; + + // Handle deferred deep link + [DeeplinkHandler handleDeeplink:deferredLink]; + } else { + // Show main content + } } @end @@ -406,36 +400,36 @@ import Foundation import UIKit class SceneDelegate: UIResponder, UIWindowSceneDelegate { - // Receive deep link when creating new scene, when either: - // - Launching app from "not running" state, or - // - Creating additional scene while app is - // in background or foreground state. - // - // Receive universal link when user clicks link. - // Receive app scheme deep link when user clicks link or - // your app/another app programmatically opens link - // using UIApplication.shared.open. + // Receive universal link or app scheme deep link + // when app is installed, and app is in "not running" state, and either: + // - User clicks link, or + // - Another app opens link using + // UIApplication.open(_:options:completionHandler:). func scene( - _ scene: UIScene, - willConnectTo session: UISceneSession, - options connectionOptions: UIScene.ConnectionOptions + _ scene: UIScene, + willConnectTo session: UISceneSession, + options connectionOptions: UIScene.ConnectionOptions ) { - // Handle incoming universal link - if let userActivity = connectionOptions.userActivities.first, - userActivity.activityType == NSUserActivityTypeBrowsingWeb, - let incomingLink = userActivity.webpageURL { - DeeplinkHandler.handleDeeplink(incomingLink) - } - // Handle incoming app scheme deep link - else if let urlContext = connectionOptions.urlContexts.first, - let incomingLink = urlContext.url { - DeeplinkHandler.handleDeeplink(incomingLink) - } + // Handle incoming universal link + if let userActivity = connectionOptions.userActivities.first, + userActivity.activityType == NSUserActivityTypeBrowsingWeb, + let incomingLink = userActivity.webpageURL + { + DeeplinkHandler.handleDeeplink(incomingLink) + } + + // Handle incoming app scheme deep link + else if let urlContext = connectionOptions.urlContexts.first { + let incomingLink = urlContext.url + DeeplinkHandler.handleDeeplink(incomingLink) + } } // Receive universal link when app is installed, - // and app is in background or foreground state, - // and user clicks link to open app to existing scene. + // and app is in background state, and either: + // - User clicks link, or + // - Another app opens link using + // UIApplication.open(_:options:completionHandler:). func scene(_ scene: UIScene, continue userActivity: NSUserActivity) { if userActivity.activityType == NSUserActivityTypeBrowsingWeb, let incomingLink = userActivity.webpageURL @@ -445,19 +439,18 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { } } - // Receive app scheme deep link when app is installed, - // and app is in background or foreground state, - // and link opens app to existing scene when either: - // - User clicks link, or - // - Your app/another app programmatically opens link - // using UIApplication.shared.open. + // Receive app scheme deep link when app is installed, and either: + // - App is in background state, and user clicks link or another app opens link using + // UIApplication.open(_:options:completionHandler:), or + // - App is in foreground and opens its own link using + // UIApplication.open(_:options:completionHandler:). func scene( _ scene: UIScene, openURLContexts URLContexts: Set ) { if let urlContext = URLContexts.first { let incomingLink = urlContext.url - // Handle incoming deep link + // Handle incoming app scheme deep link DeeplinkHandler.handleDeeplink(incomingLink) } } @@ -480,31 +473,39 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { @implementation SceneDelegate -// Receive deep link when creating new scene, when either: -// - Launching app from "not running" state, or -// - Creating additional scene while app is -// in background or foreground state. -// -// Receive universal link when user clicks link. -// Receive app scheme deep link when user clicks link or -// your app/another app programmatically opens link -// using [[UIApplication sharedApplication] openURL:]. +// Receive universal link or app scheme deep link +// when app is installed, and app is in "not running" state, and either: +// - User clicks link, or +// - Another app opens link using +// [UIApplication openURL:options:completionHandler:]. - (void)scene:(UIScene *)scene willConnectToSession:(UISceneSession *)session options:(UISceneConnectionOptions *)connectionOptions { - if (connectionOptions.URLContexts.count > 0) { + // Handle incoming universal link + if (connectionOptions.userActivities.count > 0) { + NSUserActivity *userActivity = + connectionOptions.userActivities.allObjects.firstObject; + if ([userActivity.activityType + isEqualToString:NSUserActivityTypeBrowsingWeb] && + userActivity.webpageURL) { + NSURL *incomingLink = userActivity.webpageURL; + [DeeplinkHandler handleDeeplink:incomingLink]; + } + } + // Handle incoming app scheme deep link + else if (connectionOptions.URLContexts.count > 0) { UIOpenURLContext *urlContext = connectionOptions.URLContexts.allObjects.firstObject; NSURL *incomingLink = urlContext.URL; - - // Handle incoming deep link [DeeplinkHandler handleDeeplink:incomingLink]; } } // Receive universal link when app is installed, -// and app is in background or foreground state, -// and user clicks link to open app to existing scene. +// and app is in background state, and either: +// - User clicks link, or +// - Another app opens link using +// [UIApplication openURL:options:completionHandler:]. - (void)scene:(UIScene *)scene continueUserActivity:(NSUserActivity *)userActivity { if ([userActivity.activityType @@ -516,19 +517,18 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { } } -// Receive app scheme deep link when app is installed, -// and app is in background or foreground state, -// and link opens app to existing scene when either: -// - User clicks link, or -// - Your app/another app programmatically opens link -// using [[UIApplication sharedApplication] openURL:]. +// Receive app scheme deep link when app is installed, and either: +// - App is in background state, and user clicks link or another app opens link +// using [UIApplication openURL:options:completionHandler:], or +// - App is in foreground and opens its own link using +// [UIApplication openURL:options:completionHandler:]. - (void)scene:(UIScene *)scene openURLContexts:(NSSet *)URLContexts { UIOpenURLContext *urlContext = URLContexts.allObjects.firstObject; if (urlContext) { NSURL *incomingLink = urlContext.URL; - // Handle incoming deep link + // Handle incoming app scheme deep link [DeeplinkHandler handleDeeplink:incomingLink]; } } @@ -541,9 +541,9 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { -## SwiftUI apps not using SceneDelegate lifecycle +## SwiftUI apps using AppDelegate lifecycle -If you haven't already done so, create an `AppDelegate.swift` file in your project's main directory and reference it in your main application file (for example: `App.swift` as shown below). This is required to handle app lifecycle events and Adjust SDK integration. +If you haven't already done so, create an `AppDelegate.swift` file in your project's main directory and reference it in your main application file (for example: `App.swift` as shown below). This is required to handle app lifecycle events and Adjust SDK integration. Also implement `onOpenURL`, which receives all direct deep links. @@ -560,6 +560,18 @@ struct MyApp: App { var body: some Scene { WindowGroup { ContentView() + // Receive universal link or app scheme deep link + // when app is installed, and app is in "not running" or background state, and either: + // - User clicks link, or + // - Another app opens link using + // UIApplication.open(_:options:completionHandler:) or SwiftUI's openURL. + // + // Receive app scheme deep link when app is installed, + // and app is in foreground and opens its own link using + // UIApplication.open(_:options:completionHandler:) or SwiftUI's openURL. + .onOpenURL { incomingLink in + DeeplinkHandler.handleDeeplink(incomingLink) + } } } } @@ -643,11 +655,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate, AdjustDelegate { -Below is an example `ContentView.swift` implementation that does the following: - -- Receives direct deep links. -- Handles onboarding flow and deferred deep linking. - For deferred deep linking, this is the sequence for most apps: 1. A user who doesn't have the app installed clicks an Adjust deep link, which redirects them to the app store. @@ -680,6 +687,7 @@ struct ContentView: View { } // Show onboarding screens and login prompt + // On completion, set hasCompletedOnboarding to true UserDefaults.standard.set(true, forKey: "HasCompletedOnboarding") hasCompletedOnboarding = true @@ -699,17 +707,6 @@ struct ContentView: View { // Show main content } } - // Receive deep link when app is installed, - // and app is in one of these states: - // not running, background, or foreground. - // - // Receive universal link when user clicks link. - // Receive app scheme deep link when user clicks link or - // your app/another app programmatically opens link - // using UIApplication.shared.open or SwiftUI's openURL. - .onOpenURL { incomingLink in - DeeplinkHandler.handleDeeplink(incomingLink) - } } } ``` @@ -725,7 +722,7 @@ Lastly, implement your deep link handling logic: ## SwiftUI apps using SceneDelegate lifecycle -Follow the steps in the [SwiftUI apps not using SceneDelegate lifecycle](#swiftui-apps-not-using-scenedelegate-lifecycle). In addition, implement the following method in your SceneDelegate. +Follow the steps in the [SwiftUI apps using AppDelegate lifecycle](#swiftui-apps-using-appdelegate-lifecycle). In addition, implement the following method in your SceneDelegate. @@ -736,15 +733,11 @@ Follow the steps in the [SwiftUI apps not using SceneDelegate lifecycle](#swiftu import UIKit class SceneDelegate: UIResponder, UIWindowSceneDelegate { - // Receive deep link when creating new scene, when either: - // - Launching app from "not running" state, or - // - Creating additional scene while app is - // in background or foreground state. - // - // Receive universal link when user clicks link. - // Receive app scheme deep link when user clicks link or - // your app/another app programmatically opens link - // using UIApplication.shared.open or SwiftUI's openURL. + // Receive universal link or app scheme deep link + // when app is installed, and app is in "not running" state, and either: + // - User clicks link, or + // - Another app opens link using + // UIApplication.open(_:options:completionHandler:) or SwiftUI's openURL. func scene( _ scene: UIScene, willConnectTo session: UISceneSession, @@ -758,9 +751,8 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { DeeplinkHandler.handleDeeplink(incomingLink) } // Handle incoming app scheme deep link - else if let urlContext = connectionOptions.urlContexts.first, + else if let urlContext = connectionOptions.urlContexts.first { let incomingLink = urlContext.url - { DeeplinkHandler.handleDeeplink(incomingLink) } } @@ -809,88 +801,97 @@ import Foundation // import UIKit and/or SwiftUI class DeeplinkHandler { - static func handleDeeplink(_ incomingLink: URL) { - // Send incoming deep link to Adjust's servers for attribution - // and retrieve full URL if short branded link. - // If not, return original link. - Adjust.processDeeplink(incomingLink) { processedLinkString in - guard let processedLink = URL(string: processedLinkString) else { - print("Invalid processed link") - return - } - - // Extract path, query items, and fragment from the processed link. - let components = URLComponents( - url: processedLink, - resolvingAgainstBaseURL: true - ) - let path = components?.path ?? "" - let queryItems = components?.queryItems ?? [] - - // Parse query parameters into a dictionary for easier access - let params = queryItems.reduce(into: [String: String]()) { result, item in - result[item.name] = item.value - } - - let fragment = components?.fragment - - // Implement the navigation or other app-specific logic based on - // the deep link components. - DispatchQueue.main.async { - // Example of navigating based on the path or other components. - // Replace with your actual navigation logic. - switch path { - case "/product": - if let productId = params["id"] { - handleProduct(productId: productId) - } - case "/category": - if let categoryName = params["name"] { - handleCategory(category: categoryName) - } - case "/search": - if let query = params["q"] { - handleSearch(query: query) - } - default: - print("Unhandled deep link path: \(path)") - } - } - } + static func handleDeeplink(_ incomingLink: URL) { + // Send incoming deep link to Adjust's servers for attribution + // and retrieve full URL if short branded link. + // If not, retrieve original link. + Adjust.processDeeplink(incomingLink) { processedLinkString in + guard let processedLink = URL(string: processedLinkString) else { return } } - private static func handleProduct(productId: String) { - // Example UIKit implementation: - // let productVC = ProductViewController(productId: productId) - // navigationController.pushViewController(productVC, animated: true) + // Extract path, query items, and fragment from the processed link. + let components = URLComponents( + url: processedLink, + resolvingAgainstBaseURL: true + ) - // Example SwiftUI implementation: - // let productView = ProductView(productId: productId) - // navigationPath.append(productView) - // or: router.navigate(to: productView) + // For app scheme deep links, set path = host + path + var path: String + if let scheme = processedLink.scheme, scheme == "http" || scheme == "https" + { + path = components.path + } else { + path = (components.host ?? "") + components.path + if !path.isEmpty && !path.hasPrefix("/") { + path = "/" + path + } } - private static func handleCategory(category: String) { - // Example UIKit implementation: - // let categoryVC = CategoryViewController(category: category) - // navigationController.pushViewController(categoryVC, animated: true) + let queryItems = components?.queryItems ?? [] - // Example SwiftUI implementation: - // let categoryView = CategoryView(category: category) - // navigationPath.append(categoryView) - // or: router.navigate(to: categoryView) + // Parse query parameters into a dictionary for easier access + let params = queryItems.reduce(into: [String: String]()) { result, item in + result[item.name] = item.value } - private static func handleSearch(query: String) { - // Example UIKit implementation: - // let searchVC = SearchViewController(searchQuery: query) - // navigationController.pushViewController(searchVC, animated: true) - - // Example SwiftUI implementation: - // let searchView = SearchView(searchQuery: query) - // navigationPath.append(searchView) - // or: router.navigate(to: searchView) + let fragment = components?.fragment + + // Implement the navigation or other app-specific logic based on + // the deep link components. + DispatchQueue.main.async { + // Example of navigating based on the path or other components. + // Replace with your actual navigation logic. + switch path { + case "/product": + if let productId = params["id"] { + handleProduct(productId: productId) + } + case "/category": + if let categoryName = params["name"] { + handleCategory(category: categoryName) + } + case "/search": + if let query = params["q"] { + handleSearch(query: query) + } + default: + print("Unhandled deep link path: \(path)") + } } + } + + private static func handleProduct(productId: String) { + // Example UIKit implementation: + // let productVC = ProductViewController(productId: productId) + // navigationController.pushViewController(productVC, animated: true) + + // Example SwiftUI implementation: + // let productView = ProductView(productId: productId) + // navigationPath.append(productView) + // or: router.navigate(to: productView) + } + + private static func handleCategory(category: String) { + // Example UIKit implementation: + // let categoryVC = CategoryViewController(category: category) + // navigationController.pushViewController(categoryVC, animated: true) + + // Example SwiftUI implementation: + // let categoryView = CategoryView(category: category) + // navigationPath.append(categoryView) + // or: router.navigate(to: categoryView) + } + + private static func handleSearch(query: String) { + // Example UIKit implementation: + // let searchVC = SearchViewController(searchQuery: query) + // navigationController.pushViewController(searchVC, animated: true) + + // Example SwiftUI implementation: + // let searchView = SearchView(searchQuery: query) + // navigationPath.append(searchView) + // or: router.navigate(to: searchView) + } } ``` @@ -913,70 +914,81 @@ class DeeplinkHandler { @implementation DeeplinkHandler + (void)handleDeeplink:(NSURL *)incomingLink { - // Send incoming deep link to Adjust's servers for attribution - // and retrieve full URL if short branded link. - // If not, return original link. - [Adjust processDeeplink:incomingLink - completionHandler:^(NSString *_Nonnull processedLinkString) { - NSURL *processedLink = [NSURL URLWithString:processedLinkString]; - if (!processedLink) { - NSLog(@"Invalid processed link"); - return; - } + // Send incoming deep link to Adjust's servers for attribution + // and retrieve full URL if short branded link. + // If not, retrieve original link. + [Adjust processDeeplink:incomingLink + completionHandler:^(NSString *_Nonnull processedLinkString) { + NSURL *processedLink = [NSURL URLWithString:processedLinkString]; + if (!processedLink) return; + }]; + + // Extract path, query items, and fragment from the processed link. + NSURLComponents *components = [NSURLComponents componentsWithURL:processedLink + resolvingAgainstBaseURL:YES]; + + // For app scheme deep links, set path = host + path + NSString *path; + if ([processedLink.scheme isEqualToString:@"http"] || + [processedLink.scheme isEqualToString:@"https"]) { + path = components.path; + } else { + path = [NSString + stringWithFormat:@"%@%@", components.host ?: @"", components.path]; + if (path.length > 0 && ![path hasPrefix:@"/"]) { + path = [@"/" stringByAppendingString:path]; + } + } - // Extract path, query items, and fragment from the processed link. - NSURLComponents *components = - [NSURLComponents componentsWithURL:processedLink - resolvingAgainstBaseURL:YES]; - NSString *path = components.path ?: @""; - NSArray *queryItems = components.queryItems ?: @[]; - - // Parse query parameters into a dictionary for easier access - NSMutableDictionary *params = - [NSMutableDictionary dictionary]; - for (NSURLQueryItem *item in queryItems) { - params[item.name] = item.value; - } + NSArray *queryItems = components.queryItems ?: @[]; + + // Parse query parameters into a dictionary for easier access + NSMutableDictionary *params = + [NSMutableDictionary dictionary]; + for (NSURLQueryItem *item in queryItems) { + params[item.name] = item.value; + } - NSString *fragment = components.fragment; - - // Implement the navigation or other app-specific logic based on - // the deep link components. - dispatch_async(dispatch_get_main_queue(), ^{ - // Example of navigating based on the path or other components. - // Replace with your actual navigation logic. - if ([path isEqualToString:@"/product"]) { - NSString *productId = params[@"id"]; - if (productId) { - ProductViewController *productVC = - [[ProductViewController alloc] initWithProductId:productId]; - [self navigateToViewController:productVC]; - } - } else if ([path isEqualToString:@"/category"]) { - NSString *categoryName = params[@"name"]; - if (categoryName) { - CategoryViewController *categoryVC = - [[CategoryViewController alloc] initWithCategory:categoryName]; - [self navigateToViewController:categoryVC]; - } - } else if ([path isEqualToString:@"/search"]) { - NSString *query = params[@"q"]; - if (query) { - SearchViewController *searchVC = - [[SearchViewController alloc] initWithSearchQuery:query]; - [self navigateToViewController:searchVC]; - } - } else { - NSLog(@"Unhandled deep link path: %@", path); - } - }); - }]; + NSString *fragment = components.fragment; + + // Implement the navigation or other app-specific logic based on + // the deep link components. + dispatch_async(dispatch_get_main_queue(), ^{ + // Example of navigating based on the path or other components. + // Replace with your actual navigation logic. + if ([path isEqualToString:@"/product"]) { + NSString *productId = params[@"id"]; + if (productId) { + ProductViewController *productVC = + [[ProductViewController alloc] initWithProductId:productId]; + [self navigateToViewController:productVC]; + } + } else if ([path isEqualToString:@"/category"]) { + NSString *categoryName = params[@"name"]; + if (categoryName) { + CategoryViewController *categoryVC = + [[CategoryViewController alloc] initWithCategory:categoryName]; + [self navigateToViewController:categoryVC]; + } + } else if ([path isEqualToString:@"/search"]) { + NSString *query = params[@"q"]; + if (query) { + SearchViewController *searchVC = + [[SearchViewController alloc] initWithSearchQuery:query]; + [self navigateToViewController:searchVC]; + } + } else { + NSLog(@"Unhandled deep link path: %@", path); + } + }); +}]; } + (void)navigateToViewController:(UIViewController *)viewController { - // Example navigation implementation: - // UINavigationController *navigationController = [self getNavigationController]; - // [navigationController pushViewController:viewController animated:YES]; + // Example navigation implementation: + // UINavigationController *navigationController = [self + // getNavigationController]; [navigationController + // pushViewController:viewController animated:YES]; } @end From 80081ecba379ab6c5c63074681dc4e692945547d Mon Sep 17 00:00:00 2001 From: Dave Mead Date: Wed, 20 Nov 2024 00:16:54 -0800 Subject: [PATCH 12/27] Fix deferred deeplinking issue --- .../deep-links/set-up-deep-linking.mdx | 98 +++++++++++++------ 1 file changed, 67 insertions(+), 31 deletions(-) diff --git a/src/content/docs/sdk/ios/v4/features/deep-links/set-up-deep-linking.mdx b/src/content/docs/sdk/ios/v4/features/deep-links/set-up-deep-linking.mdx index 652cea772..af21fb40e 100644 --- a/src/content/docs/sdk/ios/v4/features/deep-links/set-up-deep-linking.mdx +++ b/src/content/docs/sdk/ios/v4/features/deep-links/set-up-deep-linking.mdx @@ -279,8 +279,10 @@ class ViewController: UIViewController { // Check if onboarding has been completed if !hasCompletedOnboarding { - // Show ATT dialog - Adjust.requestTrackingAuthorization(completionHandler: nil) + // Show ATT prompt after delay to ensure app is active + DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) { + Adjust.requestTrackingAuthorization { _ in } + } // Show onboarding screens and login prompt @@ -347,8 +349,12 @@ class ViewController: UIViewController { // Check if onboarding has been completed if (!self.hasCompletedOnboarding) { - // Show ATT dialog - [Adjust requestTrackingAuthorizationWithCompletionHandler:nil]; + // Show ATT prompt after delay to ensure app is active + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), + dispatch_get_main_queue(), ^{ + [Adjust + requestTrackingAuthorizationWithCompletionHandler:nil]; + }); // Show onboarding screens and login prompt @@ -640,6 +646,8 @@ class AppDelegate: UIResponder, UIApplicationDelegate, AdjustDelegate { UserDefaults.standard.set( incomingLink.absoluteString, forKey: "lastDeferredLink") + // Post notification for app to handle deferred deep link + NotificationCenter.default.post(name: .deferredLinkReceived, object: nil) } // Return true to try to open deep link immediately @@ -663,7 +671,10 @@ For deferred deep linking, this is the sequence for most apps: 4. After the ATT prompt response or timeout, the Adjust SDK sends session and attribution requests to Adjust's servers. 5. Adjust's servers respond with attribution data, including the deep link the user clicked on ("deferred deep link"). 6. The Adjust SDK triggers a deferred deep link callback in your app, which stores the deferred deep link (shown in the AppDelegate implementation above). -7. Once onboarding completes, the app retrieves the stored deep link and navigates the user accordingly (shown in the ContentView example class below). +7. The app handles the deferred deep link in one of two ways (shown in the ContentView example class below): + - Once onboarding completes, the app checks for and handles any stored deferred deep link. + - If a deferred deep link arrives after onboarding, a notification triggers the same check and handling. +8. The app navigates the user to the deep link screen. @@ -675,39 +686,64 @@ import Adjust import SwiftUI struct ContentView: View { - @State private var hasCompletedOnboarding: Bool = UserDefaults.standard.bool( - forKey: "HasCompletedOnboarding") + @State private var hasCompletedOnboarding = UserDefaults.standard.bool( + forKey: "HasCompletedOnboarding" + ) var body: some View { - VStack { - // Check if onboarding has been completed - if !hasCompletedOnboarding { - .onAppear { - Adjust.requestTrackingAuthorization(completionHandler: nil) + NavigationStack { + VStack { + // Check if onboarding has been completed + if !hasCompletedOnboarding { + // Show onboarding screens and login prompt + + // Show ATT prompt after delay to ensure app is active + .onAppear { + DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) { + Adjust.requestTrackingAuthorization { _ in } + } + } + + // After onboarding, set hasCompletedOnboarding to true + // and check for stored deferred deep link + UserDefaults.standard.set(true, forKey: "HasCompletedOnboarding") + hasCompletedOnboarding = true + checkForDeferredLink() + } else { + // Show main content } - - // Show onboarding screens and login prompt - - // On completion, set hasCompletedOnboarding to true - UserDefaults.standard.set(true, forKey: "HasCompletedOnboarding") - hasCompletedOnboarding = true } + } - // Check if there's a stored deferred deep link - if let deferredLinkString = UserDefaults.standard.string( - forKey: "lastDeferredLink"), - let deferredLink = URL(string: deferredLinkString) - { - // Remove the stored deferred deep link - // to avoid handling it again later - UserDefaults.standard.removeObject(forKey: "lastDeferredLink") - // Handle deferred deep link - DeeplinkHandler.handleDeeplink(deferredLink) - } else { - // Show main content - } + // Check for stored deferred deep link + // that may have arrived after onboarding + .onReceive( + NotificationCenter.default.publisher(for: .deferredLinkReceived) + ) { _ in + checkForDeferredLink() } } + + private func checkForDeferredDeeplink() { + // Check for completed onboarding + // and stored deferred deep link before handling + guard hasCompletedOnboarding, + let deferredLinkString = UserDefaults.standard.string( + forKey: "lastDeferredLink" + ), + let deferredLink = URL(string: deferredLinkString) else { return } + + // Remove the stored deferred deep link to avoid handling it again later + UserDefaults.standard.removeObject(forKey: "lastDeferredLink") + + // Handle deferred deep link + DeeplinkHandler.handleDeeplink(deferredLink) + } +} + +// Set deferred deep link notification name +extension Notification.Name { + static let deferredLinkReceived = Notification.Name("deferredLinkReceived") } ``` From 35cc029c7bd5c1a2625a7bda13ad35a3a3870269 Mon Sep 17 00:00:00 2001 From: Dave Mead Date: Thu, 21 Nov 2024 00:46:13 -0800 Subject: [PATCH 13/27] Update SwiftUI implementation --- .../deep-links/set-up-deep-linking.mdx | 97 +++++++++++++------ 1 file changed, 66 insertions(+), 31 deletions(-) diff --git a/src/content/docs/sdk/ios/v4/features/deep-links/set-up-deep-linking.mdx b/src/content/docs/sdk/ios/v4/features/deep-links/set-up-deep-linking.mdx index af21fb40e..7917cf300 100644 --- a/src/content/docs/sdk/ios/v4/features/deep-links/set-up-deep-linking.mdx +++ b/src/content/docs/sdk/ios/v4/features/deep-links/set-up-deep-linking.mdx @@ -127,9 +127,12 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UserDefaults.standard.set( incomingLink.absoluteString, forKey: "lastDeferredLink") + + // Post notification for app to handle deferred deep link + NotificationCenter.default.post(name: .deferredLinkReceived, object: nil) } - // Return true to try to open deep link immediately + // Return true to let Adjust SDK immediately attempt to open deep link // upon receipt (for example: app has no ATT, onboarding screens, or login). // Otherwise, return false. return false @@ -250,7 +253,10 @@ For deferred deep linking, this is the sequence for most apps: 4. After the ATT prompt response or timeout, the Adjust SDK sends session and attribution requests to Adjust's servers. 5. Adjust's servers respond with attribution data, including the deep link the user clicked on ("deferred deep link"). 6. The Adjust SDK triggers a deferred deep link callback in your app, which stores the deferred deep link (shown in the AppDelegate implementation above). -7. Once onboarding completes, the app retrieves the stored deep link and navigates the user accordingly (shown in the ViewController example class below). +7. The app handles the deferred deep link in one of two ways (shown in the ContentView example class below): + - Once onboarding completes, the app checks for and handles any stored deferred deep link. + - If a deferred deep link arrives after onboarding, a notification triggers the same check and handling. +8. The app navigates the user to the deep link screen. @@ -263,7 +269,6 @@ import Foundation import UIKit class ViewController: UIViewController { - var hasCompletedOnboarding: Bool { get { UserDefaults.standard.bool(forKey: "HasCompletedOnboarding") @@ -274,38 +279,70 @@ class ViewController: UIViewController { } } + override func viewDidLoad() { + super.viewDidLoad() + + // Observe deferred deep link notifications + NotificationCenter.default.addObserver( + self, + selector: #selector(onDeferredLinkReceived), + name: .deferredLinkReceived, + object: nil + ) + } + override func viewDidAppear() { super.viewDidAppear() - // Check if onboarding has been completed if !hasCompletedOnboarding { + // Show onboarding screens and login prompt + // Show ATT prompt after delay to ensure app is active DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) { Adjust.requestTrackingAuthorization { _ in } } - // Show onboarding screens and login prompt - - // On completion, set hasCompletedOnboarding to true + // After onboarding, set hasCompletedOnboarding to true + // and check for stored deferred deep link hasCompletedOnboarding = true + checkForDeferredLink() + } else { + // Show main content } + } - // Check if there's a stored deferred deep link - if let deferredLinkString = UserDefaults.standard.string( - forKey: "lastDeferredLink"), + @objc private func onDeferredLinkReceived() { + // Check for deferred deep link that may arrive after onboarding + if hasCompletedOnboarding { + checkForDeferredLink() + } + } + + private func checkForDeferredLink() { + // Check for stored deferred deep link before handling + guard + let deferredLinkString = UserDefaults.standard.string( + forKey: "lastDeferredLink" + ), let deferredLink = URL(string: deferredLinkString) - { - // Remove the stored deferred deep link - // to avoid handling it again later - UserDefaults.standard.removeObject(forKey: "lastDeferredLink") + else { return } - // Handle deferred deep link - DeeplinkHandler.handleDeeplink(deferredLink) - } else { - // Show main content - } + // Remove stored deferred deep link to avoid handling again later + UserDefaults.standard.removeObject(forKey: "lastDeferredLink") + + // Handle deferred deep link + DeeplinkHandler.handleDeeplink(deferredLink) + } + + deinit { + NotificationCenter.default.removeObserver(self) } } + +// Set deferred deep link notification name +extension Notification.Name { + static let deferredLinkReceived = Notification.Name("deferredLinkReceived") +} ``` @@ -646,11 +683,12 @@ class AppDelegate: UIResponder, UIApplicationDelegate, AdjustDelegate { UserDefaults.standard.set( incomingLink.absoluteString, forKey: "lastDeferredLink") + // Post notification for app to handle deferred deep link NotificationCenter.default.post(name: .deferredLinkReceived, object: nil) } - // Return true to try to open deep link immediately + // Return true to let Adjust SDK immediately attempt to open deep link // upon receipt (for example: app has no ATT, onboarding screens, or login). // Otherwise, return false. return false @@ -686,9 +724,7 @@ import Adjust import SwiftUI struct ContentView: View { - @State private var hasCompletedOnboarding = UserDefaults.standard.bool( - forKey: "HasCompletedOnboarding" - ) + @State private var hasCompletedOnboarding = UserDefaults.standard.bool(forKey: "HasCompletedOnboarding") var body: some View { NavigationStack { @@ -715,25 +751,24 @@ struct ContentView: View { } } - // Check for stored deferred deep link - // that may have arrived after onboarding + // Check for deferred deep link that may arrive after onboarding .onReceive( NotificationCenter.default.publisher(for: .deferredLinkReceived) ) { _ in - checkForDeferredLink() + if hasCompletedOnboarding { + checkForDeferredLink() + } } } private func checkForDeferredDeeplink() { - // Check for completed onboarding - // and stored deferred deep link before handling - guard hasCompletedOnboarding, - let deferredLinkString = UserDefaults.standard.string( + // Check for stored deferred deep link before handling + guard let deferredLinkString = UserDefaults.standard.string( forKey: "lastDeferredLink" ), let deferredLink = URL(string: deferredLinkString) else { return } - // Remove the stored deferred deep link to avoid handling it again later + // Remove stored deferred deep link to avoid handling again later UserDefaults.standard.removeObject(forKey: "lastDeferredLink") // Handle deferred deep link From 6907a73ba06c74f776cb0fcd96363396ddb12166 Mon Sep 17 00:00:00 2001 From: Dave Mead Date: Thu, 28 Nov 2024 02:50:53 -0800 Subject: [PATCH 14/27] Refine implementations --- .../ios/v4/features/deep-links/deferred.mdx | 212 ---------- .../sdk/ios/v4/features/deep-links/direct.mdx | 392 ------------------ .../sdk/ios/v4/features/deep-links/index.mdx | 7 - .../deep-links/set-up-deep-linking.mdx | 149 +++---- .../ios/v4/features/deep-links/testing.mdx | 2 +- 5 files changed, 76 insertions(+), 686 deletions(-) delete mode 100644 src/content/docs/sdk/ios/v4/features/deep-links/deferred.mdx delete mode 100644 src/content/docs/sdk/ios/v4/features/deep-links/direct.mdx diff --git a/src/content/docs/sdk/ios/v4/features/deep-links/deferred.mdx b/src/content/docs/sdk/ios/v4/features/deep-links/deferred.mdx deleted file mode 100644 index c8713a3e5..000000000 --- a/src/content/docs/sdk/ios/v4/features/deep-links/deferred.mdx +++ /dev/null @@ -1,212 +0,0 @@ ---- -title: Set up deferred deep linking -description: Configure deferred deep linking for your app. -slug: en/sdk/ios/v4/features/deep-links/deferred -sidebar-position: 4 -versions: - - label: v5 - value: v5 - default: true - - label: v4 - value: v4 -redirects: - v5: /en/sdk/ios/features/deep-links/deferred ---- - -A deferred deep link sends a user to a place in your app after routing them via the App Store to install the app first. - -## How it works {#how-it-works} - -This is how a deferred deep link works: - -1. The user clicks on an Adjust deep link. -2. Adjust's servers redirect the user to the App Store. -3. The user installs your app and opens it. -4. Adjust's servers perform attribution and send the deep link to the Adjust SDK. -5. Your app displays its preliminary content, such as onboarding screens and user login, if applicable. -6. Your app retrieves the deep link from the Adjust SDK and handles the deep link. - -## Setup {#setup} - - - -When you set up Adjust deferred deep linking, you need to disable deferred deep linking in other SDKs in your app. You also need to disable any deep linking setup from other MMPs. - - - - - -Check out the [Facebook deferred deep linking documentation](https://developers.facebook.com/docs/ios/deep-linking#deferred-deep-linking) for information about how to configure deep linking with your Facebook campaigns. - - - -There are 2 approaches to set up deferred deep linking in your app: - -1. Adjust's servers automatically pass the deferred deep link to the Adjust SDK. When the user opens your app, the Adjust SDK automatically calls the `open(_:options:completionHandler:)` method with the deep link. If your app doesn't have preliminary content (for example: onboarding screens and user login), or if your app already handles this content before opening the deep link, then no further configuration is required, and you can skip the rest of this section. -2. If your app has preliminary content (for example: onboarding screens and user login), but your app doesn't already handle this before handling the deep link, then you can add a deferred deep link listener. - -### Set up a deferred deep link listener {#set-up-a-deferred-deep-link-listener} - -1. Set up a delegate callback for deferred deep linking. If you have already configured attribution callbacks, you can skip this step. - - - - - - -```swift -class AppDelegate: UIResponder, UIApplicationDelegate, AdjustDelegate { -} -``` - - - - - - - - -```objc -@interface AppDelegate : UIResponder -``` - - - - - - -2. If you haven’t already done so, create an instance of the `ADJConfig` class and set a delegate on the `ADJConfig` object in your app delegate. You need to set the delegate in `ADJConfig` before initializing the SDK. - - - - -```swift -let yourAppToken = "{YourAppToken}" -let environment = ADJEnvironmentSandbox as? String -let adjustConfig = ADJConfig( - appToken: yourAppToken, - environment: environment) -adjustConfig?.delegate = self - -// ... - -Adjust.appDidLaunch(adjustConfig) -``` - - - - -```objc -*adjustConfig = [ADJConfig configWithAppToken:@"{YourAppToken}" - environment:ADJEnvironmentSandbox]; -[adjustConfig setDelegate:self]; - -// ... - -[Adjust appDidLaunch:adjustConfig]; -``` - - - - -3. Add the `adjustDeeplinkResponse` deferred deep link callback method to the delegate. The Adjust SDK calls this method after receiving a deferred deep link. - 1. Set your deep link handling code. - 2. Set the return value of the `adjustDeeplinkResponse` method to true or false. This indicates whether you want the Adjust SDK to call the `open(_:options:completionHandler:)` method to open the deep link after your deep link handling code runs. - - - - -```swift -func adjustDeeplinkResponse(_ deeplink: URL?) -> Bool { - // add your code below to handle deep link - // (for example, show onboarding screens, then open deep link content) - // deeplink object contains the deep link - - return false -} -``` - - - - -```objc -- (BOOL)adjustDeeplinkResponse:(NSURL *)deeplink { - // add your code below to handle deep link - // (for example, show onboarding screens, then open deep link content) - // deeplink object contains the deep link - - return NO; -} -``` - - - - -## Set up Adjust LinkMe {#set-up-adjust-linkme} - - - -```objc -@property (nonatomic, assign) BOOL linkMeEnabled; -``` - - - - - -Discuss with your marketing team whether you need to implement LinkMe in your app. - - - -[Adjust’s LinkMe solution](https://help.adjust.com/en/article/linkme) is an optional feature that ensures robust deferred deep linking performance by enabling your app to read deep link information from the device pasteboard. - - - -The Adjust SDK checks the pasteboard when a user opens the app for the first time. The device displays a dialog asking if the user wants to allow the app to read the pasteboard. - - - -When a user clicks on a LinkMe URL they have the option to copy the link information to their system pasteboard. You can use the Adjust SDK to read the system pasteboard for deep link information. If deep link information is present, the Adjust SDK forwards the user to the correct page in your app. - -To enable pasteboard checking in your app, pass a `true` value to the `setLinkMeEnabled` method on your `ADJConfig` object: - - - - -```swift -let yourAppToken = "{YourAppToken}" -let environment = ADJEnvironmentSandbox as? String -let adjustConfig = ADJConfig( - appToken: yourAppToken, - environment: environment) -// ... -adjustConfig?.linkMeEnabled = true -``` - - - - -```objc -NSString *yourAppToken = @"{YourAppToken}"; -NSString *environment = ADJEnvironmentSandbox; -*adjustConfig = [ADJConfig configWithAppToken:yourAppToken - environment:environment]; -/// ... -[adjustConfig setLinkMeEnabled:YES]; -``` - - - - -```js -setupWebViewJavascriptBridge(function (bridge) { - // ... - var yourAppToken = yourAppToken; - var environment = AdjustConfig.EnvironmentSandbox; - var adjustConfig = new AdjustConfig(yourAppToken, environment); - adjustConfig.setLinkMeEnabled(true); -}); -``` - - - diff --git a/src/content/docs/sdk/ios/v4/features/deep-links/direct.mdx b/src/content/docs/sdk/ios/v4/features/deep-links/direct.mdx deleted file mode 100644 index ec45ba9a5..000000000 --- a/src/content/docs/sdk/ios/v4/features/deep-links/direct.mdx +++ /dev/null @@ -1,392 +0,0 @@ ---- -title: Set up direct deep linking -description: Configure direct deep linking for your app. -slug: en/sdk/ios/v4/features/deep-links/direct -sidebar-position: 3 -versions: - - label: v5 - value: v5 - default: true - - label: v4 - value: v4 -redirects: - v5: /en/sdk/ios/features/deep-links/direct ---- - -You can configure deep linking in your app after you have set it up in the Adjust dashboard. **Direct deep linking** occurs when a user has your app installed on their device. The link takes the user to a specific page within your app. - -To enable deep linking, you need to do the following: - -- [ ] Enable Associated Domains for your app -- [ ] Configure your deep links in Xcode - -## Enable Associated Domains {#enable-associated-domains} - -To get started, you need to enable Associated Domains in your Apple Developer account. This allows you to set universal link domains in your app. To do this, follow these steps: - -1. Log in to your [Apple Developer account](https://developer.apple.com/account/). -2. Select **Certificates, IDs & Profiles** in the left-hand menu. -3. Select **Identifiers** in the left-hand menu. -4. Find your app and select it to open the edit page. -5. Ensure that **Associated Domains** is checked under **Capabilities**. -6. Select **Save** to save your changes. - -## Configure deep links in Xcode {#configure-deep-links-in-xcode} - -Follow these steps to add your deep link configuration to your Xcode project. - -### Adjust universal links domain {#adjust-universal-links-domain} - -1. Open your app project in Xcode. -2. Select your project from the left-hand menu. -3. Select your app under **Targets**. -4. Select **Signing & Capabilities** from the top menu. -5. Ensure that **All** is selected in the submenu below. -6. Select the Add option (**+**) to add a capability. -7. Select **Associated Domains**. -8. Enter the Adjust Universal Link domain with the prefix `applinks:` - - Here is an example using the `example.adj.st` domain: `applinks:example.adj.st`. - -### Custom URL scheme {#custom-url-scheme} - - - -Check with your marketing team to see if a custom URL scheme is needed for the app and discuss what the scheme name should be. If your app targets Android devices as well, use the same scheme name for each platform. - - - -1. Open your app project in Xcode. -2. Select your project from the left-hand menu. -3. Select your app under **Targets**. -4. Select **Info** from the top menu. -5. Expand the **URL Types** section. -6. Select the Add option (**+**) to add a URL type. -7. Fill in the following information to create a URL scheme: - - **Identifier**: `$(PRODUCT_BUNDLE_IDENTIFIER)` - - **URL Schemes**: Your custom URL scheme. This must be - unique. Don't use protected schemes such as `http`, `https`, or `mailto`. - - **Role**: Editor - -This scheme will work for your production **and** debug builds. - -## Modify your iOS app {#modify-your-ios-app} - -You need to update your iOS app to set up different deep linking scenarios. How you update your app depends on whether your app uses [scenes](https://developer.apple.com/documentation/uikit/app_and_environment/scenes). - -### App doesn't use scenes {#app-doesn-t-use-scenes} - -If your app doesn't uses scenes, you need to update methods in your app delegate. - -#### Universal links {#universal-links} - -Update the `application(_:continue:restorationHandler:)` method in your app delegate to call the following methods in the Adjust SDK: - -- `ADJLinkResolution.resolveLink`: Call this method only if your marketing team requires the use of Adjust's Link Resolution solution. If the deep link uses a domain that matches an element in the `resolveUrlSuffixArray`, then the method attempts to resolve the deep link, and returns the resolved link. If the deep link doesn't match an element in this array, then the method passes through the original deep link, so you can pass all deep links to this method. -- `Adjust.appWillOpen` - Call this method to send deep links to Adjust's servers to record information. You can pass both Adjust and non-Adjust deep links to this method. Adjust's servers will ignore any deep links that don’t have Adjust parameters. - -When a user clicks on your universal link, iOS opens your app and delivers the deep link to `application(_:continue:restorationHandler:)`. This occurs whether the user has closed your app or has it running in the background. - - - - -```swift -func application( - _ application: UIApplication, - continue userActivity: NSUserActivity, - restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) - -> Bool { - - if userActivity.activityType == NSUserActivityTypeBrowsingWeb { - let incomingURL = userActivity.webpageURL - - // call the below method to resolve deep link - ADJLinkResolution.resolveLink( - withUrl: incomingURL, - resolveUrlSuffixArray: ["email.example.com", "short.example.com"], - callback: { resolvedURL in - // add your code below to handle deep link - // (for example, open deep link content) - // resolvedURL object contains the deep link - - - // call the below method to send deep link to Adjust's servers - Adjust.appWillOpen(resolvedURL) - }) - } else { - return false - } - - return true -} -``` - - - - -```objc -- (BOOL)application:(UIApplication *)application - continueUserActivity:(NSUserActivity *)userActivity - restorationHandler: - (void (^)(NSArray *restorableObjects))restorationHandler { - - if ([[userActivity activityType] - isEqualToString:NSUserActivityTypeBrowsingWeb]) { - NSURL *incomingURL = [userActivity webpageURL]; - - // call the below method to resolve deep link - [ADJLinkResolution - resolveLinkWithUrl:incomingURL - resolveUrlSuffixArray:@[ - @"email.example.com", @"short.example.com" - ] - callback:^(NSURL* _Nullable resolvedURL) { - // add your code below to handle deep link - // (for example, open deep link content) - // resolvedURL object contains the deep link - - - // call the below method to send deep link to Adjust's servers - [Adjust appWillOpenUrl:resolvedURL]; - }]; - } else { - return NO; - } - - return YES; -} -``` - - - - -#### Custom URL scheme {#custom-url-scheme-1} - -If your marketing team requires you to set up custom URL scheme deep links, update the `application(_:open:options:)` method in your app delegate to call the `Adjust.appWillOpen` method in the Adjust SDK. This method sends deep links to Adjust's servers to record them. You can pass both Adjust and non-Adjust deep links to this method. Adjust's servers ignore any deep links that don’t have Adjust parameters. - -When a user clicks on your custom URL scheme deep link, iOS opens your app and delivers the deep link to `application(_:open:options:)`. This occurs whether the user has closed your app or has it running in the background. - - - - -```swift -func application( - _ app: UIApplication, - open incomingURL: URL, - options: [UIApplication.OpenURLOptionsKey: Any] = [:] - ) -> Bool { - - // add your code below to handle deep link - // (for example, open deep link content) - // incomingURL object contains the deep link - - - // call the below method to send deep link to Adjust's servers - Adjust.appWillOpen(incomingURL) - - return true -} -``` - - - - -```objc -- (BOOL)application:(UIApplication *)app - openURL:(NSURL *)incomingURL - options:(NSDictionary *)options { - - // add your code below to handle deep link - // (for example, open deep link content) - // incomingURL object contains the deep link - - - // call the below method to send deep link to Adjust's servers - [Adjust appWillOpenUrl:incomingURL]; - - return YES; -} -``` - - - - -### App uses scenes {#app-uses-scenes} - -If your app uses scenes, you need to update methods in your scene delegate. - -#### Universal links {#universal-links-1} - -1. Update the `scene(_:willConnectTo:options:)` method in your scene delegate. When a user clicks on your universal links and the user has your app closed, iOS opens your app and delivers the deep link to this method. -2. Update the `scene(_:continue:)` method in your scene delegate. When a user clicks on your universal links, and the user has your app running in the background, iOS opens your app and delivers the deep link to this method. - -The above methods call the following methods in the Adjust SDK: - -- `ADJLinkResolution.resolveLink`: Call this method only if your marketing team requires the use of Adjust's Link Resolution solution. If the deep link uses a domain that matches an element in the `resolveUrlSuffixArray`, then the method attempts to resolve the deep link, and returns the resolved link. If the deep link doesn't match an element in this array, then the method passes through the original deep link, so you can pass all deep links to this method. -- `Adjust.appWillOpen` - Call this method to send deep links to Adjust's servers to record them. You can pass both Adjust and non-Adjust deep links to this method. Adjust's servers ignore any deep links that don’t have Adjust parameters. - - - - -```swift -func scene( - _ scene: UIScene, - willConnectTo session: UISceneSession, - options connectionOptions: UIScene.ConnectionOptions - ) { - - guard let userActivity = connectionOptions.userActivities.first, - userActivity.activityType == NSUserActivityTypeBrowsingWeb, - let incomingURL = userActivity.webpageURL - else { return } - - // call the below method to resolve deep link - ADJLinkResolution.resolveLink( - withUrl: incomingURL, - resolveUrlSuffixArray: ["email.example.com", "short.example.com"], - callback: { resolvedURL in - // add your code below to handle deep link - // (for example, open deep link content) - // resolvedURL object contains the deep link - - - // call the below method to send deep link to Adjust's servers - Adjust.appWillOpen(resolvedURL) - }) -} - -func scene( - _ scene: UIScene, - continue userActivity: NSUserActivity) { - - if userActivity.activityType == NSUserActivityTypeBrowsingWeb { - let incomingURL = userActivity.webpageURL - - // call the below method to resolve deep link - ADJLinkResolution.resolveLink( - withUrl: incomingURL, - resolveUrlSuffixArray: ["email.example.com", "short.example.com"], - callback: { resolvedURL in - // add your code below to handle deep link - // (for example, open deep link content) - // resolvedURL object contains the deep link - - - // call the below method to send deep link to Adjust's servers - Adjust.appWillOpen(resolvedURL) - }) - } -} -``` - - - - -```objc -- (void)scene:(UIScene *)scene willConnectToSession:(UISceneSession *)session - options:(UISceneConnectionOptions *)connectionOptions { - - NSUserActivity* userActivity = - [[[connectionOptions userActivities] allObjects] firstObject]; - - if ([[userActivity activityType] - isEqualToString:NSUserActivityTypeBrowsingWeb]) { - NSURL *incomingURL = [userActivity webpageURL]; - - // call the below method to resolve deep link - [ADJLinkResolution - resolveLinkWithUrl:incomingURL - resolveUrlSuffixArray:@[ - @"email.example.com", @"short.example.com" - ] - callback:^(NSURL* _Nullable resolvedURL) { - // add your code below to handle deep link - // (for example, open deep link content) - // resolvedURL object contains the deep link - - - // call the below method to send deep link to Adjust's servers - [Adjust appWillOpenUrl:resolvedURL]; - }]; - } -} - -- (void)scene:(UIScene *)scene continueUserActivity:(NSUserActivity *)userActivity{ - if ([[userActivity activityType] - isEqualToString:NSUserActivityTypeBrowsingWeb]) { - NSURL *incomingURL = [userActivity webpageURL]; - - // call the below method to resolve deep link - [ADJLinkResolution - resolveLinkWithUrl:incomingURL - resolveUrlSuffixArray:@[ - @"email.example.com", @"short.example.com" - ] - callback:^(NSURL* _Nullable resolvedURL) { - // add your code below to handle deep link - // (for example, open deep link content) - // resolvedURL object contains the deep link - - - // call the below method to send deep link to Adjust's servers - [Adjust appWillOpenUrl:resolvedURL]; - }]; - } -} -``` - - - - -#### Custom URL scheme {#custom-url-scheme-2} - -1. Update the `scene(_:willConnectTo:options:)` method in your scene delegate. When a user clicks on your custom URL scheme deep link and the user has your app closed, iOS opens your app and delivers the deep link to this method. -2. Update the `scene(_:openURLContexts:)` method in your scene delegate. When a user clicks on your custom URL scheme deep link, and the user has your app running in the background, iOS opens your app and delivers the deep link to this method. - -These methods call the `Adjust.appWillOpen` method in the Adjust SDK. This method sends deep links to Adjust's servers to recordd them. You can pass both Adjust and non-Adjust deep links to this method. Adjust's servers ignore any deep links that don’t have Adjust parameters. - - - - -```swift -func scene( - _ scene: UIScene, - openURLContexts URLContexts: Set - ) { - - guard let incomingURL = URLContexts.first?.url else { - return - } - - // add your code below to handle deep link - // (for example, open deep link content) - // incomingURL object contains the deep link - - - // call the below method to send deep link to Adjust's servers - Adjust.appWillOpen(incomingURL) -} -``` - - - - -```objc -- (void)scene:(UIScene *)scene - openURLContexts:(nonnull NSSet *)URLContexts { - - NSURL *incomingURL = [[URLContexts allObjects] firstObject].URL; - - if (incomingURL) { - // add your code below to handle deep link - // (for example, open deep link content) - // incomingURL object contains the deep link - - - // call the below method to send deep link to Adjust's servers - [Adjust appWillOpenUrl:incomingURL]; - } -} -``` - - - diff --git a/src/content/docs/sdk/ios/v4/features/deep-links/index.mdx b/src/content/docs/sdk/ios/v4/features/deep-links/index.mdx index 5e417e2fe..33ad6652b 100644 --- a/src/content/docs/sdk/ios/v4/features/deep-links/index.mdx +++ b/src/content/docs/sdk/ios/v4/features/deep-links/index.mdx @@ -13,10 +13,3 @@ versions: redirects: v5: /en/sdk/ios/features/deep-links --- - -You can create deep links to take users to specific pages in your app. The Adjust SDK uses different logic depending on if the user already has your app installed on their device: - -- Direct deep linking: occurs if the user already has your app installed. The link takes the user to the page specified in the link -- Deferred deep linking: occurs if the user doesn't have your app installed. The link takes the user to a storefront to install your app first. After the user installs the app, it opens to the page specified in the link. - -The SDK can read deep link data after a user opens your app from a link. Follow the steps in this section to get started. diff --git a/src/content/docs/sdk/ios/v4/features/deep-links/set-up-deep-linking.mdx b/src/content/docs/sdk/ios/v4/features/deep-links/set-up-deep-linking.mdx index 7917cf300..9f844eb9e 100644 --- a/src/content/docs/sdk/ios/v4/features/deep-links/set-up-deep-linking.mdx +++ b/src/content/docs/sdk/ios/v4/features/deep-links/set-up-deep-linking.mdx @@ -24,12 +24,6 @@ In addition, implement deep link handling logic: - [Deep Link Handler](#deep-link-handler) - - -If you're using the Facebook SDK for deferred deep linking, disable or remove its [deferred deep linking code](https://developers.facebook.com/docs/ios/deep-linking#deferred-deep-linking) to avoid conflicts with Adjust SDK's deferred deep link handling. - - - ## UIKit apps using AppDelegate lifecycle Update your AppDelegate to implement direct and deferred deep linking. @@ -41,7 +35,6 @@ Update your AppDelegate to implement direct and deferred deep linking. ```swift import Adjust -import Foundation import UIKit @UIApplicationMain @@ -132,7 +125,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, NotificationCenter.default.post(name: .deferredLinkReceived, object: nil) } - // Return true to let Adjust SDK immediately attempt to open deep link + // Return true to let Adjust SDK attempt to open deep link immediately // upon receipt (for example: app has no ATT, onboarding screens, or login). // Otherwise, return false. return false @@ -149,13 +142,8 @@ class AppDelegate: UIResponder, UIApplicationDelegate, ```objc #import -#import -#import - -@class DeeplinkHandler; - -@interface AppDelegate : UIResponder -@end +#import "AppDelegate.h" +#import "DeeplinkHandler.h" @implementation AppDelegate @@ -203,17 +191,20 @@ class AppDelegate: UIResponder, UIApplicationDelegate, if ([userActivity.activityType isEqualToString:NSUserActivityTypeBrowsingWeb]) { NSURL *incomingLink = userActivity.webpageURL; - // Handle incoming universal link - [DeeplinkHandler handleDeeplink:incomingLink]; + if (incomingLink) { + // Handle incoming universal link + [DeeplinkHandler handleDeeplink:incomingLink]; + } } return YES; } // Receive app scheme deep link when app is installed, and either: // - App is in "not running" or background state, and user clicks link or -// another app opens link using [UIApplication openURL:options:completionHandler:], or +// another app opens link using [UIApplication +// openURL:options:completionHandler:], or // - App is in foreground state and opens its own link using -// [UIApplication openURL:options:completionHandler:]. +// [UIApplication openURL:options:completionHandler:]. - (BOOL)application:(UIApplication *)app openURL:(NSURL *)incomingLink options: @@ -230,8 +221,14 @@ class AppDelegate: UIResponder, UIApplicationDelegate, // onboarding screens and login. [[NSUserDefaults standardUserDefaults] setObject:[deeplink absoluteString] forKey:@"lastDeferredLink"]; + + // Post notification for app to handle deferred deep link + [[NSNotificationCenter defaultCenter] + postNotificationName:@"DeferredLinkReceived" + object:nil]; } - // Return YES to try to open deep link immediately + + // Return YES to let Adjust SDK attempt to open deep link immediately // upon receipt (for example: app has no ATT, onboarding screens, or login). // Otherwise, return NO. return NO; @@ -253,7 +250,7 @@ For deferred deep linking, this is the sequence for most apps: 4. After the ATT prompt response or timeout, the Adjust SDK sends session and attribution requests to Adjust's servers. 5. Adjust's servers respond with attribution data, including the deep link the user clicked on ("deferred deep link"). 6. The Adjust SDK triggers a deferred deep link callback in your app, which stores the deferred deep link (shown in the AppDelegate implementation above). -7. The app handles the deferred deep link in one of two ways (shown in the ContentView example class below): +7. The app handles the deferred deep link in one of two ways (shown in the ViewController example class below): - Once onboarding completes, the app checks for and handles any stored deferred deep link. - If a deferred deep link arrives after onboarding, a notification triggers the same check and handling. 8. The app navigates the user to the deep link screen. @@ -265,7 +262,6 @@ For deferred deep linking, this is the sequence for most apps: ```swift import Adjust -import Foundation import UIKit class ViewController: UIViewController { @@ -291,8 +287,8 @@ class ViewController: UIViewController { ) } - override func viewDidAppear() { - super.viewDidAppear() + override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) // Check if onboarding has been completed if !hasCompletedOnboarding { // Show onboarding screens and login prompt @@ -354,20 +350,8 @@ extension Notification.Name { ```objc #import -#import -#import - -@class DeeplinkHandler; - -@interface ViewController : UIViewController - -@end - -@interface ViewController () - -@property(nonatomic, assign) BOOL hasCompletedOnboarding; - -@end +#import "DeeplinkHandler.h" +#import "ViewController.h" @implementation ViewController @@ -381,40 +365,67 @@ extension Notification.Name { forKey:@"HasCompletedOnboarding"]; } -- (void)viewDidAppear { - [super viewDidAppear]; +- (void)viewDidLoad { + [super viewDidLoad]; + + // Observe deferred deep link notifications + [[NSNotificationCenter defaultCenter] + addObserver:self + selector:@selector(onDeferredLinkReceived) + name:@"deferredLinkReceived" + object:nil]; +} + +- (void)viewDidAppear:(BOOL)animated { + [super viewDidAppear:animated]; // Check if onboarding has been completed if (!self.hasCompletedOnboarding) { - // Show ATT prompt after delay to ensure app is active - dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), - dispatch_get_main_queue(), ^{ - [Adjust - requestTrackingAuthorizationWithCompletionHandler:nil]; - }); - // Show onboarding screens and login prompt - // On completion, set hasCompletedOnboarding to true + // Show ATT prompt after delay to ensure app is active + dispatch_after( + dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), + dispatch_get_main_queue(), ^{ + [Adjust requestTrackingAuthorizationWithCompletionHandler:nil]; + }); + + // After onboarding, set hasCompletedOnboarding to true + // and check for stored deferred deep link self.hasCompletedOnboarding = YES; + [self checkForDeferredLink]; + } else { + // Show main content + } +} + +- (void)onDeferredLinkReceived { + // Check for deferred deep link that may arrive after onboarding + if (self.hasCompletedOnboarding) { + [self checkForDeferredLink]; } +} - // Check if there's a stored deferred deep link +- (void)checkForDeferredLink { + // Check for stored deferred deep link before handling NSString *deferredLinkString = [[NSUserDefaults standardUserDefaults] stringForKey:@"lastDeferredLink"]; NSURL *deferredLink = [NSURL URLWithString:deferredLinkString]; - if (deferredLink) { - // Remove the stored deferred deep link - // to avoid handling it again later - [[NSUserDefaults standardUserDefaults] - removeObjectForKey:@"lastDeferredLink"]; - - // Handle deferred deep link - [DeeplinkHandler handleDeeplink:deferredLink]; - } else { - // Show main content + if (!deferredLink) { + return; } + + // Remove stored deferred deep link to avoid handling again later + [[NSUserDefaults standardUserDefaults] + removeObjectForKey:@"lastDeferredLink"]; + + // Handle deferred deep link + [DeeplinkHandler handleDeeplink:deferredLink]; +} + +- (void)dealloc { + [[NSNotificationCenter defaultCenter] removeObserver:self]; } @end @@ -439,7 +450,6 @@ Follow the steps in the [UIKit apps using AppDelegate lifecycle section](#uikit- ```swift -import Foundation import UIKit class SceneDelegate: UIResponder, UIWindowSceneDelegate { @@ -508,11 +518,8 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { ```objc -#import -#import - -@interface SceneDelegate : UIResponder -@end +#import "DeeplinkHandler.h" +#import "SceneDelegate.h" @implementation SceneDelegate @@ -634,7 +641,6 @@ Update your AppDelegate to implement deferred deep linking. ```swift import Adjust -import Foundation import UIKit class AppDelegate: UIResponder, UIApplicationDelegate, AdjustDelegate { @@ -688,7 +694,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, AdjustDelegate { NotificationCenter.default.post(name: .deferredLinkReceived, object: nil) } - // Return true to let Adjust SDK immediately attempt to open deep link + // Return true to let Adjust SDK attempt to open deep link immediately // upon receipt (for example: app has no ATT, onboarding screens, or login). // Otherwise, return false. return false @@ -868,7 +874,6 @@ The class performs the following tasks: ```swift import Adjust -import Foundation // import UIKit and/or SwiftUI class DeeplinkHandler { @@ -975,12 +980,8 @@ class DeeplinkHandler { ```objc #import -#import -#import - -@interface DeeplinkHandler : NSObject -+ (void)handleDeeplink:(NSURL *)incomingLink; -@end +#import "DeeplinkDetailViewController.h" +#import "DeeplinkHandler.h" @implementation DeeplinkHandler diff --git a/src/content/docs/sdk/ios/v4/features/deep-links/testing.mdx b/src/content/docs/sdk/ios/v4/features/deep-links/testing.mdx index 4d4bf4479..e76b0ecd3 100644 --- a/src/content/docs/sdk/ios/v4/features/deep-links/testing.mdx +++ b/src/content/docs/sdk/ios/v4/features/deep-links/testing.mdx @@ -2,7 +2,7 @@ title: Test deep linking description: Test your deep links to ensure they work as expected. slug: en/sdk/ios/v4/features/deep-links/testing -sidebar-position: 6 +sidebar-position: 4 versions: - label: v5 value: v5 From d6163371ef978c291fe9b0b943077ace519e6c05 Mon Sep 17 00:00:00 2001 From: Dave Mead Date: Thu, 28 Nov 2024 02:59:13 -0800 Subject: [PATCH 15/27] Fix sidebar value --- src/content/docs/sdk/ios/v4/features/deep-links/resolution.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/content/docs/sdk/ios/v4/features/deep-links/resolution.mdx b/src/content/docs/sdk/ios/v4/features/deep-links/resolution.mdx index f8dda7614..a69140003 100644 --- a/src/content/docs/sdk/ios/v4/features/deep-links/resolution.mdx +++ b/src/content/docs/sdk/ios/v4/features/deep-links/resolution.mdx @@ -4,7 +4,7 @@ description: Set up link resolution for deep linking via email, SMS, QR codes, and platforms that shorten links. slug: en/sdk/ios/v4/features/deep-links/resolution -sidebar-position: 5 +sidebar-position: 3 versions: - label: v5 value: v5 From dc950dd1dc5de3f95eb8130c61841f49eb207643 Mon Sep 17 00:00:00 2001 From: Dave Mead Date: Sat, 30 Nov 2024 17:18:40 -0800 Subject: [PATCH 16/27] Add v5 version. Fix sidebar order. --- .../configure-deep-link-settings.mdx | 2 +- .../ios/v4/features/deep-links/resolution.mdx | 4 +- .../ios/v5/features/deep-links/deferred.mdx | 199 --- .../sdk/ios/v5/features/deep-links/direct.mdx | 398 ------ .../sdk/ios/v5/features/deep-links/index.mdx | 7 - .../ios/v5/features/deep-links/resolution.mdx | 6 +- .../deep-links/set-up-deep-linking.mdx | 1074 +++++++++++++++++ .../ios/v5/features/deep-links/testing.mdx | 2 +- .../navigation/buildSidebarHierarchy.ts | 47 +- 9 files changed, 1102 insertions(+), 637 deletions(-) delete mode 100644 src/content/docs/sdk/ios/v5/features/deep-links/deferred.mdx delete mode 100644 src/content/docs/sdk/ios/v5/features/deep-links/direct.mdx create mode 100644 src/content/docs/sdk/ios/v5/features/deep-links/set-up-deep-linking.mdx diff --git a/src/content/docs/sdk/ios/v4/features/deep-links/configure-deep-link-settings.mdx b/src/content/docs/sdk/ios/v4/features/deep-links/configure-deep-link-settings.mdx index 8a08a8062..0e015f38f 100644 --- a/src/content/docs/sdk/ios/v4/features/deep-links/configure-deep-link-settings.mdx +++ b/src/content/docs/sdk/ios/v4/features/deep-links/configure-deep-link-settings.mdx @@ -27,7 +27,7 @@ Copy your branded domain to configure it in Xcode in the section below.
### Configure universal links -1. Open your Xcode project/workspace (use the .xcworkspace file if you are using Cocoapods, otherwise use the .xcodeproj file). +1. Open your Xcode project. 2. In the navigator pane, select the project name to access the project settings. 3. In the project settings, under **Targets**, select the appropriate target (usually your app's name). 4. Select the **Signing & Capabilities** tab. diff --git a/src/content/docs/sdk/ios/v4/features/deep-links/resolution.mdx b/src/content/docs/sdk/ios/v4/features/deep-links/resolution.mdx index a69140003..f5227fdc1 100644 --- a/src/content/docs/sdk/ios/v4/features/deep-links/resolution.mdx +++ b/src/content/docs/sdk/ios/v4/features/deep-links/resolution.mdx @@ -1,8 +1,6 @@ --- title: Link resolution -description: - Set up link resolution for deep linking via email, SMS, QR codes, and - platforms that shorten links. +description: Set up link resolution for deep linking via email, SMS, QR codes, and platforms that shorten links. slug: en/sdk/ios/v4/features/deep-links/resolution sidebar-position: 3 versions: diff --git a/src/content/docs/sdk/ios/v5/features/deep-links/deferred.mdx b/src/content/docs/sdk/ios/v5/features/deep-links/deferred.mdx deleted file mode 100644 index 91a661369..000000000 --- a/src/content/docs/sdk/ios/v5/features/deep-links/deferred.mdx +++ /dev/null @@ -1,199 +0,0 @@ ---- -title: Set up deferred deep linking -description: Configure deferred deep linking for your app. -slug: en/sdk/ios/features/deep-links/deferred -sidebar-position: 4 -versions: - - label: v5 - value: v5 - default: true - - label: v4 - value: v4 -redirects: - v4: /en/sdk/ios/v4/features/deep-links/deferred ---- - -A deferred deep link sends a user to a place in your app after routing them via the App Store to install the app first. - -## How it works {#how-it-works} - -This is how a deferred deep link works: - -1. The user clicks on an Adjust deep link. -2. Adjust's servers redirect the user to the App Store. -3. The user installs your app and opens it. -4. Adjust's servers perform attribution and send the deep link to the Adjust SDK. -5. Your app displays its preliminary content, such as onboarding screens and user login, if applicable. -6. Your app retrieves the deep link from the Adjust SDK and handles the deep link. - -## Setup {#setup} - - - -When you set up Adjust deferred deep linking, you need to disable deferred deep linking in other SDKs in your app. You also need to disable any deep linking setup from other MMPs. - - - - - -Check out the [Facebook deferred deep linking documentation](https://developers.facebook.com/docs/ios/deep-linking#deferred-deep-linking) for information about how to configure deep linking with your Facebook campaigns. - - - -There are 2 approaches to set up deferred deep linking in your app: - -1. Adjust's servers automatically pass the deferred deep link to the Adjust SDK. When the user opens your app, the Adjust SDK automatically calls the `open(_:options:completionHandler:)` method with the deep link. If your app doesn't have preliminary content (for example: onboarding screens and user login), or if your app already handles this content before opening the deep link, then no further configuration is required, and you can skip the rest of this section. -2. If your app has preliminary content (for example: onboarding screens and user login), but your app doesn't already handle this before handling the deep link, then you can add a deferred deep link listener. - -### Set up a deferred deep link listener {#set-up-a-deferred-deep-link-listener} - -1. Set up a delegate callback for deferred deep linking. If you have already configured attribution callbacks, you can skip this step. - - - - - - -```swift -class AppDelegate: UIResponder, UIApplicationDelegate, AdjustDelegate { -} -``` - - - - - - - - -```objc -@interface AppDelegate : UIResponder -``` - - - - - - -2. If you haven’t already done so, create an instance of the `ADJConfig` class and set a delegate on the `ADJConfig` object in your app delegate. You need to set the delegate in `ADJConfig` before initializing the SDK. - - - - -```swift -let yourAppToken = "{YourAppToken}" -let environment = ADJEnvironmentSandbox -let adjustConfig = ADJConfig( - appToken: yourAppToken, - environment: environment) -adjustConfig?.delegate = self - -// ... - -Adjust.initSdk(adjustConfig) -``` - - - - -```objc -ADJConfig *adjustConfig = [[ADJConfig alloc] initWithAppToken:@"{YourAppToken}" - environment:ADJEnvironmentSandbox]; -[adjustConfig setDelegate:self]; - -// ... - -[Adjust initSdk:adjustConfig]; -``` - - - - -3. Add the `adjustDeferredDeeplinkReceived` deferred deep link callback method to the delegate. The Adjust SDK calls this method after receiving a deferred deep link. - 1. Set your deep link handling code. - 2. Set the return value of the `adjustDeferredDeeplinkReceived` method to true or false. This indicates whether you want the Adjust SDK to call the `open(_:options:completionHandler:)` method to open the deep link after your deep link handling code runs. - - - - -```swift -func adjustDeferredDeeplinkReceived(_ deeplink: URL?) -> Bool { - // add your code below to handle deep link - // (for example, show onboarding screens, then open deep link content) - // deeplink object contains the deep link - - return false -} -``` - - - - -```objc -- (BOOL)adjustDeferredDeeplinkReceived:(NSURL *)deeplink { - // add your code below to handle deep link - // (for example, show onboarding screens, then open deep link content) - // deeplink object contains the deep link - - return NO; -} -``` - - - - -## Set up Adjust LinkMe {#set-up-adjust-linkme} - - - -```objc -- (void)enableLinkMe; -``` - - - - - -Discuss with your marketing team whether you need to implement LinkMe in your app. - - - -[Adjust’s LinkMe solution](https://help.adjust.com/en/article/linkme) is an optional feature that ensures robust deferred deep linking performance by enabling your app to read deep link information from the device pasteboard. - - - -The Adjust SDK checks the pasteboard when a user opens the app for the first time. The device displays a dialog asking if the user wants to allow the app to read the pasteboard. - - - -When a user clicks on a LinkMe URL they have the option to copy the link information to their system pasteboard. You can use the Adjust SDK to read the system pasteboard for deep link information. If deep link information is present, the Adjust SDK forwards the user to the correct page in your app. - -To enable pasteboard checking in your app, call the `[ADJConfig enableLinkMe]` method. - - - - -```swift -let yourAppToken = "{YourAppToken}" -let environment = ADJEnvironmentSandbox -let adjustConfig = ADJConfig( - appToken: yourAppToken, - environment: environment) -// ... -adjustConfig.enableLinkMe() -``` - - - - -```objc -NSString *yourAppToken = @"{YourAppToken}"; -NSString *environment = ADJEnvironmentSandbox; -ADJConfig *adjustConfig = [[ADJConfig alloc] initWithAppToken:@"{YourAppToken}" - environment:ADJEnvironmentSandbox]; -/// ... -[adjustConfig enableLinkMe]; -``` - - - diff --git a/src/content/docs/sdk/ios/v5/features/deep-links/direct.mdx b/src/content/docs/sdk/ios/v5/features/deep-links/direct.mdx deleted file mode 100644 index 8fcde1dcf..000000000 --- a/src/content/docs/sdk/ios/v5/features/deep-links/direct.mdx +++ /dev/null @@ -1,398 +0,0 @@ ---- -title: Set up direct deep linking -description: Configure direct deep linking for your app. -slug: en/sdk/ios/features/deep-links/direct -sidebar-position: 3 -versions: - - label: v5 - value: v5 - default: true - - label: v4 - value: v4 -redirects: - v4: /en/sdk/ios/v4/features/deep-links/direct ---- - -You can configure deep linking in your app after you have set it up in the Adjust dashboard. **Direct deep linking** occurs when a user has your app installed on their device. The link takes the user to a specific page within your app. - -To enable deep linking, you need to do the following: - -- [ ] Enable Associated Domains for your app -- [ ] Configure your deep links in Xcode - -## Enable Associated Domains {#enable-associated-domains} - -To get started, you need to enable Associated Domains in your Apple Developer account. This allows you to set universal link domains in your app. To do this, follow these steps: - -1. Log in to your [Apple Developer account](https://developer.apple.com/account/). -2. Select **Certificates, IDs & Profiles** in the left-hand menu. -3. Select **Identifiers** in the left-hand menu. -4. Find your app and select it to open the edit page. -5. Ensure that **Associated Domains** is checked under **Capabilities**. -6. Select **Save** to save your changes. - -## Configure deep links in Xcode {#configure-deep-links-in-xcode} - -Follow these steps to add your deep link configuration to your Xcode project. - -### Adjust universal links domain {#adjust-universal-links-domain} - -1. Open your app project in Xcode. -2. Select your project from the left-hand menu. -3. Select your app under **Targets**. -4. Select **Signing & Capabilities** from the top menu. -5. Ensure that **All** is selected in the submenu below. -6. Select the Add option (**+**) to add a capability. -7. Select **Associated Domains**. -8. Enter the Adjust Universal Link domain with the prefix `applinks:` - - Here is an example using the `example.adj.st` domain: `applinks:example.adj.st`. - -### Custom URL scheme {#custom-url-scheme} - - - -Check with your marketing team to see if a custom URL scheme is needed for the app and discuss what the scheme name should be. If your app targets Android devices as well, use the same scheme name for each platform. - - - -1. Open your app project in Xcode. -2. Select your project from the left-hand menu. -3. Select your app under **Targets**. -4. Select **Info** from the top menu. -5. Expand the **URL Types** section. -6. Select the Add option (**+**) to add a URL type. -7. Fill in the following information to create a URL scheme: - - **Identifier**: `$(PRODUCT_BUNDLE_IDENTIFIER)` - - **URL Schemes**: Your custom URL scheme. This must be - unique. Don't use protected schemes such as `http`, `https`, or `mailto`. - - **Role**: Editor - -This scheme will work for your production **and** debug builds. - -## Modify your iOS app {#modify-your-ios-app} - -You need to update your iOS app to set up different deep linking scenarios. How you update your app depends on whether your app uses [scenes](https://developer.apple.com/documentation/uikit/app_and_environment/scenes). - -### App doesn't use scenes {#app-doesn-t-use-scenes} - -If your app doesn't uses scenes, you need to update methods in your app delegate. - -#### Universal links {#universal-links} - -Update the `application(_:continue:restorationHandler:)` method in your app delegate to call the following methods in the Adjust SDK: - -- `ADJLinkResolution.resolveLink`: Call this method only if your marketing team requires the use of Adjust's Link Resolution solution. If the deep link uses a domain that matches an element in the `resolveUrlSuffixArray`, then the method attempts to resolve the deep link, and returns the resolved link. If the deep link doesn't match an element in this array, then the method passes through the original deep link, so you can pass all deep links to this method. -- `Adjust.processDeeplink` - Call this method to send deep links to Adjust's servers to record information. You can pass both Adjust and non-Adjust deep links to this method. Adjust's servers will ignore any deep links that don’t have Adjust parameters. - -When a user clicks on your universal link, iOS opens your app and delivers the deep link to `application(_:continue:restorationHandler:)`. This occurs whether the user has closed your app or has it running in the background. - - - - -```swift -func application( - _ application: UIApplication, - continue userActivity: NSUserActivity, - restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) - -> Bool { - - if userActivity.activityType == NSUserActivityTypeBrowsingWeb { - let incomingURL = userActivity.webpageURL - - // call the below method to resolve deep link - ADJLinkResolution.resolveLink( - withUrl: incomingURL, - resolveUrlSuffixArray: ["email.example.com", "short.example.com"], - callback: { resolvedURL in - // add your code below to handle deep link - // (for example, open deep link content) - // resolvedURL object contains the deep link - - - // call the below method to send deep link to Adjust's servers - let deeplink = ADJDeeplink(deeplink: resolvedURL)! - Adjust.processDeeplink(deeplink) - }) - } else { - return false - } - - return true -} -``` - - - - -```objc -- (BOOL)application:(UIApplication *)application - continueUserActivity:(NSUserActivity *)userActivity - restorationHandler: - (void (^)(NSArray *restorableObjects))restorationHandler { - - if ([[userActivity activityType] - isEqualToString:NSUserActivityTypeBrowsingWeb]) { - NSURL *incomingURL = [userActivity webpageURL]; - - // call the below method to resolve deep link - [ADJLinkResolution - resolveLinkWithUrl:incomingURL - resolveUrlSuffixArray:@[ - @"email.example.com", @"short.example.com" - ] - callback:^(NSURL* _Nullable resolvedURL) { - // add your code below to handle deep link - // (for example, open deep link content) - // resolvedURL object contains the deep link - - - // call the below method to send deep link to Adjust's servers - ADJDeeplink *deeplink = [[ADJDeeplink alloc] initWithDeeplink:resolvedURL]; - [Adjust processDeeplink:deeplink]; - }]; - } else { - return NO; - } - - return YES; -} -``` - - - - -#### Custom URL scheme {#custom-url-scheme-1} - -If your marketing team requires you to set up custom URL scheme deep links, update the `application(_:open:options:)` method in your app delegate to call the `Adjust.processDeeplink` method in the Adjust SDK. This method sends deep links to Adjust's servers to record them. You can pass both Adjust and non-Adjust deep links to this method. Adjust's servers ignore any deep links that don’t have Adjust parameters. - -When a user clicks on your custom URL scheme deep link, iOS opens your app and delivers the deep link to `application(_:open:options:)`. This occurs whether the user has closed your app or has it running in the background. - - - - -```swift -func application( - _ app: UIApplication, - open incomingURL: URL, - options: [UIApplication.OpenURLOptionsKey: Any] = [:] - ) -> Bool { - - // add your code below to handle deep link - // (for example, open deep link content) - // incomingURL object contains the deep link - - - // call the below method to send deep link to Adjust's servers - Adjust.processDeeplink(ADJDeeplink(deeplink: incomingURL)) - - return true -} -``` - - - - -```objc -- (BOOL)application:(UIApplication *)app - openURL:(NSURL *)incomingURL - options:(NSDictionary *)options { - - // add your code below to handle deep link - // (for example, open deep link content) - // incomingURL object contains the deep link - - - // call the below method to send deep link to Adjust's servers - [Adjust processDeeplink:[[ADJDeeplink alloc] initWithDeeplink:incomingURL]]; - - return YES; -} -``` - - - - -### App uses scenes {#app-uses-scenes} - -If your app uses scenes, you need to update methods in your scene delegate. - -#### Universal links {#universal-links-1} - -1. Update the `scene(_:willConnectTo:options:)` method in your scene delegate. When a user clicks on your universal links and the user has your app closed, iOS opens your app and delivers the deep link to this method. -2. Update the `scene(_:continue:)` method in your scene delegate. When a user clicks on your universal links, and the user has your app running in the background, iOS opens your app and delivers the deep link to this method. - -The above methods call the following methods in the Adjust SDK: - -- `ADJLinkResolution.resolveLink`: Call this method only if your marketing team requires the use of Adjust's Link Resolution solution. If the deep link uses a domain that matches an element in the `resolveUrlSuffixArray`, then the method attempts to resolve the deep link, and returns the resolved link. If the deep link doesn't match an element in this array, then the method passes through the original deep link, so you can pass all deep links to this method. -- `Adjust.processDeeplink` - Call this method to send deep links to Adjust's servers to record them. You can pass both Adjust and non-Adjust deep links to this method. Adjust's servers ignore any deep links that don’t have Adjust parameters. - - - - -```swift -func scene( - _ scene: UIScene, - willConnectTo session: UISceneSession, - options connectionOptions: UIScene.ConnectionOptions - ) { - - guard let userActivity = connectionOptions.userActivities.first, - userActivity.activityType == NSUserActivityTypeBrowsingWeb, - let incomingURL = userActivity.webpageURL - else { return } - - // call the below method to resolve deep link - ADJLinkResolution.resolveLink( - withUrl: incomingURL, - resolveUrlSuffixArray: ["email.example.com", "short.example.com"], - callback: { resolvedURL in - // add your code below to handle deep link - // (for example, open deep link content) - // resolvedURL object contains the deep link - - - // call the below method to send deep link to Adjust's servers - let deeplink = ADJDeeplink(deeplink: resolvedURL)! - Adjust.processDeeplink(deeplink) - }) -} - -func scene( - _ scene: UIScene, - continue userActivity: NSUserActivity) { - - if userActivity.activityType == NSUserActivityTypeBrowsingWeb { - let incomingURL = userActivity.webpageURL - - // call the below method to resolve deep link - ADJLinkResolution.resolveLink( - withUrl: incomingURL, - resolveUrlSuffixArray: ["email.example.com", "short.example.com"], - callback: { resolvedURL in - // add your code below to handle deep link - // (for example, open deep link content) - // resolvedURL object contains the deep link - - - // call the below method to send deep link to Adjust's servers - let deeplink = ADJDeeplink(deeplink: resolvedURL)! - Adjust.processDeeplink(deeplink) - }) - } -} -``` - - - - -```objc -- (void)scene:(UIScene *)scene willConnectToSession:(UISceneSession *)session - options:(UISceneConnectionOptions *)connectionOptions { - - NSUserActivity* userActivity = - [[[connectionOptions userActivities] allObjects] firstObject]; - - if ([[userActivity activityType] - isEqualToString:NSUserActivityTypeBrowsingWeb]) { - NSURL *incomingURL = [userActivity webpageURL]; - - // call the below method to resolve deep link - [ADJLinkResolution - resolveLinkWithUrl:incomingURL - resolveUrlSuffixArray:@[ - @"email.example.com", @"short.example.com" - ] - callback:^(NSURL* _Nullable resolvedURL) { - // add your code below to handle deep link - // (for example, open deep link content) - // resolvedURL object contains the deep link - - - // call the below method to send deep link to Adjust's servers - ADJDeeplink *deeplink = [[ADJDeeplink alloc] initWithDeeplink:resolvedURL]; - [Adjust processDeeplink:deeplink]; - }]; - } -} - -- (void)scene:(UIScene *)scene continueUserActivity:(NSUserActivity *)userActivity{ - if ([[userActivity activityType] - isEqualToString:NSUserActivityTypeBrowsingWeb]) { - NSURL *incomingURL = [userActivity webpageURL]; - - // call the below method to resolve deep link - [ADJLinkResolution - resolveLinkWithUrl:incomingURL - resolveUrlSuffixArray:@[ - @"email.example.com", @"short.example.com" - ] - callback:^(NSURL* _Nullable resolvedURL) { - // add your code below to handle deep link - // (for example, open deep link content) - // resolvedURL object contains the deep link - - - // call the below method to send deep link to Adjust's servers - ADJDeeplink *deeplink = [[ADJDeeplink alloc] initWithDeeplink:resolvedURL]; - [Adjust processDeeplink:deeplink]; - }]; - } -} -``` - - - - -#### Custom URL scheme {#custom-url-scheme-2} - -1. Update the `scene(_:willConnectTo:options:)` method in your scene delegate. When a user clicks on your custom URL scheme deep link and the user has your app closed, iOS opens your app and delivers the deep link to this method. -2. Update the `scene(_:openURLContexts:)` method in your scene delegate. When a user clicks on your custom URL scheme deep link, and the user has your app running in the background, iOS opens your app and delivers the deep link to this method. - -These methods call the `Adjust.appWillOpen` method in the Adjust SDK. This method sends deep links to Adjust's servers to recordd them. You can pass both Adjust and non-Adjust deep links to this method. Adjust's servers ignore any deep links that don’t have Adjust parameters. - - - - -```swift -func scene( - _ scene: UIScene, - openURLContexts URLContexts: Set - ) { - - guard let incomingURL = URLContexts.first?.url else { - return - } - - // add your code below to handle deep link - // (for example, open deep link content) - // incomingURL object contains the deep link - - - // call the below method to send deep link to Adjust's servers - Adjust.processDeeplink(incomingURL) -} -``` - - - - -```objc -- (void)scene:(UIScene *)scene - openURLContexts:(nonnull NSSet *)URLContexts { - - NSURL *incomingURL = [[URLContexts allObjects] firstObject].URL; - - if (incomingURL) { - // add your code below to handle deep link - // (for example, open deep link content) - // incomingURL object contains the deep link - - - // call the below method to send deep link to Adjust's servers - [Adjust processDeeplink:incomingURL]; - } -} -``` - - - diff --git a/src/content/docs/sdk/ios/v5/features/deep-links/index.mdx b/src/content/docs/sdk/ios/v5/features/deep-links/index.mdx index e30dee6d6..43f6953ac 100644 --- a/src/content/docs/sdk/ios/v5/features/deep-links/index.mdx +++ b/src/content/docs/sdk/ios/v5/features/deep-links/index.mdx @@ -13,10 +13,3 @@ versions: redirects: v4: /en/sdk/ios/v4/features/deep-links --- - -You can create deep links to take users to specific pages in your app. The Adjust SDK uses different logic depending on if the user already has your app installed on their device: - -- Direct deep linking: occurs if the user already has your app installed. The link takes the user to the page specified in the link -- Deferred deep linking: occurs if the user doesn't have your app installed. The link takes the user to a storefront to install your app first. After the user installs the app, it opens to the page specified in the link. - -The SDK can read deep link data after a user opens your app from a link. Follow the steps in this section to get started. diff --git a/src/content/docs/sdk/ios/v5/features/deep-links/resolution.mdx b/src/content/docs/sdk/ios/v5/features/deep-links/resolution.mdx index 37ea23798..160b4b461 100644 --- a/src/content/docs/sdk/ios/v5/features/deep-links/resolution.mdx +++ b/src/content/docs/sdk/ios/v5/features/deep-links/resolution.mdx @@ -1,10 +1,8 @@ --- title: Link resolution -description: - Set up link resolution for deep linking via email, SMS, QR codes, and - platforms that shorten links. +description: Set up link resolution for deep linking via email, SMS, QR codes, and platforms that shorten links. slug: en/sdk/ios/features/deep-links/resolution -sidebar-position: 5 +sidebar-position: 3 versions: - label: v5 value: v5 diff --git a/src/content/docs/sdk/ios/v5/features/deep-links/set-up-deep-linking.mdx b/src/content/docs/sdk/ios/v5/features/deep-links/set-up-deep-linking.mdx new file mode 100644 index 000000000..9214c7b11 --- /dev/null +++ b/src/content/docs/sdk/ios/v5/features/deep-links/set-up-deep-linking.mdx @@ -0,0 +1,1074 @@ +--- +title: Set up deep linking +description: Set up deep linking in your app. +slug: en/sdk/ios/features/deep-links/set-up-deep-linking +sidebar-position: 2 +versions: + - label: v5 + value: v5 + default: true + - label: v4 + value: v4 +redirects: + v4: /en/sdk/ios/v4/features/deep-links/set-up-deep-linking +--- + +Please follow the steps in the section that corresponds to the type of app you have: + +- [UIKit apps using AppDelegate lifecycle](#uikit-apps-using-appdelegate-lifecycle) +- [UIKit apps using SceneDelegate lifecycle](#uikit-apps-using-scenedelegate-lifecycle) +- [SwiftUI apps using AppDelegate lifecycle](#swiftui-apps-using-appdelegate-lifecycle) +- [SwiftUI apps using SceneDelegate lifecycle](#swiftui-apps-using-scenedelegate-lifecycle) + +In addition, implement deep link handling logic: + +- [Deep link handler](#deep-link-handler) + +## UIKit apps using AppDelegate lifecycle + +Update your AppDelegate to implement direct and deferred deep linking. + + + + + + +```swift +import Adjust +import UIKit + +@UIApplicationMain +class AppDelegate: UIResponder, UIApplicationDelegate, + AdjustDelegate +{ + func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: + [UIApplication.LaunchOptionsKey: Any]? + ) -> Bool { + // Configure Adjust SDK + // Replace {YourAppToken} with your Adjust app token + let appToken = "{YourAppToken}" + var adjustConfig: ADJConfig? + + #if DEBUG + adjustConfig = ADJConfig( + appToken: appToken, + environment: ADJEnvironmentSandbox) + adjustConfig?.logLevel = ADJLogLevelVerbose + #else + adjustConfig = ADJConfig( + appToken: appToken, + environment: ADJEnvironmentProduction, + allowSuppressLogLevel: true) + adjustConfig?.logLevel = ADJLogLevelSuppress + #endif + + // Wait up to 120 seconds after app open for user to respond to ATT + // before sending install session to Adjust's servers. + // Ensure this interval is long enough for user to respond. + adjustConfig?.attConsentWaitingInterval = 120 + + // Create delegate for deferred deep linking + adjustConfig?.delegate = self + + // Initialize Adjust SDK + Adjust.initSdk(adjustConfig) + + return true + } + + // Receive universal link when app is installed, + // and app is in "not running" or background state, + // and user clicks link or another app opens link using + // UIApplication.open(_:options:completionHandler:). + func application( + _ application: UIApplication, + continue userActivity: NSUserActivity, + restorationHandler: + @escaping ([UIUserActivityRestoring]?) -> Void + ) -> Bool { + if userActivity.activityType == NSUserActivityTypeBrowsingWeb, + let incomingLink = userActivity.webpageURL + { + // Handle incoming universal link + DeeplinkHandler.handleDeeplink(incomingLink) + } + return true + } + + // Receive app scheme deep link when app is installed, and either: + // - App is in "not running" or background state, and user clicks link or + // another app opens link using UIApplication.open(_:options:completionHandler:), or + // - App is in foreground state and opens its own link using + // UIApplication.open(_:options:completionHandler:). + func application( + _ application: UIApplication, + open incomingLink: URL, + options: [UIApplication.OpenURLOptionsKey: Any] = [:] + ) -> Bool { + // Handle incoming app scheme deep link + DeeplinkHandler.handleDeeplink(incomingLink) + return true + } + + // Receive deferred deep link via AdjustDelegate method + func adjustDeferredDeeplinkReceived(_ deeplink: URL?) -> Bool { + if let incomingLink = deeplink { + // Store incoming deferred deep link to invoke after + // onboarding screens and login. + UserDefaults.standard.set( + incomingLink.absoluteString, + forKey: "lastDeferredLink") + + // Post notification for app to handle deferred deep link + NotificationCenter.default.post(name: .deferredLinkReceived, object: nil) + } + + // Return true to let Adjust SDK attempt to open deep link immediately + // upon receipt (for example: app has no ATT, onboarding screens, or login). + // Otherwise, return false. + return false + } +} +``` + + + + + + + + +```objc +#import +#import "AppDelegate.h" +#import "DeeplinkHandler.h" + +@implementation AppDelegate + +- (BOOL)application:(UIApplication *)application + didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { + // Configure Adjust SDK + // Replace {YourAppToken} with your Adjust app token + NSString *appToken = @"{YourAppToken}"; + ADJConfig *adjustConfig; + +#ifdef DEBUG + adjustConfig = [ADJConfig configWithAppToken:appToken + environment:ADJEnvironmentSandbox]; + [adjustConfig setLogLevel:ADJLogLevelVerbose]; +#else + adjustConfig = [ADJConfig configWithAppToken:appToken + environment:ADJEnvironmentProduction + allowSuppressLogLevel:YES]; + [adjustConfig setLogLevel:ADJLogLevelSuppress]; +#endif + + // Wait up to 120 seconds after app open for user to respond to ATT + // before sending install session to Adjust's servers. + // Ensure this interval is long enough for user to respond. + adjustConfig.attConsentWaitingInterval = 120; + + // Create delegate for deferred deep linking + adjustConfig.delegate = self; + + // Initialize Adjust SDK + [Adjust initSdk:adjustConfig]; + + return YES; +} + +// Receive universal link when app is installed, +// and app is in "not running" or background state, +// and user clicks link or another app opens link using +// [UIApplication openURL:options:completionHandler:]. +- (BOOL)application:(UIApplication *)application + continueUserActivity:(NSUserActivity *)userActivity + restorationHandler: + (void (^)(NSArray> *_Nullable)) + restorationHandler { + if ([userActivity.activityType + isEqualToString:NSUserActivityTypeBrowsingWeb]) { + NSURL *incomingLink = userActivity.webpageURL; + if (incomingLink) { + // Handle incoming universal link + [DeeplinkHandler handleDeeplink:incomingLink]; + } + } + return YES; +} + +// Receive app scheme deep link when app is installed, and either: +// - App is in "not running" or background state, and user clicks link or +// another app opens link using [UIApplication +// openURL:options:completionHandler:], or +// - App is in foreground state and opens its own link using +// [UIApplication openURL:options:completionHandler:]. +- (BOOL)application:(UIApplication *)app + openURL:(NSURL *)incomingLink + options: + (NSDictionary *)options { + // Handle incoming app scheme deep link + [DeeplinkHandler handleDeeplink:incomingLink]; + return YES; +} + +// Receive deferred deep link via AdjustDelegate method +- (BOOL)adjustDeferredDeeplinkReceived:(NSURL *)deeplink { + if (deeplink) { + // Store incoming deferred deep link to invoke after + // onboarding screens and login. + [[NSUserDefaults standardUserDefaults] setObject:[deeplink absoluteString] + forKey:@"lastDeferredLink"]; + + // Post notification for app to handle deferred deep link + [[NSNotificationCenter defaultCenter] + postNotificationName:@"DeferredLinkReceived" + object:nil]; + } + + // Return YES to let Adjust SDK attempt to open deep link immediately + // upon receipt (for example: app has no ATT, onboarding screens, or login). + // Otherwise, return NO. + return NO; +} + +@end +``` + + + + + + +For deferred deep linking, this is the sequence for most apps: + +1. A user who doesn't have the app installed clicks an Adjust deep link, which redirects them to the app store. +2. The user installs and opens the app. +3. The app begins its onboarding process (for example: ATT prompt, onboarding screens, login prompt). +4. After the ATT prompt response or timeout, the Adjust SDK sends session and attribution requests to Adjust's servers. +5. Adjust's servers respond with attribution data, including the deep link the user clicked on ("deferred deep link"). +6. The Adjust SDK triggers a deferred deep link callback in your app, which stores the deferred deep link (shown in the AppDelegate implementation above). +7. The app handles the deferred deep link in one of two ways (shown in the ViewController example class below): + - Once onboarding completes, the app checks for and handles any stored deferred deep link. + - If a deferred deep link arrives after onboarding, a notification triggers the same check and handling. +8. The app navigates the user to the deep link screen. + + + + + + +```swift +import Adjust +import UIKit + +class ViewController: UIViewController { + var hasCompletedOnboarding: Bool { + get { + UserDefaults.standard.bool(forKey: "HasCompletedOnboarding") + } + set { + UserDefaults.standard.set( + newValue, forKey: "HasCompletedOnboarding") + } + } + + override func viewDidLoad() { + super.viewDidLoad() + + // Observe deferred deep link notifications + NotificationCenter.default.addObserver( + self, + selector: #selector(onDeferredLinkReceived), + name: .deferredLinkReceived, + object: nil + ) + } + + override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + // Check if onboarding has been completed + if !hasCompletedOnboarding { + // Show onboarding screens and login prompt + + // Show ATT prompt after delay to ensure app is active + DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) { + Adjust.requestTrackingAuthorization { _ in } + } + + // After onboarding, set hasCompletedOnboarding to true + // and check for stored deferred deep link + hasCompletedOnboarding = true + checkForDeferredLink() + } else { + // Show main content + } + } + + @objc private func onDeferredLinkReceived() { + // Check for deferred deep link that may arrive after onboarding + if hasCompletedOnboarding { + checkForDeferredLink() + } + } + + private func checkForDeferredLink() { + // Check for stored deferred deep link before handling + guard + let deferredLinkString = UserDefaults.standard.string( + forKey: "lastDeferredLink" + ), + let deferredLink = URL(string: deferredLinkString) + else { return } + + // Remove stored deferred deep link to avoid handling again later + UserDefaults.standard.removeObject(forKey: "lastDeferredLink") + + // Handle deferred deep link + DeeplinkHandler.handleDeeplink(deferredLink) + } + + deinit { + NotificationCenter.default.removeObserver(self) + } +} + +// Set deferred deep link notification name +extension Notification.Name { + static let deferredLinkReceived = Notification.Name("deferredLinkReceived") +} +``` + + + + + + + + +```objc +#import +#import "DeeplinkHandler.h" +#import "ViewController.h" + +@implementation ViewController + +- (BOOL)hasCompletedOnboarding { + return [[NSUserDefaults standardUserDefaults] + boolForKey:@"HasCompletedOnboarding"]; +} + +- (void)setHasCompletedOnboarding:(BOOL)hasCompletedOnboarding { + [[NSUserDefaults standardUserDefaults] setBool:hasCompletedOnboarding + forKey:@"HasCompletedOnboarding"]; +} + +- (void)viewDidLoad { + [super viewDidLoad]; + + // Observe deferred deep link notifications + [[NSNotificationCenter defaultCenter] + addObserver:self + selector:@selector(onDeferredLinkReceived) + name:@"deferredLinkReceived" + object:nil]; +} + +- (void)viewDidAppear:(BOOL)animated { + [super viewDidAppear:animated]; + + // Check if onboarding has been completed + if (!self.hasCompletedOnboarding) { + // Show onboarding screens and login prompt + + // Show ATT prompt after delay to ensure app is active + dispatch_after( + dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), + dispatch_get_main_queue(), ^{ + [Adjust requestTrackingAuthorizationWithCompletionHandler:nil]; + }); + + // After onboarding, set hasCompletedOnboarding to true + // and check for stored deferred deep link + self.hasCompletedOnboarding = YES; + [self checkForDeferredLink]; + } else { + // Show main content + } +} + +- (void)onDeferredLinkReceived { + // Check for deferred deep link that may arrive after onboarding + if (self.hasCompletedOnboarding) { + [self checkForDeferredLink]; + } +} + +- (void)checkForDeferredLink { + // Check for stored deferred deep link before handling + NSString *deferredLinkString = + [[NSUserDefaults standardUserDefaults] stringForKey:@"lastDeferredLink"]; + NSURL *deferredLink = [NSURL URLWithString:deferredLinkString]; + + if (!deferredLink) { + return; + } + + // Remove stored deferred deep link to avoid handling again later + [[NSUserDefaults standardUserDefaults] + removeObjectForKey:@"lastDeferredLink"]; + + // Handle deferred deep link + [DeeplinkHandler handleDeeplink:deferredLink]; +} + +- (void)dealloc { + [[NSNotificationCenter defaultCenter] removeObserver:self]; +} + +@end +``` + + + + + + +Lastly, implement your deep link handling logic: + +[Deep Link Handler](#deep-link-handler) + +## UIKit apps using SceneDelegate lifecycle + +Follow the steps in the [UIKit apps using AppDelegate lifecycle section](#uikit-apps-using-appdelegate-lifecycle), except instead of implementing the `application(_:continue:restorationHandler:)` and `application(_:open:options:)` methods in your AppDelegate for direct deep linking, implement the following methods in your SceneDelegate. + + + + + + +```swift +import UIKit + +class SceneDelegate: UIResponder, UIWindowSceneDelegate { + // Receive universal link or app scheme deep link + // when app is installed, and app is in "not running" state, and either: + // - User clicks link, or + // - Another app opens link using + // UIApplication.open(_:options:completionHandler:). + func scene( + _ scene: UIScene, + willConnectTo session: UISceneSession, + options connectionOptions: UIScene.ConnectionOptions + ) { + // Handle incoming universal link + if let userActivity = connectionOptions.userActivities.first, + userActivity.activityType == NSUserActivityTypeBrowsingWeb, + let incomingLink = userActivity.webpageURL + { + DeeplinkHandler.handleDeeplink(incomingLink) + } + + // Handle incoming app scheme deep link + else if let urlContext = connectionOptions.urlContexts.first { + let incomingLink = urlContext.url + DeeplinkHandler.handleDeeplink(incomingLink) + } + } + + // Receive universal link when app is installed, + // and app is in background state, and either: + // - User clicks link, or + // - Another app opens link using + // UIApplication.open(_:options:completionHandler:). + func scene(_ scene: UIScene, continue userActivity: NSUserActivity) { + if userActivity.activityType == NSUserActivityTypeBrowsingWeb, + let incomingLink = userActivity.webpageURL + { + // Handle incoming universal link + DeeplinkHandler.handleDeeplink(incomingLink) + } + } + + // Receive app scheme deep link when app is installed, and either: + // - App is in background state, and user clicks link or another app opens link using + // UIApplication.open(_:options:completionHandler:), or + // - App is in foreground and opens its own link using + // UIApplication.open(_:options:completionHandler:). + func scene( + _ scene: UIScene, openURLContexts URLContexts: Set + ) { + if let urlContext = URLContexts.first { + let incomingLink = urlContext.url + + // Handle incoming app scheme deep link + DeeplinkHandler.handleDeeplink(incomingLink) + } + } +} +``` + + + + + + + + +```objc +#import "DeeplinkHandler.h" +#import "SceneDelegate.h" + +@implementation SceneDelegate + +// Receive universal link or app scheme deep link +// when app is installed, and app is in "not running" state, and either: +// - User clicks link, or +// - Another app opens link using +// [UIApplication openURL:options:completionHandler:]. +- (void)scene:(UIScene *)scene + willConnectToSession:(UISceneSession *)session + options:(UISceneConnectionOptions *)connectionOptions { + // Handle incoming universal link + if (connectionOptions.userActivities.count > 0) { + NSUserActivity *userActivity = + connectionOptions.userActivities.allObjects.firstObject; + if ([userActivity.activityType + isEqualToString:NSUserActivityTypeBrowsingWeb] && + userActivity.webpageURL) { + NSURL *incomingLink = userActivity.webpageURL; + [DeeplinkHandler handleDeeplink:incomingLink]; + } + } + // Handle incoming app scheme deep link + else if (connectionOptions.URLContexts.count > 0) { + UIOpenURLContext *urlContext = + connectionOptions.URLContexts.allObjects.firstObject; + NSURL *incomingLink = urlContext.URL; + [DeeplinkHandler handleDeeplink:incomingLink]; + } +} + +// Receive universal link when app is installed, +// and app is in background state, and either: +// - User clicks link, or +// - Another app opens link using +// [UIApplication openURL:options:completionHandler:]. +- (void)scene:(UIScene *)scene + continueUserActivity:(NSUserActivity *)userActivity { + if ([userActivity.activityType + isEqualToString:NSUserActivityTypeBrowsingWeb]) { + NSURL *incomingLink = userActivity.webpageURL; + + // Handle incoming universal link + [DeeplinkHandler handleDeeplink:incomingLink]; + } +} + +// Receive app scheme deep link when app is installed, and either: +// - App is in background state, and user clicks link or another app opens link +// using [UIApplication openURL:options:completionHandler:], or +// - App is in foreground and opens its own link using +// [UIApplication openURL:options:completionHandler:]. +- (void)scene:(UIScene *)scene + openURLContexts:(NSSet *)URLContexts { + UIOpenURLContext *urlContext = URLContexts.allObjects.firstObject; + if (urlContext) { + NSURL *incomingLink = urlContext.URL; + + // Handle incoming app scheme deep link + [DeeplinkHandler handleDeeplink:incomingLink]; + } +} + +@end +``` + + + + + + +## SwiftUI apps using AppDelegate lifecycle + +If you haven't already done so, create an `AppDelegate.swift` file in your project's main directory and reference it in your main application file (for example: `App.swift` as shown below). This is required to handle app lifecycle events and Adjust SDK integration. Also implement `onOpenURL`, which receives all direct deep links. + + + + + + +```swift +import SwiftUI + +@main +struct MyApp: App { + @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate + + var body: some Scene { + WindowGroup { + ContentView() + // Receive universal link or app scheme deep link + // when app is installed, and app is in "not running" or background state, and either: + // - User clicks link, or + // - Another app opens link using + // UIApplication.open(_:options:completionHandler:) or SwiftUI's openURL. + // + // Receive app scheme deep link when app is installed, + // and app is in foreground and opens its own link using + // UIApplication.open(_:options:completionHandler:) or SwiftUI's openURL. + .onOpenURL { incomingLink in + DeeplinkHandler.handleDeeplink(incomingLink) + } + } + } +} +``` + + + + + + +Update your AppDelegate to implement deferred deep linking. + + + + + + +```swift +import Adjust +import UIKit + +class AppDelegate: UIResponder, UIApplicationDelegate, AdjustDelegate { + func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: + [UIApplication.LaunchOptionsKey: Any]? + ) -> Bool { + // Configure Adjust SDK + // Replace {YourAppToken} with your Adjust app token + let appToken = "{YourAppToken}" + var adjustConfig: ADJConfig? + + #if DEBUG + adjustConfig = ADJConfig( + appToken: appToken, + environment: ADJEnvironmentSandbox) + adjustConfig?.logLevel = ADJLogLevelVerbose + #else + adjustConfig = ADJConfig( + appToken: appToken, + environment: ADJEnvironmentProduction, + allowSuppressLogLevel: true) + adjustConfig?.logLevel = ADJLogLevelSuppress + #endif + + // Wait up to 120 seconds after app open for user to respond to ATT + // before sending install session to Adjust's servers. + // Ensure this interval is long enough for user to respond. + adjustConfig?.attConsentWaitingInterval = 120 + + // Create delegate for deferred deep linking + adjustConfig?.delegate = self + + // Initialize Adjust SDK + Adjust.initSdk(adjustConfig) + + return true + } + + // Receive deferred deep link via AdjustDelegate method + func adjustDeferredDeeplinkReceived(_ deeplink: URL?) -> Bool { + if let incomingLink = deeplink { + // Store incoming deferred deep link to invoke after + // onboarding screens and login. + UserDefaults.standard.set( + incomingLink.absoluteString, + forKey: "lastDeferredLink") + + // Post notification for app to handle deferred deep link + NotificationCenter.default.post(name: .deferredLinkReceived, object: nil) + } + + // Return true to let Adjust SDK attempt to open deep link immediately + // upon receipt (for example: app has no ATT, onboarding screens, or login). + // Otherwise, return false. + return false + } +} +``` + + + + + + +For deferred deep linking, this is the sequence for most apps: + +1. A user who doesn't have the app installed clicks an Adjust deep link, which redirects them to the app store. +2. The user installs and opens the app. +3. The app begins its onboarding process (for example: ATT prompt, onboarding screens, login prompt). +4. After the ATT prompt response or timeout, the Adjust SDK sends session and attribution requests to Adjust's servers. +5. Adjust's servers respond with attribution data, including the deep link the user clicked on ("deferred deep link"). +6. The Adjust SDK triggers a deferred deep link callback in your app, which stores the deferred deep link (shown in the AppDelegate implementation above). +7. The app handles the deferred deep link in one of two ways (shown in the ContentView example class below): + - Once onboarding completes, the app checks for and handles any stored deferred deep link. + - If a deferred deep link arrives after onboarding, a notification triggers the same check and handling. +8. The app navigates the user to the deep link screen. + + + + + + +```swift +import Adjust +import SwiftUI + +struct ContentView: View { + @State private var hasCompletedOnboarding = UserDefaults.standard.bool(forKey: "HasCompletedOnboarding") + + var body: some View { + NavigationStack { + VStack { + // Check if onboarding has been completed + if !hasCompletedOnboarding { + // Show onboarding screens and login prompt + + // Show ATT prompt after delay to ensure app is active + .onAppear { + DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) { + Adjust.requestTrackingAuthorization { _ in } + } + } + + // After onboarding, set hasCompletedOnboarding to true + // and check for stored deferred deep link + UserDefaults.standard.set(true, forKey: "HasCompletedOnboarding") + hasCompletedOnboarding = true + checkForDeferredLink() + } else { + // Show main content + } + } + } + + // Check for deferred deep link that may arrive after onboarding + .onReceive( + NotificationCenter.default.publisher(for: .deferredLinkReceived) + ) { _ in + if hasCompletedOnboarding { + checkForDeferredLink() + } + } + } + + private func checkForDeferredDeeplink() { + // Check for stored deferred deep link before handling + guard let deferredLinkString = UserDefaults.standard.string( + forKey: "lastDeferredLink" + ), + let deferredLink = URL(string: deferredLinkString) else { return } + + // Remove stored deferred deep link to avoid handling again later + UserDefaults.standard.removeObject(forKey: "lastDeferredLink") + + // Handle deferred deep link + DeeplinkHandler.handleDeeplink(deferredLink) + } +} + +// Set deferred deep link notification name +extension Notification.Name { + static let deferredLinkReceived = Notification.Name("deferredLinkReceived") +} +``` + + + + + + +Lastly, implement your deep link handling logic: + +[Deep Link Handler](#deep-link-handler) + +## SwiftUI apps using SceneDelegate lifecycle + +Follow the steps in the [SwiftUI apps using AppDelegate lifecycle](#swiftui-apps-using-appdelegate-lifecycle). In addition, implement the following method in your SceneDelegate. + + + + + + +```swift +import UIKit + +class SceneDelegate: UIResponder, UIWindowSceneDelegate { + // Receive universal link or app scheme deep link + // when app is installed, and app is in "not running" state, and either: + // - User clicks link, or + // - Another app opens link using + // UIApplication.open(_:options:completionHandler:) or SwiftUI's openURL. + func scene( + _ scene: UIScene, + willConnectTo session: UISceneSession, + options connectionOptions: UIScene.ConnectionOptions + ) { + // Handle incoming universal link + if let userActivity = connectionOptions.userActivities.first, + userActivity.activityType == NSUserActivityTypeBrowsingWeb, + let incomingLink = userActivity.webpageURL + { + DeeplinkHandler.handleDeeplink(incomingLink) + } + // Handle incoming app scheme deep link + else if let urlContext = connectionOptions.urlContexts.first { + let incomingLink = urlContext.url + DeeplinkHandler.handleDeeplink(incomingLink) + } + } +} +``` + + + + + + +## Deep link handler + +The preceding code examples use an example DeeplinkHandler class. This example class is shown below and handles all types of links: + +- Adjust branded links (full go.link links) +- Adjust short branded links (short go.link links) +- Adjust universal links (adj.st links) +- Non-Adjust universal links (example.com links) +- App scheme deep links (example:// links) + +The class performs the following tasks: + +1. The class uses Adjust's `processAndResolveDeeplink` method, which sends the deep link to Adjust's servers to accomplish two things: + +- Record the deep link click for attribution purposes. +- If the deep link is an [Adjust short branded link](https://www.help.adjust.com/en/article/short-branded-links), respond with the corresponding full URL. Otherwise, respond with the original link. + +2. After processing, the class parses the link and navigates to the appropriate screen. This part of the code is specific to each app. Your app has to implement its own logic for handling deep links and opening the corresponding content. Your deep link handling has to meet the following key requirements: + +- Your app should treat Adjust branded links the same as other universal links, such as your own. Adjust recommends implementing domain-agnostic deep link handling logic to meet this requirement. For example, the following links should navigate to the same screen in your app: + - Adjust branded link: `https://example.go.link/summer-clothes?promo=beach` + - Your universal link: `https://www.example.com/summer-clothes?promo=beach` +- In cases where iOS doesn't support universal links, Adjust automatically converts them to app scheme deep links. Additionally, Adjust's servers convert all deferred deep links to app scheme deep link format. Therefore it's crucial for the app to handle universal links and app scheme deep links equivalently. For example, the following links should navigate to the same screen in your app: + - Adjust branded link: `https://example.go.link/summer-clothes?promo=beach` + - App scheme deep link: `example://summer-clothes?promo=beach` + + + + + + +```swift +import Adjust +// import UIKit and/or SwiftUI + +class DeeplinkHandler { + static func handleDeeplink(_ incomingLink: URL) { + // Send incoming deep link to Adjust's servers for attribution + // and retrieve full URL if short branded link. + // If not, retrieve original link. + let deeplink = ADJDeeplink(deeplink: incomingLink) + Adjust.processAndResolveDeeplink(deeplink) { processedLinkString in + guard let processedLink = URL(string: processedLinkString) else { return } + } + + // Extract path, query items, and fragment from the processed link. + let components = URLComponents( + url: processedLink, + resolvingAgainstBaseURL: true + ) + + // For app scheme deep links, set path = host + path + var path: String + if let scheme = processedLink.scheme, scheme == "http" || scheme == "https" + { + path = components.path + } else { + path = (components.host ?? "") + components.path + if !path.isEmpty && !path.hasPrefix("/") { + path = "/" + path + } + } + + let queryItems = components?.queryItems ?? [] + + // Parse query parameters into a dictionary for easier access + let params = queryItems.reduce(into: [String: String]()) { result, item in + result[item.name] = item.value + } + + let fragment = components?.fragment + + // Implement the navigation or other app-specific logic based on + // the deep link components. + DispatchQueue.main.async { + // Example of navigating based on the path or other components. + // Replace with your actual navigation logic. + switch path { + case "/product": + if let productId = params["id"] { + handleProduct(productId: productId) + } + case "/category": + if let categoryName = params["name"] { + handleCategory(category: categoryName) + } + case "/search": + if let query = params["q"] { + handleSearch(query: query) + } + default: + print("Unhandled deep link path: \(path)") + } + } + } + + private static func handleProduct(productId: String) { + // Example UIKit implementation: + // let productVC = ProductViewController(productId: productId) + // navigationController.pushViewController(productVC, animated: true) + + // Example SwiftUI implementation: + // let productView = ProductView(productId: productId) + // navigationPath.append(productView) + // or: router.navigate(to: productView) + } + + private static func handleCategory(category: String) { + // Example UIKit implementation: + // let categoryVC = CategoryViewController(category: category) + // navigationController.pushViewController(categoryVC, animated: true) + + // Example SwiftUI implementation: + // let categoryView = CategoryView(category: category) + // navigationPath.append(categoryView) + // or: router.navigate(to: categoryView) + } + + private static func handleSearch(query: String) { + // Example UIKit implementation: + // let searchVC = SearchViewController(searchQuery: query) + // navigationController.pushViewController(searchVC, animated: true) + + // Example SwiftUI implementation: + // let searchView = SearchView(searchQuery: query) + // navigationPath.append(searchView) + // or: router.navigate(to: searchView) + } +} +``` + + + + + + + + +```objc +#import +#import "DeeplinkDetailViewController.h" +#import "DeeplinkHandler.h" + +@implementation DeeplinkHandler + ++ (void)handleDeeplink:(NSURL *)incomingLink { + // Send incoming deep link to Adjust's servers for attribution + // and retrieve full URL if short branded link. + // If not, retrieve original link. + ADJDeeplink *deeplink = [[ADJDeeplink alloc] initWithDeeplink:incomingURL]; + [Adjust processAndResolveDeeplink:deeplink + withCompletionHandler:^(NSString *_Nonnull processedLinkString) { + NSURL *processedLink = [NSURL URLWithString:processedLinkString]; + if (!processedLink) return; + }]; + + // Extract path, query items, and fragment from the processed link. + NSURLComponents *components = [NSURLComponents componentsWithURL:processedLink + resolvingAgainstBaseURL:YES]; + + // For app scheme deep links, set path = host + path + NSString *path; + if ([processedLink.scheme isEqualToString:@"http"] || + [processedLink.scheme isEqualToString:@"https"]) { + path = components.path; + } else { + path = [NSString + stringWithFormat:@"%@%@", components.host ?: @"", components.path]; + if (path.length > 0 && ![path hasPrefix:@"/"]) { + path = [@"/" stringByAppendingString:path]; + } + } + + NSArray *queryItems = components.queryItems ?: @[]; + + // Parse query parameters into a dictionary for easier access + NSMutableDictionary *params = + [NSMutableDictionary dictionary]; + for (NSURLQueryItem *item in queryItems) { + params[item.name] = item.value; + } + + NSString *fragment = components.fragment; + + // Implement the navigation or other app-specific logic based on + // the deep link components. + dispatch_async(dispatch_get_main_queue(), ^{ + // Example of navigating based on the path or other components. + // Replace with your actual navigation logic. + if ([path isEqualToString:@"/product"]) { + NSString *productId = params[@"id"]; + if (productId) { + ProductViewController *productVC = + [[ProductViewController alloc] initWithProductId:productId]; + [self navigateToViewController:productVC]; + } + } else if ([path isEqualToString:@"/category"]) { + NSString *categoryName = params[@"name"]; + if (categoryName) { + CategoryViewController *categoryVC = + [[CategoryViewController alloc] initWithCategory:categoryName]; + [self navigateToViewController:categoryVC]; + } + } else if ([path isEqualToString:@"/search"]) { + NSString *query = params[@"q"]; + if (query) { + SearchViewController *searchVC = + [[SearchViewController alloc] initWithSearchQuery:query]; + [self navigateToViewController:searchVC]; + } + } else { + NSLog(@"Unhandled deep link path: %@", path); + } + }); +}]; +} + ++ (void)navigateToViewController:(UIViewController *)viewController { + // Example navigation implementation: + // UINavigationController *navigationController = [self + // getNavigationController]; [navigationController + // pushViewController:viewController animated:YES]; +} + +@end +``` + + + + + diff --git a/src/content/docs/sdk/ios/v5/features/deep-links/testing.mdx b/src/content/docs/sdk/ios/v5/features/deep-links/testing.mdx index 2804f5a83..ade483c31 100644 --- a/src/content/docs/sdk/ios/v5/features/deep-links/testing.mdx +++ b/src/content/docs/sdk/ios/v5/features/deep-links/testing.mdx @@ -2,7 +2,7 @@ title: Test deep linking description: Test your deep links to ensure they work as expected. slug: en/sdk/ios/features/deep-links/testing -sidebar-position: 6 +sidebar-position: 4 versions: - label: v5 value: v5 diff --git a/src/utils/helpers/navigation/buildSidebarHierarchy.ts b/src/utils/helpers/navigation/buildSidebarHierarchy.ts index f07883315..197562897 100644 --- a/src/utils/helpers/navigation/buildSidebarHierarchy.ts +++ b/src/utils/helpers/navigation/buildSidebarHierarchy.ts @@ -66,6 +66,19 @@ export const buildSidebarHierarchy = (entries: ContentCollectionEntry[]): [Langu }); }; + // Sort function for sidebar items + const sortSidebarItems = (a: SidebarItem, b: SidebarItem) => { + // If both have positions, sort by position + if (a.position !== undefined && b.position !== undefined) { + return a.position - b.position; + } + // If only one has position, positioned items come first + if (a.position !== undefined) return -1; + if (b.position !== undefined) return 1; + // If neither has position, sort alphabetically by title + return a.title.localeCompare(b.title); + }; + // Build the parent-child relationships sortedEntries.forEach(entry => { // Fetch the entry from the slug map @@ -100,12 +113,8 @@ export const buildSidebarHierarchy = (entries: ContentCollectionEntry[]): [Langu // Set the parent for the child potentialChild.parent = entry.slug; - // Insert the child in the correct position or alphabetically - if (potentialChild.position) { - structuredEntry.children?.splice(potentialChild.position - 1, 0, potentialChild); - } else { - structuredEntry.children?.push(potentialChild); - } + // Add child to the children array + structuredEntry.children?.push(potentialChild); // Remove the entry from the root array since it's now in a child array if (type === "sdk") { @@ -116,22 +125,8 @@ export const buildSidebarHierarchy = (entries: ContentCollectionEntry[]): [Langu } }); - // Sort the children alphabetically if they have no position - structuredEntry.children?.sort((a, b) => { - if (a.position && b.position) { - return 0; - } - if (a.position) { - return -1; - } - if (b.position) { - return 1; - } - // Sort alphabetically by file name (last part of the id) - const aFileName = a.id.split('/').pop()?.toLowerCase(); - const bFileName = b.id.split('/').pop()?.toLowerCase(); - return aFileName?.localeCompare(bFileName!); - }); + // Sort children by position + structuredEntry.children?.sort(sortSidebarItems); // If the entry has no parent, nest it directly under the type array if (!structuredEntry.parent) { @@ -146,5 +141,9 @@ export const buildSidebarHierarchy = (entries: ContentCollectionEntry[]): [Langu setChildLevels(structuredEntry, structuredEntry.level); }); - return [hierarchy, slugMap]; // Return sdk and api arrays inside the hierarchy object -}; + // Sort top-level entries by position + hierarchy.sdk.sort(sortSidebarItems); + hierarchy.api.sort(sortSidebarItems); + + return [hierarchy, slugMap]; +}; \ No newline at end of file From 347c18b098580a6dba8637027be802f8e63d8bc5 Mon Sep 17 00:00:00 2001 From: Dave Mead Date: Sat, 30 Nov 2024 17:27:17 -0800 Subject: [PATCH 17/27] Apply small fixes --- .../ios/v5/features/deep-links/configure-deep-link-settings.mdx | 2 +- .../docs/sdk/ios/v5/features/deep-links/set-up-deep-linking.mdx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/content/docs/sdk/ios/v5/features/deep-links/configure-deep-link-settings.mdx b/src/content/docs/sdk/ios/v5/features/deep-links/configure-deep-link-settings.mdx index a28681510..c12f4fbde 100644 --- a/src/content/docs/sdk/ios/v5/features/deep-links/configure-deep-link-settings.mdx +++ b/src/content/docs/sdk/ios/v5/features/deep-links/configure-deep-link-settings.mdx @@ -27,7 +27,7 @@ Copy your branded domain to configure it in Xcode in the section below.
### Configure universal links -1. Open your Xcode project/workspace (use the .xcworkspace file if you are using Cocoapods, otherwise use the .xcodeproj file). +1. Open your Xcode project. 2. In the navigator pane, select the project name to access the project settings. 3. In the project settings, under **Targets**, select the appropriate target (usually your app's name). 4. Select the **Signing & Capabilities** tab. diff --git a/src/content/docs/sdk/ios/v5/features/deep-links/set-up-deep-linking.mdx b/src/content/docs/sdk/ios/v5/features/deep-links/set-up-deep-linking.mdx index 9214c7b11..25c584d74 100644 --- a/src/content/docs/sdk/ios/v5/features/deep-links/set-up-deep-linking.mdx +++ b/src/content/docs/sdk/ios/v5/features/deep-links/set-up-deep-linking.mdx @@ -767,7 +767,7 @@ struct ContentView: View { } } - private func checkForDeferredDeeplink() { + private func checkForDeferredLink() { // Check for stored deferred deep link before handling guard let deferredLinkString = UserDefaults.standard.string( forKey: "lastDeferredLink" From 46f03644666589eb859effd55bb9096c9723ca5e Mon Sep 17 00:00:00 2001 From: Dave Mead Date: Sat, 30 Nov 2024 17:54:54 -0800 Subject: [PATCH 18/27] Fix formatting on configure page --- .../deep-links/configure-deep-link-settings.mdx | 2 +- .../deep-links/configure-deep-link-settings.mdx | 11 +++++++++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/src/content/docs/sdk/ios/v4/features/deep-links/configure-deep-link-settings.mdx b/src/content/docs/sdk/ios/v4/features/deep-links/configure-deep-link-settings.mdx index 0e015f38f..d25312971 100644 --- a/src/content/docs/sdk/ios/v4/features/deep-links/configure-deep-link-settings.mdx +++ b/src/content/docs/sdk/ios/v4/features/deep-links/configure-deep-link-settings.mdx @@ -86,7 +86,7 @@ If you need to set up an app scheme, follow these steps: ## Configure settings in Adjust dashboard -In the Adjust dashboard, [create an app](https://help.adjust.com/en/article/app-setup) if you haven't already done so. Then, configure its [iOS platform settings](https://help.adjust.com/en/article/platforms-ios-android-amazon-microsoft) using the previously collected data points, detailed below. Note that the Adjust dashboard only supports one bundle ID and one app scheme per app. If you need to test with a Debug Bundle ID or Debug App Scheme, create a separate test app. +In the Adjust dashboard, [create an app](https://help.adjust.com/en/article/app-setup) if you haven't already done so. Then, configure its [iOS platform settings](https://help.adjust.com/en/article/platforms-ios-android-amazon-microsoft) using the previously collected data points, detailed below. Note that the Adjust dashboard only supports one bundle ID and one app scheme per app. If you need to test with a Debug Bundle ID or Debug App Scheme, create a separate test app and corresponding branded domain. | Data Point | Example | Requirement | | ------------------ | ----------------- | ----------------------------------------------------------------------------------------------------- | diff --git a/src/content/docs/sdk/ios/v5/features/deep-links/configure-deep-link-settings.mdx b/src/content/docs/sdk/ios/v5/features/deep-links/configure-deep-link-settings.mdx index c12f4fbde..f2cea9ea6 100644 --- a/src/content/docs/sdk/ios/v5/features/deep-links/configure-deep-link-settings.mdx +++ b/src/content/docs/sdk/ios/v5/features/deep-links/configure-deep-link-settings.mdx @@ -66,7 +66,14 @@ If your app already has an app scheme, determine which of the below configuratio | Build setting variable, such as `$(APP_SCHEME)` | Static values, such as `com.example.app` and `com.example.debug`, respectively | **Build Settings** tab -> search for "APP_SCHEME" -> values for Release and Debug are the "Release App Scheme" and "Debug App Scheme," respectively | | Build setting variable, such as `$(APP_SCHEME)` | Build setting variable, such as `$(PRODUCT_BUNDLE_IDENTIFIER)` | **Build Settings** tab -> search for "APP_SCHEME" -> values for Release and Debug are the "Release App Scheme" and "Debug App Scheme," respectively | -If you need to set up an app scheme, follow these steps: - Select the **+** button to add a new URL Type. - Fill in the following fields: - **Identifier**: `$(PRODUCT_BUNDLE_IDENTIFIER)` - **Role**: Editor - **URL Schemes**: Enter your desired app scheme (for example: enter `example` to represent `example://`). Don't use `http`, `https`, or reserved iOS schemes like `mailto`, `tel`, `sms`, or `facetime`. Entering a static value here will create a single app scheme used for both release and debug builds. - Once created, remember to note the app scheme for configuration in the Adjust dashboard later. +If you need to set up an app scheme, follow these steps: + +- Select the **+** button to add a new URL Type. +- Fill in the following fields: + - **Identifier**: `$(PRODUCT_BUNDLE_IDENTIFIER)` + - **Role**: Editor + - **URL Schemes**: Enter your desired app scheme (for example: enter `example` to represent `example://`). Don't use `http`, `https`, or reserved iOS schemes like `mailto`, `tel`, `sms`, or `facetime`. Entering a static value here will create a single app scheme used for both release and debug builds. +- Once created, remember to note the app scheme for configuration in the Adjust dashboard later.
## Retrieve App ID Prefix from Apple Developer Portal @@ -79,7 +86,7 @@ If you need to set up an app scheme, follow these steps: - Select the **+** butt ## Configure settings in Adjust dashboard -In the Adjust dashboard, [create an app](https://help.adjust.com/en/article/app-setup) if you haven't already done so. Then, configure its [iOS platform settings](https://help.adjust.com/en/article/platforms-ios-android-amazon-microsoft) using the previously collected data points, detailed below. Note that the Adjust dashboard only supports one bundle ID and one app scheme per app. If you need to test with a Debug Bundle ID or Debug App Scheme, create a separate test app. +In the Adjust dashboard, [create an app](https://help.adjust.com/en/article/app-setup) if you haven't already done so. Then, configure its [iOS platform settings](https://help.adjust.com/en/article/platforms-ios-android-amazon-microsoft) using the previously collected data points, detailed below. Note that the Adjust dashboard only supports one bundle ID and one app scheme per app. If you need to test with a Debug Bundle ID or Debug App Scheme, create a separate test app and corresponding branded domain. | Data Point | Example | Requirement | | ------------------ | ----------------- | ----------------------------------------------------------------------------------------------------- | From 4c144d00fcf14e845e16f3971213ca7423354368 Mon Sep 17 00:00:00 2001 From: Dave Mead Date: Sat, 30 Nov 2024 20:25:33 -0800 Subject: [PATCH 19/27] Revise configuration page --- .../configure-deep-link-settings.mdx | 74 +++++++++++-------- .../configure-deep-link-settings.mdx | 74 +++++++++++-------- 2 files changed, 86 insertions(+), 62 deletions(-) diff --git a/src/content/docs/sdk/ios/v4/features/deep-links/configure-deep-link-settings.mdx b/src/content/docs/sdk/ios/v4/features/deep-links/configure-deep-link-settings.mdx index d25312971..fc95266c8 100644 --- a/src/content/docs/sdk/ios/v4/features/deep-links/configure-deep-link-settings.mdx +++ b/src/content/docs/sdk/ios/v4/features/deep-links/configure-deep-link-settings.mdx @@ -14,13 +14,25 @@ redirects: ---
-## Set up a branded domain +## Create an app [Create an app](https://help.adjust.com/en/article/app-setup) +in the Adjust dashboard. + +Please note that the Adjust dashboard only supports one bundle ID (`com.example.app`) and app scheme (`example://`) for each app. If your app uses different bundle IDs and/or app schemes for your release and debug builds, create a separate debug app. + +## Add iOS platform + +Add iOS in the platform settings for your app. This step requires entering the bundle ID. If you're not sure of the bundle ID for your app build, enter a temporary value (`com.example.app`) to save the platform settings. Follow the rest of this guide to collect all required data points, then return to platform settings in the dashboard to finish the configuration. +
-In the Adjust dashboard, [set up a branded +## Set up a branded domain In the Adjust dashboard, [set up a branded domain](https://help.adjust.com/en/article/set-up-branded-domain) using Adjust's -go.link domain (for example: `example.go.link`). +go.link domain (for example: `brandname.go.link`). + +If your iOS app and Android app use separate apps in Adjust, ensure that you select the same branded domain (`brandname.go.link`) for both apps in the Adjust dashboard. This allows you to use the same branded domain for both platforms. + +If you use separate apps for release and debug builds, create a separate branded domain for your debug app (`brandnamedebug.go.link`). If your debug apps use separate apps for iOS and Android in Adjust, ensure that you also select this same branded domain for both debug apps in the Adjust dashboard. -Copy your branded domain to configure it in Xcode in the section below. +Make note of your branded domains to configure in Xcode in the next section.
## Configure settings in Xcode @@ -33,10 +45,10 @@ Copy your branded domain to configure it in Xcode in the section below. 4. Select the **Signing & Capabilities** tab. 5. Complete these steps for both the **Release** and **Debug** sub-tabs: - - Note the value in the **Bundle Identifier** field. This is your app's Bundle ID ("Release Bundle ID" or "Debug Bundle ID," respectively), which you'll need to add to the Adjust dashboard later. - - In the **Associated Domains** section, add an entry for your branded domain. Here is an example using the branded domain `example.go.link`: + - Make note of the value in the **Bundle Identifier** field. This is your app's bundle ID ("Release Bundle ID" or "Debug Bundle ID," respectively), which you need to configure in the iOS platform settings in the Adjust dashboard. + - In the **Associated Domains** section, add an entry for each of your branded domains. Here is an example entry using the branded domain `brandname.go.link`: - `applinks:example.go.link` + `applinks:brandname.go.link` **Troubleshoot missing or problematic Associated Domains settings** @@ -49,31 +61,31 @@ Copy your branded domain to configure it in Xcode in the section below. 1. Go to the Apple Developer portal and [enable the Associated Domains capability](https://developer.apple.com/help/account/manage-identifiers/enable-app-capabilities) for your app. 2. [Download and import](https://help.apple.com/xcode/mac/current/#/dev1bf96f17e) the updated provisioning profile into Xcode. -### Configure app scheme +### Retrieve or configure app scheme App scheme is required for certain use cases where iOS doesn’t support universal links. You can reuse an existing app scheme for Adjust deep linking. -1. In Xcode, select the **Info** tab. -2. Expand the **URL Types** section. +1. In the Xcode navigator pane, select the project name to access the project settings. +2. Under Targets, select the appropriate target (usually your app’s name). +3. Select the **Info** tab. +4. Expand the **URL Types** section. -If your app already has an app scheme, determine which of the below configurations you have and retrieve it: +If your app already has an app scheme, there will be URL Type entries, each of which has a single value for **URL Schemes**. Use the table below to determine your app schemes based on your URL Types configuration. Make note of the app schemes to configure them in the Adjust dashboard later. -| **URL Schemes** field | **Identifier** field | App Scheme | -| --------------------------------------------------------------------- | ------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------- | -| Static value, such as `example` | Static value, such as `com.example.app` | `example://` is the "Release App Scheme" | -| Static value, such as `example` | Build setting variable, such as `$(PRODUCT_BUNDLE_IDENTIFIER)` | `example://` is the "Release App Scheme" | -| Two static values, such as `example` and `exampleDebug`, respectively | Static values, such as `com.example.app` and `com.example.debug`, respectively | `example://` is the "Release App Scheme" and `exampleDebug://` is the "Debug App Scheme" | -| Build setting variable, such as `$(APP_SCHEME)` | Static values, such as `com.example.app` and `com.example.debug`, respectively | **Build Settings** tab -> search for "APP_SCHEME" -> values for Release and Debug are the "Release App Scheme" and "Debug App Scheme," respectively | -| Build setting variable, such as `$(APP_SCHEME)` | Build setting variable, such as `$(PRODUCT_BUNDLE_IDENTIFIER)` | **Build Settings** tab -> search for "APP_SCHEME" -> values for Release and Debug are the "Release App Scheme" and "Debug App Scheme," respectively | +| **URL Schemes** field | App Scheme | +| ------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Static value, such as `example` | `example://` is the "Release App Scheme" | +| Static values, such as `example`, `exampleDebug` in separate URL Types entries | `example://` is the "Release App Scheme"
`exampleDebug://` is the "Debug App Scheme" | +| Dynamic build setting variable, such as `$(APP_SCHEME)` | Go to **Build Settings** tab -> search for variable name, such as "APP_SCHEME" -> values for Release and Debug are the "Release App Scheme" and "Debug App Scheme," respectively | -If you need to set up an app scheme, follow these steps: +If you need to create an app scheme, follow these steps: - Select the **+** button to add a new URL Type. - Fill in the following fields: - **Identifier**: `$(PRODUCT_BUNDLE_IDENTIFIER)` - **Role**: Editor - - **URL Schemes**: Enter your desired app scheme (for example: enter `example` to represent `example://`). Don't use `http`, `https`, or reserved iOS schemes like `mailto`, `tel`, `sms`, or `facetime`. Entering a static value here will create a single app scheme used for both release and debug builds. -- Once created, remember to note the app scheme for configuration in the Adjust dashboard later. + - **URL Schemes**: Enter your desired app scheme value (enter `example`, not `example://`). Don't use `http`, `https`, or reserved iOS schemes like `mailto`, `tel`, `sms`, or `facetime`. Entering a static value here will create a single app scheme used for both release and debug builds. +- Once created, make note of the app scheme for configuration in the Adjust dashboard later.
## Retrieve App ID Prefix from Apple Developer Portal @@ -81,17 +93,17 @@ If you need to set up an app scheme, follow these steps: 1. Log into the [Apple Developer portal](https://developer.apple.com/account/). 2. Under **Certificates, IDs & Profiles**, select **Identifiers**. 3. Select your app. -4. Near the top of the page, copy the **App ID Prefix** to configure in the Adjust dashboard below. +4. Near the top of the page, make note of the **App ID Prefix** to configure in the Adjust dashboard in the next section.
-## Configure settings in Adjust dashboard +## Finish configuring iOS platform settings -In the Adjust dashboard, [create an app](https://help.adjust.com/en/article/app-setup) if you haven't already done so. Then, configure its [iOS platform settings](https://help.adjust.com/en/article/platforms-ios-android-amazon-microsoft) using the previously collected data points, detailed below. Note that the Adjust dashboard only supports one bundle ID and one app scheme per app. If you need to test with a Debug Bundle ID or Debug App Scheme, create a separate test app and corresponding branded domain. +In the Adjust dashboard, finish configuring [iOS platform settings](https://help.adjust.com/en/article/platforms-ios-android-amazon-microsoft) for your apps using the below data points that you collected. -| Data Point | Example | Requirement | -| ------------------ | ----------------- | ----------------------------------------------------------------------------------------------------- | -| Release Bundle ID | com.example.app | Required. | -| Debug Bundle ID | com.example.debug | Required if Debug Bundle ID is different than Release Bundle ID, and you are testing a debug build. | -| Release App Scheme | example:// | Required for use cases where iOS doesn't support universal links. | -| Debug App Scheme | exampleDebug:// | Required if Debug App Scheme is different than Release App Scheme, and you are testing a debug build. | -| App ID Prefix | ABCDE12345 | Required. | +| Data Point | Example | Requirement | +| ------------------ | ----------------- | ------------------------------------------------------------------ | +| Release Bundle ID | com.example.app | Required. | +| Debug Bundle ID | com.example.debug | Required if Debug Bundle ID is different than Release Bundle ID. | +| Release App Scheme | example:// | Required for use cases where iOS doesn't support universal links. | +| Debug App Scheme | exampleDebug:// | Required if Debug App Scheme is different than Release App Scheme. | +| App ID Prefix | ABCDE12345 | Required. | diff --git a/src/content/docs/sdk/ios/v5/features/deep-links/configure-deep-link-settings.mdx b/src/content/docs/sdk/ios/v5/features/deep-links/configure-deep-link-settings.mdx index f2cea9ea6..378a6589d 100644 --- a/src/content/docs/sdk/ios/v5/features/deep-links/configure-deep-link-settings.mdx +++ b/src/content/docs/sdk/ios/v5/features/deep-links/configure-deep-link-settings.mdx @@ -14,13 +14,25 @@ redirects: ---
-## Set up a branded domain +## Create an app [Create an app](https://help.adjust.com/en/article/app-setup) +in the Adjust dashboard. + +Please note that the Adjust dashboard only supports one bundle ID (`com.example.app`) and app scheme (`example://`) for each app. If your app uses different bundle IDs and/or app schemes for your release and debug builds, create a separate debug app. + +## Add iOS platform + +Add iOS in the platform settings for your app. This step requires entering the bundle ID. If you're not sure of the bundle ID for your app build, enter a temporary value (`com.example.app`) to save the platform settings. Follow the rest of this guide to collect all required data points, then return to platform settings in the dashboard to finish the configuration. +
-In the Adjust dashboard, [set up a branded +## Set up a branded domain In the Adjust dashboard, [set up a branded domain](https://help.adjust.com/en/article/set-up-branded-domain) using Adjust's -go.link domain (for example: `example.go.link`). +go.link domain (for example: `brandname.go.link`). + +If your iOS app and Android app use separate apps in Adjust, ensure that you select the same branded domain (`brandname.go.link`) for both apps in the Adjust dashboard. This allows you to use the same branded domain for both platforms. + +If you use separate apps for release and debug builds, create a separate branded domain for your debug app (`brandnamedebug.go.link`). If your debug apps use separate apps for iOS and Android in Adjust, ensure that you also select this same branded domain for both debug apps in the Adjust dashboard. -Copy your branded domain to configure it in Xcode in the section below. +Make note of your branded domains to configure in Xcode in the next section.
## Configure settings in Xcode @@ -33,10 +45,10 @@ Copy your branded domain to configure it in Xcode in the section below. 4. Select the **Signing & Capabilities** tab. 5. Complete these steps for both the **Release** and **Debug** sub-tabs: - - Note the value in the **Bundle Identifier** field. This is your app's Bundle ID ("Release Bundle ID" or "Debug Bundle ID," respectively), which you'll need to add to the Adjust dashboard later. - - In the **Associated Domains** section, add an entry for your branded domain. Here is an example using the branded domain `example.go.link`: + - Make note of the value in the **Bundle Identifier** field. This is your app's bundle ID ("Release Bundle ID" or "Debug Bundle ID," respectively), which you need to configure in the iOS platform settings in the Adjust dashboard. + - In the **Associated Domains** section, add an entry for each of your branded domains. Here is an example entry using the branded domain `brandname.go.link`: - `applinks:example.go.link` + `applinks:brandname.go.link` **Troubleshoot missing or problematic Associated Domains settings** @@ -49,31 +61,31 @@ Copy your branded domain to configure it in Xcode in the section below. 1. Go to the Apple Developer portal and [enable the Associated Domains capability](https://developer.apple.com/help/account/manage-identifiers/enable-app-capabilities) for your app. 2. [Download and import](https://help.apple.com/xcode/mac/current/#/dev1bf96f17e) the updated provisioning profile into Xcode. -### Configure app scheme +### Retrieve or configure app scheme App scheme is required for certain use cases where iOS doesn’t support universal links. You can reuse an existing app scheme for Adjust deep linking. -1. In Xcode, select the **Info** tab. -2. Expand the **URL Types** section. +1. In the Xcode navigator pane, select the project name to access the project settings. +2. Under Targets, select the appropriate target (usually your app’s name). +3. Select the **Info** tab. +4. Expand the **URL Types** section. -If your app already has an app scheme, determine which of the below configurations you have and retrieve it: +If your app already has an app scheme, there will be URL Type entries, each of which has a single value for **URL Schemes**. Use the table below to determine your app schemes based on your URL Types configuration. Make note of the app schemes to configure them in the Adjust dashboard later. -| **URL Schemes** field | **Identifier** field | App Scheme | -| --------------------------------------------------------------------- | ------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------- | -| Static value, such as `example` | Static value, such as `com.example.app` | `example://` is the "Release App Scheme" | -| Static value, such as `example` | Build setting variable, such as `$(PRODUCT_BUNDLE_IDENTIFIER)` | `example://` is the "Release App Scheme" | -| Two static values, such as `example` and `exampleDebug`, respectively | Static values, such as `com.example.app` and `com.example.debug`, respectively | `example://` is the "Release App Scheme" and `exampleDebug://` is the "Debug App Scheme" | -| Build setting variable, such as `$(APP_SCHEME)` | Static values, such as `com.example.app` and `com.example.debug`, respectively | **Build Settings** tab -> search for "APP_SCHEME" -> values for Release and Debug are the "Release App Scheme" and "Debug App Scheme," respectively | -| Build setting variable, such as `$(APP_SCHEME)` | Build setting variable, such as `$(PRODUCT_BUNDLE_IDENTIFIER)` | **Build Settings** tab -> search for "APP_SCHEME" -> values for Release and Debug are the "Release App Scheme" and "Debug App Scheme," respectively | +| **URL Schemes** field | App Scheme | +| ------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Static value, such as `example` | `example://` is the "Release App Scheme" | +| Static values, such as `example`, `exampleDebug` in separate URL Types entries | `example://` is the "Release App Scheme"
`exampleDebug://` is the "Debug App Scheme" | +| Dynamic build setting variable, such as `$(APP_SCHEME)` | Go to **Build Settings** tab -> search for variable name, such as "APP_SCHEME" -> values for Release and Debug are the "Release App Scheme" and "Debug App Scheme," respectively | -If you need to set up an app scheme, follow these steps: +If you need to create an app scheme, follow these steps: - Select the **+** button to add a new URL Type. - Fill in the following fields: - **Identifier**: `$(PRODUCT_BUNDLE_IDENTIFIER)` - **Role**: Editor - - **URL Schemes**: Enter your desired app scheme (for example: enter `example` to represent `example://`). Don't use `http`, `https`, or reserved iOS schemes like `mailto`, `tel`, `sms`, or `facetime`. Entering a static value here will create a single app scheme used for both release and debug builds. -- Once created, remember to note the app scheme for configuration in the Adjust dashboard later. + - **URL Schemes**: Enter your desired app scheme value (enter `example`, not `example://`). Don't use `http`, `https`, or reserved iOS schemes like `mailto`, `tel`, `sms`, or `facetime`. Entering a static value here will create a single app scheme used for both release and debug builds. +- Once created, make note of the app scheme for configuration in the Adjust dashboard later.
## Retrieve App ID Prefix from Apple Developer Portal @@ -81,17 +93,17 @@ If you need to set up an app scheme, follow these steps: 1. Log into the [Apple Developer portal](https://developer.apple.com/account/). 2. Under **Certificates, IDs & Profiles**, select **Identifiers**. 3. Select your app. -4. Near the top of the page, copy the **App ID Prefix** to configure in the Adjust dashboard below. +4. Near the top of the page, make note of the **App ID Prefix** to configure in the Adjust dashboard in the next section.
-## Configure settings in Adjust dashboard +## Finish configuring iOS platform settings -In the Adjust dashboard, [create an app](https://help.adjust.com/en/article/app-setup) if you haven't already done so. Then, configure its [iOS platform settings](https://help.adjust.com/en/article/platforms-ios-android-amazon-microsoft) using the previously collected data points, detailed below. Note that the Adjust dashboard only supports one bundle ID and one app scheme per app. If you need to test with a Debug Bundle ID or Debug App Scheme, create a separate test app and corresponding branded domain. +In the Adjust dashboard, finish configuring [iOS platform settings](https://help.adjust.com/en/article/platforms-ios-android-amazon-microsoft) for your apps using the below data points that you collected. -| Data Point | Example | Requirement | -| ------------------ | ----------------- | ----------------------------------------------------------------------------------------------------- | -| Release Bundle ID | com.example.app | Required. | -| Debug Bundle ID | com.example.debug | Required if Debug Bundle ID is different than Release Bundle ID, and you are testing a debug build. | -| Release App Scheme | example:// | Required for use cases where iOS doesn't support universal links. | -| Debug App Scheme | exampleDebug:// | Required if Debug App Scheme is different than Release App Scheme, and you are testing a debug build. | -| App ID Prefix | ABCDE12345 | Required. | +| Data Point | Example | Requirement | +| ------------------ | ----------------- | ------------------------------------------------------------------ | +| Release Bundle ID | com.example.app | Required. | +| Debug Bundle ID | com.example.debug | Required if Debug Bundle ID is different than Release Bundle ID. | +| Release App Scheme | example:// | Required for use cases where iOS doesn't support universal links. | +| Debug App Scheme | exampleDebug:// | Required if Debug App Scheme is different than Release App Scheme. | +| App ID Prefix | ABCDE12345 | Required. | From e8ec4acf960e8da1bf1497e20116585cde4d4fa3 Mon Sep 17 00:00:00 2001 From: Dave Mead Date: Sat, 30 Nov 2024 20:55:26 -0800 Subject: [PATCH 20/27] Fix formatting --- .../deep-links/configure-deep-link-settings.mdx | 15 ++++++++------- .../deep-links/configure-deep-link-settings.mdx | 15 ++++++++------- 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/src/content/docs/sdk/ios/v4/features/deep-links/configure-deep-link-settings.mdx b/src/content/docs/sdk/ios/v4/features/deep-links/configure-deep-link-settings.mdx index fc95266c8..b12c632f8 100644 --- a/src/content/docs/sdk/ios/v4/features/deep-links/configure-deep-link-settings.mdx +++ b/src/content/docs/sdk/ios/v4/features/deep-links/configure-deep-link-settings.mdx @@ -14,8 +14,9 @@ redirects: ---
-## Create an app [Create an app](https://help.adjust.com/en/article/app-setup) -in the Adjust dashboard. +## Create an app + +[Create an app](https://help.adjust.com/en/article/app-setup) in the Adjust dashboard. Please note that the Adjust dashboard only supports one bundle ID (`com.example.app`) and app scheme (`example://`) for each app. If your app uses different bundle IDs and/or app schemes for your release and debug builds, create a separate debug app. @@ -24,11 +25,11 @@ Please note that the Adjust dashboard only supports one bundle ID (`com.example. Add iOS in the platform settings for your app. This step requires entering the bundle ID. If you're not sure of the bundle ID for your app build, enter a temporary value (`com.example.app`) to save the platform settings. Follow the rest of this guide to collect all required data points, then return to platform settings in the dashboard to finish the configuration.
-## Set up a branded domain In the Adjust dashboard, [set up a branded -domain](https://help.adjust.com/en/article/set-up-branded-domain) using Adjust's -go.link domain (for example: `brandname.go.link`). +## Set up a branded domain{" "} + +In the Adjust dashboard, [set up a branded domain](https://help.adjust.com/en/article/set-up-branded-domain) using Adjust's go.link domain (for example: `brandname.go.link`). -If your iOS app and Android app use separate apps in Adjust, ensure that you select the same branded domain (`brandname.go.link`) for both apps in the Adjust dashboard. This allows you to use the same branded domain for both platforms. +If your iOS app and Android app use separate apps in Adjust, ensure that you select the same branded domain (`brandname.go.link`) for both apps in the Adjust dashboard. If you use separate apps for release and debug builds, create a separate branded domain for your debug app (`brandnamedebug.go.link`). If your debug apps use separate apps for iOS and Android in Adjust, ensure that you also select this same branded domain for both debug apps in the Adjust dashboard. @@ -46,7 +47,7 @@ Make note of your branded domains to configure in Xcode in the next section. 5. Complete these steps for both the **Release** and **Debug** sub-tabs: - Make note of the value in the **Bundle Identifier** field. This is your app's bundle ID ("Release Bundle ID" or "Debug Bundle ID," respectively), which you need to configure in the iOS platform settings in the Adjust dashboard. - - In the **Associated Domains** section, add an entry for each of your branded domains. Here is an example entry using the branded domain `brandname.go.link`: + - In the **Associated Domains** section, add an entry for each of your branded domains. For the example domain `brandname.go.link`, here is the required entry: `applinks:brandname.go.link` diff --git a/src/content/docs/sdk/ios/v5/features/deep-links/configure-deep-link-settings.mdx b/src/content/docs/sdk/ios/v5/features/deep-links/configure-deep-link-settings.mdx index 378a6589d..b01bd56e4 100644 --- a/src/content/docs/sdk/ios/v5/features/deep-links/configure-deep-link-settings.mdx +++ b/src/content/docs/sdk/ios/v5/features/deep-links/configure-deep-link-settings.mdx @@ -14,8 +14,9 @@ redirects: ---
-## Create an app [Create an app](https://help.adjust.com/en/article/app-setup) -in the Adjust dashboard. +## Create an app + +[Create an app](https://help.adjust.com/en/article/app-setup) in the Adjust dashboard. Please note that the Adjust dashboard only supports one bundle ID (`com.example.app`) and app scheme (`example://`) for each app. If your app uses different bundle IDs and/or app schemes for your release and debug builds, create a separate debug app. @@ -24,11 +25,11 @@ Please note that the Adjust dashboard only supports one bundle ID (`com.example. Add iOS in the platform settings for your app. This step requires entering the bundle ID. If you're not sure of the bundle ID for your app build, enter a temporary value (`com.example.app`) to save the platform settings. Follow the rest of this guide to collect all required data points, then return to platform settings in the dashboard to finish the configuration.
-## Set up a branded domain In the Adjust dashboard, [set up a branded -domain](https://help.adjust.com/en/article/set-up-branded-domain) using Adjust's -go.link domain (for example: `brandname.go.link`). +## Set up a branded domain{" "} + +In the Adjust dashboard, [set up a branded domain](https://help.adjust.com/en/article/set-up-branded-domain) using Adjust's go.link domain (for example: `brandname.go.link`). -If your iOS app and Android app use separate apps in Adjust, ensure that you select the same branded domain (`brandname.go.link`) for both apps in the Adjust dashboard. This allows you to use the same branded domain for both platforms. +If your iOS app and Android app use separate apps in Adjust, ensure that you select the same branded domain (`brandname.go.link`) for both apps in the Adjust dashboard. If you use separate apps for release and debug builds, create a separate branded domain for your debug app (`brandnamedebug.go.link`). If your debug apps use separate apps for iOS and Android in Adjust, ensure that you also select this same branded domain for both debug apps in the Adjust dashboard. @@ -46,7 +47,7 @@ Make note of your branded domains to configure in Xcode in the next section. 5. Complete these steps for both the **Release** and **Debug** sub-tabs: - Make note of the value in the **Bundle Identifier** field. This is your app's bundle ID ("Release Bundle ID" or "Debug Bundle ID," respectively), which you need to configure in the iOS platform settings in the Adjust dashboard. - - In the **Associated Domains** section, add an entry for each of your branded domains. Here is an example entry using the branded domain `brandname.go.link`: + - In the **Associated Domains** section, add an entry for each of your branded domains. For the example domain `brandname.go.link`, here is the required entry: `applinks:brandname.go.link` From 46748cd8c71d6d812da163f80aaa8f011a450876 Mon Sep 17 00:00:00 2001 From: Dave Mead Date: Sat, 30 Nov 2024 21:56:48 -0800 Subject: [PATCH 21/27] Revise some text --- .../v4/features/deep-links/set-up-deep-linking.mdx | 14 +++++++------- .../v5/features/deep-links/set-up-deep-linking.mdx | 14 +++++++------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/content/docs/sdk/ios/v4/features/deep-links/set-up-deep-linking.mdx b/src/content/docs/sdk/ios/v4/features/deep-links/set-up-deep-linking.mdx index 9f844eb9e..82c8641ee 100644 --- a/src/content/docs/sdk/ios/v4/features/deep-links/set-up-deep-linking.mdx +++ b/src/content/docs/sdk/ios/v4/features/deep-links/set-up-deep-linking.mdx @@ -242,17 +242,17 @@ class AppDelegate: UIResponder, UIApplicationDelegate,
-For deferred deep linking, this is the sequence for most apps: +Most apps have some kind of onboarding process (for example: ATT prompt, onboarding screens, login prompt). When setting up deferred deep linking, you have to ensure that onboarding and launching deferred deep links don't interfere with each other. One approach is to wait until after onboarding to launch deferred deep links. Here's how it works in the code examples: 1. A user who doesn't have the app installed clicks an Adjust deep link, which redirects them to the app store. 2. The user installs and opens the app. -3. The app begins its onboarding process (for example: ATT prompt, onboarding screens, login prompt). +3. The app begins its onboarding process. 4. After the ATT prompt response or timeout, the Adjust SDK sends session and attribution requests to Adjust's servers. 5. Adjust's servers respond with attribution data, including the deep link the user clicked on ("deferred deep link"). 6. The Adjust SDK triggers a deferred deep link callback in your app, which stores the deferred deep link (shown in the AppDelegate implementation above). 7. The app handles the deferred deep link in one of two ways (shown in the ViewController example class below): - Once onboarding completes, the app checks for and handles any stored deferred deep link. - - If a deferred deep link arrives after onboarding, a notification triggers the same check and handling. + - If a deferred deep link arrives shortly after onboarding, a notification triggers the same check and handling. 8. The app navigates the user to the deep link screen. @@ -707,17 +707,17 @@ class AppDelegate: UIResponder, UIApplicationDelegate, AdjustDelegate {
-For deferred deep linking, this is the sequence for most apps: +Most apps have some kind of onboarding process (for example: ATT prompt, onboarding screens, login prompt). When setting up deferred deep linking, you have to ensure that onboarding and launching deferred deep links don't interfere with each other. One approach is to wait until after onboarding to launch deferred deep links. Here's how it works in the code examples: 1. A user who doesn't have the app installed clicks an Adjust deep link, which redirects them to the app store. 2. The user installs and opens the app. -3. The app begins its onboarding process (for example: ATT prompt, onboarding screens, login prompt). +3. The app begins its onboarding process. 4. After the ATT prompt response or timeout, the Adjust SDK sends session and attribution requests to Adjust's servers. 5. Adjust's servers respond with attribution data, including the deep link the user clicked on ("deferred deep link"). 6. The Adjust SDK triggers a deferred deep link callback in your app, which stores the deferred deep link (shown in the AppDelegate implementation above). 7. The app handles the deferred deep link in one of two ways (shown in the ContentView example class below): - Once onboarding completes, the app checks for and handles any stored deferred deep link. - - If a deferred deep link arrives after onboarding, a notification triggers the same check and handling. + - If a deferred deep link arrives shortly after onboarding, a notification triggers the same check and handling. 8. The app navigates the user to the deep link screen. @@ -860,7 +860,7 @@ The class performs the following tasks: 2. After processing, the class parses the link and navigates to the appropriate screen. This part of the code is specific to each app. Your app has to implement its own logic for handling deep links and opening the corresponding content. Your deep link handling has to meet the following key requirements: -- Your app should treat Adjust branded links the same as other universal links, such as your own. Adjust recommends implementing domain-agnostic deep link handling logic to meet this requirement. For example, the following links should navigate to the same screen in your app: +- Your app should treat Adjust branded links the same as other universal links, such as your own. For example, the following links should navigate to the same screen in your app: - Adjust branded link: `https://example.go.link/summer-clothes?promo=beach` - Your universal link: `https://www.example.com/summer-clothes?promo=beach` - In cases where iOS doesn't support universal links, Adjust automatically converts them to app scheme deep links. Additionally, Adjust's servers convert all deferred deep links to app scheme deep link format. Therefore it's crucial for the app to handle universal links and app scheme deep links equivalently. For example, the following links should navigate to the same screen in your app: diff --git a/src/content/docs/sdk/ios/v5/features/deep-links/set-up-deep-linking.mdx b/src/content/docs/sdk/ios/v5/features/deep-links/set-up-deep-linking.mdx index 25c584d74..76771e870 100644 --- a/src/content/docs/sdk/ios/v5/features/deep-links/set-up-deep-linking.mdx +++ b/src/content/docs/sdk/ios/v5/features/deep-links/set-up-deep-linking.mdx @@ -242,17 +242,17 @@ class AppDelegate: UIResponder, UIApplicationDelegate,
-For deferred deep linking, this is the sequence for most apps: +Most apps have some kind of onboarding process (for example: ATT prompt, onboarding screens, login prompt). When setting up deferred deep linking, you have to ensure that onboarding and launching deferred deep links don't interfere with each other. One approach is to wait until after onboarding to launch deferred deep links. Here's how it works in the code examples: 1. A user who doesn't have the app installed clicks an Adjust deep link, which redirects them to the app store. 2. The user installs and opens the app. -3. The app begins its onboarding process (for example: ATT prompt, onboarding screens, login prompt). +3. The app begins its onboarding process. 4. After the ATT prompt response or timeout, the Adjust SDK sends session and attribution requests to Adjust's servers. 5. Adjust's servers respond with attribution data, including the deep link the user clicked on ("deferred deep link"). 6. The Adjust SDK triggers a deferred deep link callback in your app, which stores the deferred deep link (shown in the AppDelegate implementation above). 7. The app handles the deferred deep link in one of two ways (shown in the ViewController example class below): - Once onboarding completes, the app checks for and handles any stored deferred deep link. - - If a deferred deep link arrives after onboarding, a notification triggers the same check and handling. + - If a deferred deep link arrives shortly after onboarding, a notification triggers the same check and handling. 8. The app navigates the user to the deep link screen. @@ -707,17 +707,17 @@ class AppDelegate: UIResponder, UIApplicationDelegate, AdjustDelegate {
-For deferred deep linking, this is the sequence for most apps: +Most apps have some kind of onboarding process (for example: ATT prompt, onboarding screens, login prompt). When setting up deferred deep linking, you have to ensure that onboarding and launching deferred deep links don't interfere with each other. One approach is to wait until after onboarding to launch deferred deep links. Here's how it works in the code examples: 1. A user who doesn't have the app installed clicks an Adjust deep link, which redirects them to the app store. 2. The user installs and opens the app. -3. The app begins its onboarding process (for example: ATT prompt, onboarding screens, login prompt). +3. The app begins its onboarding process. 4. After the ATT prompt response or timeout, the Adjust SDK sends session and attribution requests to Adjust's servers. 5. Adjust's servers respond with attribution data, including the deep link the user clicked on ("deferred deep link"). 6. The Adjust SDK triggers a deferred deep link callback in your app, which stores the deferred deep link (shown in the AppDelegate implementation above). 7. The app handles the deferred deep link in one of two ways (shown in the ContentView example class below): - Once onboarding completes, the app checks for and handles any stored deferred deep link. - - If a deferred deep link arrives after onboarding, a notification triggers the same check and handling. + - If a deferred deep link arrives shortly after onboarding, a notification triggers the same check and handling. 8. The app navigates the user to the deep link screen. @@ -860,7 +860,7 @@ The class performs the following tasks: 2. After processing, the class parses the link and navigates to the appropriate screen. This part of the code is specific to each app. Your app has to implement its own logic for handling deep links and opening the corresponding content. Your deep link handling has to meet the following key requirements: -- Your app should treat Adjust branded links the same as other universal links, such as your own. Adjust recommends implementing domain-agnostic deep link handling logic to meet this requirement. For example, the following links should navigate to the same screen in your app: +- Your app should treat Adjust branded links the same as other universal links, such as your own. For example, the following links should navigate to the same screen in your app: - Adjust branded link: `https://example.go.link/summer-clothes?promo=beach` - Your universal link: `https://www.example.com/summer-clothes?promo=beach` - In cases where iOS doesn't support universal links, Adjust automatically converts them to app scheme deep links. Additionally, Adjust's servers convert all deferred deep links to app scheme deep link format. Therefore it's crucial for the app to handle universal links and app scheme deep links equivalently. For example, the following links should navigate to the same screen in your app: From 4b5ed6cdbb3a20bc151f0646e5d0ab4d6e865af9 Mon Sep 17 00:00:00 2001 From: Dave Mead Date: Sat, 30 Nov 2024 22:29:00 -0800 Subject: [PATCH 22/27] Fix capitalization --- .../sdk/ios/v4/features/deep-links/set-up-deep-linking.mdx | 6 +++--- .../sdk/ios/v5/features/deep-links/set-up-deep-linking.mdx | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/content/docs/sdk/ios/v4/features/deep-links/set-up-deep-linking.mdx b/src/content/docs/sdk/ios/v4/features/deep-links/set-up-deep-linking.mdx index 82c8641ee..cbbb66f47 100644 --- a/src/content/docs/sdk/ios/v4/features/deep-links/set-up-deep-linking.mdx +++ b/src/content/docs/sdk/ios/v4/features/deep-links/set-up-deep-linking.mdx @@ -22,7 +22,7 @@ Please follow the steps in the section that corresponds to the type of app you h In addition, implement deep link handling logic: -- [Deep Link Handler](#deep-link-handler) +- [Deep link handler](#deep-link-handler) ## UIKit apps using AppDelegate lifecycle @@ -438,7 +438,7 @@ extension Notification.Name { Lastly, implement your deep link handling logic: -[Deep Link Handler](#deep-link-handler) +[Deep link handler](#deep-link-handler) ## UIKit apps using SceneDelegate lifecycle @@ -795,7 +795,7 @@ extension Notification.Name { Lastly, implement your deep link handling logic: -[Deep Link Handler](#deep-link-handler) +[Deep link handler](#deep-link-handler) ## SwiftUI apps using SceneDelegate lifecycle diff --git a/src/content/docs/sdk/ios/v5/features/deep-links/set-up-deep-linking.mdx b/src/content/docs/sdk/ios/v5/features/deep-links/set-up-deep-linking.mdx index 76771e870..c25b9c238 100644 --- a/src/content/docs/sdk/ios/v5/features/deep-links/set-up-deep-linking.mdx +++ b/src/content/docs/sdk/ios/v5/features/deep-links/set-up-deep-linking.mdx @@ -438,7 +438,7 @@ extension Notification.Name { Lastly, implement your deep link handling logic: -[Deep Link Handler](#deep-link-handler) +[Deep link handler](#deep-link-handler) ## UIKit apps using SceneDelegate lifecycle @@ -795,7 +795,7 @@ extension Notification.Name { Lastly, implement your deep link handling logic: -[Deep Link Handler](#deep-link-handler) +[Deep link handler](#deep-link-handler) ## SwiftUI apps using SceneDelegate lifecycle From 238473cb851cf78a4271d0b0fc09ac4b5c659191 Mon Sep 17 00:00:00 2001 From: Dave Mead Date: Sat, 30 Nov 2024 23:57:51 -0800 Subject: [PATCH 23/27] Fix some code examples --- .../deep-links/set-up-deep-linking.mdx | 216 ++++++----- .../deep-links/set-up-deep-linking.mdx | 335 +++++++++--------- 2 files changed, 271 insertions(+), 280 deletions(-) diff --git a/src/content/docs/sdk/ios/v4/features/deep-links/set-up-deep-linking.mdx b/src/content/docs/sdk/ios/v4/features/deep-links/set-up-deep-linking.mdx index cbbb66f47..d4c9051bf 100644 --- a/src/content/docs/sdk/ios/v4/features/deep-links/set-up-deep-linking.mdx +++ b/src/content/docs/sdk/ios/v4/features/deep-links/set-up-deep-linking.mdx @@ -874,7 +874,8 @@ The class performs the following tasks: ```swift import Adjust -// import UIKit and/or SwiftUI +import UIKit +// import SwiftUI - if applicable class DeeplinkHandler { static func handleDeeplink(_ incomingLink: URL) { @@ -883,55 +884,53 @@ class DeeplinkHandler { // If not, retrieve original link. Adjust.processDeeplink(incomingLink) { processedLinkString in guard let processedLink = URL(string: processedLinkString) else { return } - } - // Extract path, query items, and fragment from the processed link. - let components = URLComponents( - url: processedLink, - resolvingAgainstBaseURL: true - ) - - // For app scheme deep links, set path = host + path - var path: String - if let scheme = processedLink.scheme, scheme == "http" || scheme == "https" - { - path = components.path - } else { - path = (components.host ?? "") + components.path - if !path.isEmpty && !path.hasPrefix("/") { - path = "/" + path + // Extract path, query items, and fragment from the processed link. + guard let components = URLComponents( + url: processedLink, + resolvingAgainstBaseURL: true + ) else { return } + + // For app scheme deep links, set path = host + path + var path: String + if let scheme = processedLink.scheme, scheme == "http" || + scheme == "https" { + path = components.path + } else { + path = (components.host ?? "") + components.path + if !path.isEmpty && !path.hasPrefix("/") { + path = "/" + path + } } - } - - let queryItems = components?.queryItems ?? [] - - // Parse query parameters into a dictionary for easier access - let params = queryItems.reduce(into: [String: String]()) { result, item in - result[item.name] = item.value - } - - let fragment = components?.fragment - // Implement the navigation or other app-specific logic based on - // the deep link components. - DispatchQueue.main.async { - // Example of navigating based on the path or other components. - // Replace with your actual navigation logic. - switch path { - case "/product": - if let productId = params["id"] { - handleProduct(productId: productId) - } - case "/category": - if let categoryName = params["name"] { - handleCategory(category: categoryName) - } - case "/search": - if let query = params["q"] { - handleSearch(query: query) + let queryItems = components.queryItems ?? [] + // Parse query parameters into a dictionary for easier access + let params = queryItems.reduce(into: [String: String]()) { result, item in + result[item.name] = item.value + } + let fragment = components.fragment + + // Implement the navigation or other app-specific logic based on + // the deep link components. + DispatchQueue.main.async { + // Example of navigating based on the path or other components. + // Replace with your actual navigation logic. + switch path { + case "/product": + if let productId = params["id"] { + handleProduct(productId: productId) + } + case "/category": + if let categoryName = params["name"] { + handleCategory(category: categoryName) + } + case "/search": + if let query = params["q"] { + handleSearch(query: query) + } + default: + print("Unhandled deep link path: \(path)") } - default: - print("Unhandled deep link path: \(path)") } } } @@ -940,7 +939,6 @@ class DeeplinkHandler { // Example UIKit implementation: // let productVC = ProductViewController(productId: productId) // navigationController.pushViewController(productVC, animated: true) - // Example SwiftUI implementation: // let productView = ProductView(productId: productId) // navigationPath.append(productView) @@ -951,7 +949,6 @@ class DeeplinkHandler { // Example UIKit implementation: // let categoryVC = CategoryViewController(category: category) // navigationController.pushViewController(categoryVC, animated: true) - // Example SwiftUI implementation: // let categoryView = CategoryView(category: category) // navigationPath.append(categoryView) @@ -962,11 +959,10 @@ class DeeplinkHandler { // Example UIKit implementation: // let searchVC = SearchViewController(searchQuery: query) // navigationController.pushViewController(searchVC, animated: true) - // Example SwiftUI implementation: // let searchView = SearchView(searchQuery: query) // navigationPath.append(searchView) - // or: router.navigate(to: searchView) + // or: router.navigate(to: productView) } } ``` @@ -980,7 +976,6 @@ class DeeplinkHandler { ```objc #import -#import "DeeplinkDetailViewController.h" #import "DeeplinkHandler.h" @implementation DeeplinkHandler @@ -990,70 +985,71 @@ class DeeplinkHandler { // and retrieve full URL if short branded link. // If not, retrieve original link. [Adjust processDeeplink:incomingLink - completionHandler:^(NSString *_Nonnull processedLinkString) { - NSURL *processedLink = [NSURL URLWithString:processedLinkString]; - if (!processedLink) return; - }]; - - // Extract path, query items, and fragment from the processed link. - NSURLComponents *components = [NSURLComponents componentsWithURL:processedLink - resolvingAgainstBaseURL:YES]; - - // For app scheme deep links, set path = host + path - NSString *path; - if ([processedLink.scheme isEqualToString:@"http"] || - [processedLink.scheme isEqualToString:@"https"]) { - path = components.path; - } else { - path = [NSString - stringWithFormat:@"%@%@", components.host ?: @"", components.path]; - if (path.length > 0 && ![path hasPrefix:@"/"]) { - path = [@"/" stringByAppendingString:path]; + completionHandler:^(NSString *_Nonnull processedLinkString) { + NSURL *processedLink = [NSURL URLWithString:processedLinkString]; + if (!processedLink) return; + + // Extract path, query items, and fragment from the processed link. + NSURLComponents *components = + [NSURLComponents componentsWithURL:processedLink + resolvingAgainstBaseURL:YES]; + + // For app scheme deep links, set path = host + path + NSString *path; + if ([processedLink.scheme isEqualToString:@"http"] || + [processedLink.scheme isEqualToString:@"https"]) { + path = components.path; + } else { + path = [NSString stringWithFormat:@"%@%@", + components.host ?: @"", + components.path]; + if (path.length > 0 && ![path hasPrefix:@"/"]) { + path = [@"/" stringByAppendingString:path]; + } } - } - NSArray *queryItems = components.queryItems ?: @[]; + NSArray *queryItems = components.queryItems ?: @[]; - // Parse query parameters into a dictionary for easier access - NSMutableDictionary *params = - [NSMutableDictionary dictionary]; - for (NSURLQueryItem *item in queryItems) { - params[item.name] = item.value; - } + // Parse query parameters into a dictionary for easier access + NSMutableDictionary *params = + [NSMutableDictionary dictionary]; + for (NSURLQueryItem *item in queryItems) { + params[item.name] = item.value; + } - NSString *fragment = components.fragment; - - // Implement the navigation or other app-specific logic based on - // the deep link components. - dispatch_async(dispatch_get_main_queue(), ^{ - // Example of navigating based on the path or other components. - // Replace with your actual navigation logic. - if ([path isEqualToString:@"/product"]) { - NSString *productId = params[@"id"]; - if (productId) { - ProductViewController *productVC = - [[ProductViewController alloc] initWithProductId:productId]; - [self navigateToViewController:productVC]; - } - } else if ([path isEqualToString:@"/category"]) { - NSString *categoryName = params[@"name"]; - if (categoryName) { - CategoryViewController *categoryVC = - [[CategoryViewController alloc] initWithCategory:categoryName]; - [self navigateToViewController:categoryVC]; - } - } else if ([path isEqualToString:@"/search"]) { - NSString *query = params[@"q"]; - if (query) { - SearchViewController *searchVC = - [[SearchViewController alloc] initWithSearchQuery:query]; - [self navigateToViewController:searchVC]; + NSString *fragment = components.fragment; + + // Implement the navigation or other app-specific logic based on + // the deep link components. + dispatch_async(dispatch_get_main_queue(), ^{ + // Example of navigating based on the path or other components. + // Replace with your actual navigation logic. + if ([path isEqualToString:@"/product"]) { + NSString *productId = params[@"id"]; + if (productId) { + ProductViewController *productVC = + [[ProductViewController alloc] initWithProductId:productId]; + [self navigateToViewController:productVC]; + } + } else if ([path isEqualToString:@"/category"]) { + NSString *categoryName = params[@"name"]; + if (categoryName) { + CategoryViewController *categoryVC = + [[CategoryViewController alloc] initWithCategory:categoryName]; + [self navigateToViewController:categoryVC]; + } + } else if ([path isEqualToString:@"/search"]) { + NSString *query = params[@"q"]; + if (query) { + SearchViewController *searchVC = + [[SearchViewController alloc] initWithSearchQuery:query]; + [self navigateToViewController:searchVC]; + } + } else { + NSLog(@"Unhandled deep link path: %@", path); } - } else { - NSLog(@"Unhandled deep link path: %@", path); - } - }); -}]; + }); + }]; } + (void)navigateToViewController:(UIViewController *)viewController { diff --git a/src/content/docs/sdk/ios/v5/features/deep-links/set-up-deep-linking.mdx b/src/content/docs/sdk/ios/v5/features/deep-links/set-up-deep-linking.mdx index c25b9c238..b7171a1ed 100644 --- a/src/content/docs/sdk/ios/v5/features/deep-links/set-up-deep-linking.mdx +++ b/src/content/docs/sdk/ios/v5/features/deep-links/set-up-deep-linking.mdx @@ -295,7 +295,7 @@ class ViewController: UIViewController { // Show ATT prompt after delay to ensure app is active DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) { - Adjust.requestTrackingAuthorization { _ in } + Adjust.requestTrackingAuthorizationWithCompletionHandler { _ in } } // After onboarding, set hasCompletedOnboarding to true @@ -742,7 +742,7 @@ struct ContentView: View { // Show ATT prompt after delay to ensure app is active .onAppear { DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) { - Adjust.requestTrackingAuthorization { _ in } + Adjust.requestTrackingAuthorizationWithCompletionHandler { _ in } } } @@ -874,101 +874,96 @@ The class performs the following tasks: ```swift import Adjust -// import UIKit and/or SwiftUI - +import UIKit +// import SwiftUI - if applicable class DeeplinkHandler { - static func handleDeeplink(_ incomingLink: URL) { - // Send incoming deep link to Adjust's servers for attribution - // and retrieve full URL if short branded link. - // If not, retrieve original link. - let deeplink = ADJDeeplink(deeplink: incomingLink) - Adjust.processAndResolveDeeplink(deeplink) { processedLinkString in - guard let processedLink = URL(string: processedLinkString) else { return } - } - - // Extract path, query items, and fragment from the processed link. - let components = URLComponents( - url: processedLink, - resolvingAgainstBaseURL: true - ) - - // For app scheme deep links, set path = host + path - var path: String - if let scheme = processedLink.scheme, scheme == "http" || scheme == "https" - { - path = components.path - } else { - path = (components.host ?? "") + components.path - if !path.isEmpty && !path.hasPrefix("/") { - path = "/" + path - } - } - - let queryItems = components?.queryItems ?? [] - - // Parse query parameters into a dictionary for easier access - let params = queryItems.reduce(into: [String: String]()) { result, item in - result[item.name] = item.value - } - - let fragment = components?.fragment - - // Implement the navigation or other app-specific logic based on - // the deep link components. - DispatchQueue.main.async { - // Example of navigating based on the path or other components. - // Replace with your actual navigation logic. - switch path { - case "/product": - if let productId = params["id"] { - handleProduct(productId: productId) - } - case "/category": - if let categoryName = params["name"] { - handleCategory(category: categoryName) - } - case "/search": - if let query = params["q"] { - handleSearch(query: query) - } - default: - print("Unhandled deep link path: \(path)") - } - } - } - - private static func handleProduct(productId: String) { - // Example UIKit implementation: - // let productVC = ProductViewController(productId: productId) - // navigationController.pushViewController(productVC, animated: true) - - // Example SwiftUI implementation: - // let productView = ProductView(productId: productId) - // navigationPath.append(productView) - // or: router.navigate(to: productView) - } - - private static func handleCategory(category: String) { - // Example UIKit implementation: - // let categoryVC = CategoryViewController(category: category) - // navigationController.pushViewController(categoryVC, animated: true) - - // Example SwiftUI implementation: - // let categoryView = CategoryView(category: category) - // navigationPath.append(categoryView) - // or: router.navigate(to: categoryView) - } - - private static func handleSearch(query: String) { - // Example UIKit implementation: - // let searchVC = SearchViewController(searchQuery: query) - // navigationController.pushViewController(searchVC, animated: true) - - // Example SwiftUI implementation: - // let searchView = SearchView(searchQuery: query) - // navigationPath.append(searchView) - // or: router.navigate(to: searchView) - } + static func handleDeeplink(_ incomingLink: URL) { + // Send incoming deep link to Adjust's servers for attribution + // and retrieve full URL if short branded link. + // If not, retrieve original link. + let deeplink = ADJDeeplink(deeplink: incomingLink) + Adjust.processAndResolveDeeplink(deeplink) { processedLinkString in + guard let processedLink = URL(string: processedLinkString) else { return } + + // Extract path, query items, and fragment from the processed link. + guard let components = URLComponents( + url: processedLink, + resolvingAgainstBaseURL: true + ) else { return } + + // For app scheme deep links, set path = host + path + var path: String + if let scheme = processedLink.scheme, scheme == "http" || + scheme == "https" { + path = components.path + } else { + path = (components.host ?? "") + components.path + if !path.isEmpty && !path.hasPrefix("/") { + path = "/" + path + } + } + + let queryItems = components.queryItems ?? [] + // Parse query parameters into a dictionary for easier access + let params = queryItems.reduce(into: [String: String]()) { result, item in + result[item.name] = item.value + } + let fragment = components.fragment + + // Implement the navigation or other app-specific logic based on + // the deep link components. + DispatchQueue.main.async { + // Example of navigating based on the path or other components. + // Replace with your actual navigation logic. + switch path { + case "/product": + if let productId = params["id"] { + handleProduct(productId: productId) + } + case "/category": + if let categoryName = params["name"] { + handleCategory(category: categoryName) + } + case "/search": + if let query = params["q"] { + handleSearch(query: query) + } + default: + print("Unhandled deep link path: \(path)") + } + } + } + } + + private static func handleProduct(productId: String) { + // Example UIKit implementation: + // let productVC = ProductViewController(productId: productId) + // navigationController.pushViewController(productVC, animated: true) + // Example SwiftUI implementation: + // let productView = ProductView(productId: productId) + // navigationPath.append(productView) + // or: router.navigate(to: productView) + } + + private static func handleCategory(category: String) { + // Example UIKit implementation: + // let categoryVC = CategoryViewController(category: category) + // navigationController.pushViewController(categoryVC, animated: true) + // Example SwiftUI implementation: + // let categoryView = CategoryView(category: category) + // navigationPath.append(categoryView) + // or: router.navigate(to: categoryView) + } + + private static func handleSearch(query: String) { + // Example UIKit implementation: + // let searchVC = SearchViewController(searchQuery: query) + // navigationController.pushViewController(searchVC, animated: true) + // Example SwiftUI implementation: + // let searchView = SearchView(searchQuery: query) + // navigationPath.append(searchView) + // or: router.navigate(to: productView) + } } ``` @@ -981,88 +976,88 @@ class DeeplinkHandler { ```objc #import -#import "DeeplinkDetailViewController.h" #import "DeeplinkHandler.h" @implementation DeeplinkHandler + (void)handleDeeplink:(NSURL *)incomingLink { - // Send incoming deep link to Adjust's servers for attribution - // and retrieve full URL if short branded link. - // If not, retrieve original link. - ADJDeeplink *deeplink = [[ADJDeeplink alloc] initWithDeeplink:incomingURL]; - [Adjust processAndResolveDeeplink:deeplink - withCompletionHandler:^(NSString *_Nonnull processedLinkString) { - NSURL *processedLink = [NSURL URLWithString:processedLinkString]; - if (!processedLink) return; - }]; - - // Extract path, query items, and fragment from the processed link. - NSURLComponents *components = [NSURLComponents componentsWithURL:processedLink - resolvingAgainstBaseURL:YES]; - - // For app scheme deep links, set path = host + path - NSString *path; - if ([processedLink.scheme isEqualToString:@"http"] || - [processedLink.scheme isEqualToString:@"https"]) { - path = components.path; - } else { - path = [NSString - stringWithFormat:@"%@%@", components.host ?: @"", components.path]; - if (path.length > 0 && ![path hasPrefix:@"/"]) { - path = [@"/" stringByAppendingString:path]; - } - } - - NSArray *queryItems = components.queryItems ?: @[]; - - // Parse query parameters into a dictionary for easier access - NSMutableDictionary *params = - [NSMutableDictionary dictionary]; - for (NSURLQueryItem *item in queryItems) { - params[item.name] = item.value; - } - - NSString *fragment = components.fragment; - - // Implement the navigation or other app-specific logic based on - // the deep link components. - dispatch_async(dispatch_get_main_queue(), ^{ - // Example of navigating based on the path or other components. - // Replace with your actual navigation logic. - if ([path isEqualToString:@"/product"]) { - NSString *productId = params[@"id"]; - if (productId) { - ProductViewController *productVC = - [[ProductViewController alloc] initWithProductId:productId]; - [self navigateToViewController:productVC]; - } - } else if ([path isEqualToString:@"/category"]) { - NSString *categoryName = params[@"name"]; - if (categoryName) { - CategoryViewController *categoryVC = - [[CategoryViewController alloc] initWithCategory:categoryName]; - [self navigateToViewController:categoryVC]; - } - } else if ([path isEqualToString:@"/search"]) { - NSString *query = params[@"q"]; - if (query) { - SearchViewController *searchVC = - [[SearchViewController alloc] initWithSearchQuery:query]; - [self navigateToViewController:searchVC]; - } - } else { - NSLog(@"Unhandled deep link path: %@", path); - } - }); -}]; + // Send incoming deep link to Adjust's servers for attribution + // and retrieve full URL if short branded link. + // If not, retrieve original link. + ADJDeeplink *deeplink = [[ADJDeeplink alloc] initWithDeeplink:incomingURL]; + [Adjust processAndResolveDeeplink:deeplink + withCompletionHandler:^(NSString *_Nonnull processedLinkString) { + NSURL *processedLink = [NSURL URLWithString:processedLinkString]; + if (!processedLink) return; + + // Extract path, query items, and fragment from the processed link. + NSURLComponents *components = + [NSURLComponents componentsWithURL:processedLink + resolvingAgainstBaseURL:YES]; + + // For app scheme deep links, set path = host + path + NSString *path; + if ([processedLink.scheme isEqualToString:@"http"] || + [processedLink.scheme isEqualToString:@"https"]) { + path = components.path; + } else { + path = [NSString stringWithFormat:@"%@%@", + components.host ?: @"", + components.path]; + if (path.length > 0 && ![path hasPrefix:@"/"]) { + path = [@"/" stringByAppendingString:path]; + } + } + + NSArray *queryItems = components.queryItems ?: @[]; + + // Parse query parameters into a dictionary for easier access + NSMutableDictionary *params = + [NSMutableDictionary dictionary]; + for (NSURLQueryItem *item in queryItems) { + params[item.name] = item.value; + } + + NSString *fragment = components.fragment; + + // Implement the navigation or other app-specific logic based on + // the deep link components. + dispatch_async(dispatch_get_main_queue(), ^{ + // Example of navigating based on the path or other components. + // Replace with your actual navigation logic. + if ([path isEqualToString:@"/product"]) { + NSString *productId = params[@"id"]; + if (productId) { + ProductViewController *productVC = + [[ProductViewController alloc] initWithProductId:productId]; + [self navigateToViewController:productVC]; + } + } else if ([path isEqualToString:@"/category"]) { + NSString *categoryName = params[@"name"]; + if (categoryName) { + CategoryViewController *categoryVC = + [[CategoryViewController alloc] initWithCategory:categoryName]; + [self navigateToViewController:categoryVC]; + } + } else if ([path isEqualToString:@"/search"]) { + NSString *query = params[@"q"]; + if (query) { + SearchViewController *searchVC = + [[SearchViewController alloc] initWithSearchQuery:query]; + [self navigateToViewController:searchVC]; + } + } else { + NSLog(@"Unhandled deep link path: %@", path); + } + }); + }]; } + (void)navigateToViewController:(UIViewController *)viewController { - // Example navigation implementation: - // UINavigationController *navigationController = [self - // getNavigationController]; [navigationController - // pushViewController:viewController animated:YES]; + // Example navigation implementation: + // UINavigationController *navigationController = [self + // getNavigationController]; [navigationController + // pushViewController:viewController animated:YES]; } @end From 58badd129f096cad4cabb261627ee745f62e7579 Mon Sep 17 00:00:00 2001 From: Dave Mead Date: Sun, 1 Dec 2024 00:08:12 -0800 Subject: [PATCH 24/27] Fix spacing --- .../docs/sdk/ios/v5/features/deep-links/set-up-deep-linking.mdx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/content/docs/sdk/ios/v5/features/deep-links/set-up-deep-linking.mdx b/src/content/docs/sdk/ios/v5/features/deep-links/set-up-deep-linking.mdx index b7171a1ed..d59a22cc9 100644 --- a/src/content/docs/sdk/ios/v5/features/deep-links/set-up-deep-linking.mdx +++ b/src/content/docs/sdk/ios/v5/features/deep-links/set-up-deep-linking.mdx @@ -876,6 +876,7 @@ The class performs the following tasks: import Adjust import UIKit // import SwiftUI - if applicable + class DeeplinkHandler { static func handleDeeplink(_ incomingLink: URL) { // Send incoming deep link to Adjust's servers for attribution From 6775d043fa70e14e949313aeee43275c83ea51f2 Mon Sep 17 00:00:00 2001 From: Dave Mead Date: Sun, 1 Dec 2024 22:24:56 -0800 Subject: [PATCH 25/27] Fix wording --- .../sdk/ios/v4/features/deep-links/set-up-deep-linking.mdx | 4 ++-- .../sdk/ios/v5/features/deep-links/set-up-deep-linking.mdx | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/content/docs/sdk/ios/v4/features/deep-links/set-up-deep-linking.mdx b/src/content/docs/sdk/ios/v4/features/deep-links/set-up-deep-linking.mdx index d4c9051bf..49c620e8a 100644 --- a/src/content/docs/sdk/ios/v4/features/deep-links/set-up-deep-linking.mdx +++ b/src/content/docs/sdk/ios/v4/features/deep-links/set-up-deep-linking.mdx @@ -249,7 +249,7 @@ Most apps have some kind of onboarding process (for example: ATT prompt, onboard 3. The app begins its onboarding process. 4. After the ATT prompt response or timeout, the Adjust SDK sends session and attribution requests to Adjust's servers. 5. Adjust's servers respond with attribution data, including the deep link the user clicked on ("deferred deep link"). -6. The Adjust SDK triggers a deferred deep link callback in your app, which stores the deferred deep link (shown in the AppDelegate implementation above). +6. The Adjust SDK triggers a deferred deep link callback in the app, which stores the deferred deep link (shown in the AppDelegate implementation above). 7. The app handles the deferred deep link in one of two ways (shown in the ViewController example class below): - Once onboarding completes, the app checks for and handles any stored deferred deep link. - If a deferred deep link arrives shortly after onboarding, a notification triggers the same check and handling. @@ -714,7 +714,7 @@ Most apps have some kind of onboarding process (for example: ATT prompt, onboard 3. The app begins its onboarding process. 4. After the ATT prompt response or timeout, the Adjust SDK sends session and attribution requests to Adjust's servers. 5. Adjust's servers respond with attribution data, including the deep link the user clicked on ("deferred deep link"). -6. The Adjust SDK triggers a deferred deep link callback in your app, which stores the deferred deep link (shown in the AppDelegate implementation above). +6. The Adjust SDK triggers a deferred deep link callback in the app, which stores the deferred deep link (shown in the AppDelegate implementation above). 7. The app handles the deferred deep link in one of two ways (shown in the ContentView example class below): - Once onboarding completes, the app checks for and handles any stored deferred deep link. - If a deferred deep link arrives shortly after onboarding, a notification triggers the same check and handling. diff --git a/src/content/docs/sdk/ios/v5/features/deep-links/set-up-deep-linking.mdx b/src/content/docs/sdk/ios/v5/features/deep-links/set-up-deep-linking.mdx index d59a22cc9..d70ea28da 100644 --- a/src/content/docs/sdk/ios/v5/features/deep-links/set-up-deep-linking.mdx +++ b/src/content/docs/sdk/ios/v5/features/deep-links/set-up-deep-linking.mdx @@ -249,7 +249,7 @@ Most apps have some kind of onboarding process (for example: ATT prompt, onboard 3. The app begins its onboarding process. 4. After the ATT prompt response or timeout, the Adjust SDK sends session and attribution requests to Adjust's servers. 5. Adjust's servers respond with attribution data, including the deep link the user clicked on ("deferred deep link"). -6. The Adjust SDK triggers a deferred deep link callback in your app, which stores the deferred deep link (shown in the AppDelegate implementation above). +6. The Adjust SDK triggers a deferred deep link callback in the app, which stores the deferred deep link (shown in the AppDelegate implementation above). 7. The app handles the deferred deep link in one of two ways (shown in the ViewController example class below): - Once onboarding completes, the app checks for and handles any stored deferred deep link. - If a deferred deep link arrives shortly after onboarding, a notification triggers the same check and handling. @@ -714,7 +714,7 @@ Most apps have some kind of onboarding process (for example: ATT prompt, onboard 3. The app begins its onboarding process. 4. After the ATT prompt response or timeout, the Adjust SDK sends session and attribution requests to Adjust's servers. 5. Adjust's servers respond with attribution data, including the deep link the user clicked on ("deferred deep link"). -6. The Adjust SDK triggers a deferred deep link callback in your app, which stores the deferred deep link (shown in the AppDelegate implementation above). +6. The Adjust SDK triggers a deferred deep link callback in the app, which stores the deferred deep link (shown in the AppDelegate implementation above). 7. The app handles the deferred deep link in one of two ways (shown in the ContentView example class below): - Once onboarding completes, the app checks for and handles any stored deferred deep link. - If a deferred deep link arrives shortly after onboarding, a notification triggers the same check and handling. From ac21c4bf30978aa086903776913cdb9b6f83eb97 Mon Sep 17 00:00:00 2001 From: Dave Mead Date: Mon, 2 Dec 2024 00:38:20 -0800 Subject: [PATCH 26/27] Simplify deferred deep linking examples --- .../deep-links/set-up-deep-linking.mdx | 257 ++++++----------- .../deep-links/set-up-deep-linking.mdx | 267 ++++++------------ 2 files changed, 169 insertions(+), 355 deletions(-) diff --git a/src/content/docs/sdk/ios/v4/features/deep-links/set-up-deep-linking.mdx b/src/content/docs/sdk/ios/v4/features/deep-links/set-up-deep-linking.mdx index 49c620e8a..f25f75ec4 100644 --- a/src/content/docs/sdk/ios/v4/features/deep-links/set-up-deep-linking.mdx +++ b/src/content/docs/sdk/ios/v4/features/deep-links/set-up-deep-linking.mdx @@ -115,19 +115,17 @@ class AppDelegate: UIResponder, UIApplicationDelegate, // Receive deferred deep link via AdjustDelegate method func adjustDeeplinkResponse(_ deeplink: URL?) -> Bool { if let incomingLink = deeplink { - // Store incoming deferred deep link to invoke after - // onboarding screens and login. - UserDefaults.standard.set( - incomingLink.absoluteString, - forKey: "lastDeferredLink") - - // Post notification for app to handle deferred deep link - NotificationCenter.default.post(name: .deferredLinkReceived, object: nil) + if UserDefaults.standard.bool(forKey: "HasCompletedOnboarding") { + // If onboarding is complete, handle deferred deep link immediately + DeeplinkHandler.handleDeeplink(incomingLink) + } else { + // Store deferred deep link to invoke after onboarding screens and login + UserDefaults.standard.set(incomingLink.absoluteString, forKey: "lastDeferredLink") + } } - // Return true to let Adjust SDK attempt to open deep link immediately // upon receipt (for example: app has no ATT, onboarding screens, or login). - // Otherwise, return false. + // Otherwise, return false to prevent SDK from opening the deep link immediately. return false } } @@ -217,20 +215,20 @@ class AppDelegate: UIResponder, UIApplicationDelegate, // Receive deferred deep link via AdjustDelegate method - (BOOL)adjustDeeplinkResponse:(NSURL *)deeplink { if (deeplink) { - // Store incoming deferred deep link to invoke after - // onboarding screens and login. - [[NSUserDefaults standardUserDefaults] setObject:[deeplink absoluteString] - forKey:@"lastDeferredLink"]; - - // Post notification for app to handle deferred deep link - [[NSNotificationCenter defaultCenter] - postNotificationName:@"DeferredLinkReceived" - object:nil]; + if ([[NSUserDefaults standardUserDefaults] + boolForKey:@"HasCompletedOnboarding"]) { + // If onboarding is complete, handle deferred deep link immediately + [DeeplinkHandler handleDeeplink:deeplink]; + } else { + // Store deferred deep link to invoke after onboarding screens and login + [[NSUserDefaults standardUserDefaults] + setObject:deeplink.absoluteString + forKey:@"lastDeferredLink"]; + } } - // Return YES to let Adjust SDK attempt to open deep link immediately // upon receipt (for example: app has no ATT, onboarding screens, or login). - // Otherwise, return NO. + // Otherwise, return NO to prevent SDK from opening the deep link immediately. return NO; } @@ -249,10 +247,10 @@ Most apps have some kind of onboarding process (for example: ATT prompt, onboard 3. The app begins its onboarding process. 4. After the ATT prompt response or timeout, the Adjust SDK sends session and attribution requests to Adjust's servers. 5. Adjust's servers respond with attribution data, including the deep link the user clicked on ("deferred deep link"). -6. The Adjust SDK triggers a deferred deep link callback in the app, which stores the deferred deep link (shown in the AppDelegate implementation above). -7. The app handles the deferred deep link in one of two ways (shown in the ViewController example class below): - - Once onboarding completes, the app checks for and handles any stored deferred deep link. - - If a deferred deep link arrives shortly after onboarding, a notification triggers the same check and handling. +6. The Adjust SDK triggers a deferred deep link callback in the app (shown in the AppDelegate implementation above). The callback checks whether onboarding is complete: + - If onboarding is complete, it handles the deep link immediately. + - If onboarding isn't complete, it stores the deep link. +7. Once onboarding completes, the app checks for and handles any stored deferred deep link (shown in the ViewController example class below). 8. The app navigates the user to the deep link screen. @@ -270,25 +268,13 @@ class ViewController: UIViewController { UserDefaults.standard.bool(forKey: "HasCompletedOnboarding") } set { - UserDefaults.standard.set( - newValue, forKey: "HasCompletedOnboarding") + UserDefaults.standard.set(newValue, forKey: "HasCompletedOnboarding") } } - override func viewDidLoad() { - super.viewDidLoad() - - // Observe deferred deep link notifications - NotificationCenter.default.addObserver( - self, - selector: #selector(onDeferredLinkReceived), - name: .deferredLinkReceived, - object: nil - ) - } - override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) + // Check if onboarding has been completed if !hasCompletedOnboarding { // Show onboarding screens and login prompt @@ -299,46 +285,23 @@ class ViewController: UIViewController { } // After onboarding, set hasCompletedOnboarding to true - // and check for stored deferred deep link hasCompletedOnboarding = true - checkForDeferredLink() - } else { - // Show main content - } - } - - @objc private func onDeferredLinkReceived() { - // Check for deferred deep link that may arrive after onboarding - if hasCompletedOnboarding { - checkForDeferredLink() - } - } - private func checkForDeferredLink() { - // Check for stored deferred deep link before handling - guard - let deferredLinkString = UserDefaults.standard.string( + // Check for deferred deep link that arrived during onboarding + if let deferredLinkString = UserDefaults.standard.string( forKey: "lastDeferredLink" ), - let deferredLink = URL(string: deferredLinkString) - else { return } - - // Remove stored deferred deep link to avoid handling again later - UserDefaults.standard.removeObject(forKey: "lastDeferredLink") - - // Handle deferred deep link - DeeplinkHandler.handleDeeplink(deferredLink) - } - - deinit { - NotificationCenter.default.removeObserver(self) + let deferredLink = URL(string: deferredLinkString) { + // Remove stored deferred deep link to avoid handling again later + UserDefaults.standard.removeObject(forKey: "lastDeferredLink") + // Handle deferred deep link + DeeplinkHandler.handleDeeplink(deferredLink) + } + } else { + // Show main content + } } } - -// Set deferred deep link notification name -extension Notification.Name { - static let deferredLinkReceived = Notification.Name("deferredLinkReceived") -} ``` @@ -357,23 +320,12 @@ extension Notification.Name { - (BOOL)hasCompletedOnboarding { return [[NSUserDefaults standardUserDefaults] - boolForKey:@"HasCompletedOnboarding"]; + boolForKey:@"HasCompletedOnboarding"]; } - (void)setHasCompletedOnboarding:(BOOL)hasCompletedOnboarding { [[NSUserDefaults standardUserDefaults] setBool:hasCompletedOnboarding - forKey:@"HasCompletedOnboarding"]; -} - -- (void)viewDidLoad { - [super viewDidLoad]; - - // Observe deferred deep link notifications - [[NSNotificationCenter defaultCenter] - addObserver:self - selector:@selector(onDeferredLinkReceived) - name:@"deferredLinkReceived" - object:nil]; + forKey:@"HasCompletedOnboarding"]; } - (void)viewDidAppear:(BOOL)animated { @@ -382,50 +334,30 @@ extension Notification.Name { // Check if onboarding has been completed if (!self.hasCompletedOnboarding) { // Show onboarding screens and login prompt - // Show ATT prompt after delay to ensure app is active dispatch_after( - dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), - dispatch_get_main_queue(), ^{ - [Adjust requestTrackingAuthorizationWithCompletionHandler:nil]; - }); + dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), + dispatch_get_main_queue(), ^{ + [Adjust requestTrackingAuthorization]; + }); // After onboarding, set hasCompletedOnboarding to true - // and check for stored deferred deep link self.hasCompletedOnboarding = YES; - [self checkForDeferredLink]; - } else { - // Show main content - } -} -- (void)onDeferredLinkReceived { - // Check for deferred deep link that may arrive after onboarding - if (self.hasCompletedOnboarding) { - [self checkForDeferredLink]; - } -} - -- (void)checkForDeferredLink { - // Check for stored deferred deep link before handling - NSString *deferredLinkString = + // Check for deferred deep link that arrived during onboarding + NSString *deferredLinkString = [[NSUserDefaults standardUserDefaults] stringForKey:@"lastDeferredLink"]; - NSURL *deferredLink = [NSURL URLWithString:deferredLinkString]; - - if (!deferredLink) { - return; + NSURL *deferredLink = [NSURL URLWithString:deferredLinkString]; + if (deferredLink) { + // Remove stored deferred deep link to avoid handling again later + [[NSUserDefaults standardUserDefaults] + removeObjectForKey:@"lastDeferredLink"]; + // Handle deferred deep link + [DeeplinkHandler handleDeeplink:deferredLink]; + } + } else { + // Show main content } - - // Remove stored deferred deep link to avoid handling again later - [[NSUserDefaults standardUserDefaults] - removeObjectForKey:@"lastDeferredLink"]; - - // Handle deferred deep link - [DeeplinkHandler handleDeeplink:deferredLink]; -} - -- (void)dealloc { - [[NSNotificationCenter defaultCenter] removeObserver:self]; } @end @@ -684,19 +616,17 @@ class AppDelegate: UIResponder, UIApplicationDelegate, AdjustDelegate { // Receive deferred deep link via AdjustDelegate method func adjustDeeplinkResponse(_ deeplink: URL?) -> Bool { if let incomingLink = deeplink { - // Store incoming deferred deep link to invoke after - // onboarding screens and login. - UserDefaults.standard.set( - incomingLink.absoluteString, - forKey: "lastDeferredLink") - - // Post notification for app to handle deferred deep link - NotificationCenter.default.post(name: .deferredLinkReceived, object: nil) + if UserDefaults.standard.bool(forKey: "HasCompletedOnboarding") { + // If onboarding is complete, handle deferred deep link immediately + DeeplinkHandler.handleDeeplink(incomingLink) + } else { + // Store deferred deep link to invoke after onboarding screens and login + UserDefaults.standard.set(incomingLink.absoluteString, forKey: "lastDeferredLink") + } } - // Return true to let Adjust SDK attempt to open deep link immediately // upon receipt (for example: app has no ATT, onboarding screens, or login). - // Otherwise, return false. + // Otherwise, return false to prevent SDK from opening the deep link immediately. return false } } @@ -714,10 +644,10 @@ Most apps have some kind of onboarding process (for example: ATT prompt, onboard 3. The app begins its onboarding process. 4. After the ATT prompt response or timeout, the Adjust SDK sends session and attribution requests to Adjust's servers. 5. Adjust's servers respond with attribution data, including the deep link the user clicked on ("deferred deep link"). -6. The Adjust SDK triggers a deferred deep link callback in the app, which stores the deferred deep link (shown in the AppDelegate implementation above). -7. The app handles the deferred deep link in one of two ways (shown in the ContentView example class below): - - Once onboarding completes, the app checks for and handles any stored deferred deep link. - - If a deferred deep link arrives shortly after onboarding, a notification triggers the same check and handling. +6. The Adjust SDK triggers a deferred deep link callback in the app (shown in the AppDelegate implementation above). The callback checks whether onboarding is complete: + - If onboarding is complete, it handles the deep link immediately. + - If onboarding isn't complete, it stores the deep link. +7. Once onboarding completes, the app checks for and handles any stored deferred deep link (shown in the ContentView example class below). 8. The app navigates the user to the deep link screen. @@ -730,13 +660,11 @@ import Adjust import SwiftUI struct ContentView: View { - @State private var hasCompletedOnboarding = UserDefaults.standard.bool(forKey: "HasCompletedOnboarding") - var body: some View { NavigationStack { VStack { // Check if onboarding has been completed - if !hasCompletedOnboarding { + if !UserDefaults.standard.bool(forKey: "HasCompletedOnboarding") { // Show onboarding screens and login prompt // Show ATT prompt after delay to ensure app is active @@ -744,48 +672,28 @@ struct ContentView: View { DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) { Adjust.requestTrackingAuthorization { _ in } } - } - // After onboarding, set hasCompletedOnboarding to true - // and check for stored deferred deep link - UserDefaults.standard.set(true, forKey: "HasCompletedOnboarding") - hasCompletedOnboarding = true - checkForDeferredLink() + // After onboarding, set hasCompletedOnboarding to true + UserDefaults.standard.set(true, forKey: "HasCompletedOnboarding") + + // Check for deferred deep link that arrived during onboarding + if let deferredLinkString = UserDefaults.standard.string( + forKey: "lastDeferredLink" + ), + let deferredLink = URL(string: deferredLinkString) { + // Remove stored deferred deep link to avoid handling again later + UserDefaults.standard.removeObject(forKey: "lastDeferredLink") + // Handle deferred deep link + DeeplinkHandler.handleDeeplink(deferredLink) + } + } } else { // Show main content } } } - - // Check for deferred deep link that may arrive after onboarding - .onReceive( - NotificationCenter.default.publisher(for: .deferredLinkReceived) - ) { _ in - if hasCompletedOnboarding { - checkForDeferredLink() - } - } - } - - private func checkForDeferredDeeplink() { - // Check for stored deferred deep link before handling - guard let deferredLinkString = UserDefaults.standard.string( - forKey: "lastDeferredLink" - ), - let deferredLink = URL(string: deferredLinkString) else { return } - - // Remove stored deferred deep link to avoid handling again later - UserDefaults.standard.removeObject(forKey: "lastDeferredLink") - - // Handle deferred deep link - DeeplinkHandler.handleDeeplink(deferredLink) } } - -// Set deferred deep link notification name -extension Notification.Name { - static let deferredLinkReceived = Notification.Name("deferredLinkReceived") -} ``` @@ -874,8 +782,7 @@ The class performs the following tasks: ```swift import Adjust -import UIKit -// import SwiftUI - if applicable +// import SwiftUI and/or UIKit class DeeplinkHandler { static func handleDeeplink(_ incomingLink: URL) { diff --git a/src/content/docs/sdk/ios/v5/features/deep-links/set-up-deep-linking.mdx b/src/content/docs/sdk/ios/v5/features/deep-links/set-up-deep-linking.mdx index d70ea28da..c614a460d 100644 --- a/src/content/docs/sdk/ios/v5/features/deep-links/set-up-deep-linking.mdx +++ b/src/content/docs/sdk/ios/v5/features/deep-links/set-up-deep-linking.mdx @@ -115,22 +115,19 @@ class AppDelegate: UIResponder, UIApplicationDelegate, // Receive deferred deep link via AdjustDelegate method func adjustDeferredDeeplinkReceived(_ deeplink: URL?) -> Bool { if let incomingLink = deeplink { - // Store incoming deferred deep link to invoke after - // onboarding screens and login. - UserDefaults.standard.set( - incomingLink.absoluteString, - forKey: "lastDeferredLink") - - // Post notification for app to handle deferred deep link - NotificationCenter.default.post(name: .deferredLinkReceived, object: nil) + if UserDefaults.standard.bool(forKey: "HasCompletedOnboarding") { + // If onboarding is complete, handle deferred deep link immediately + DeeplinkHandler.handleDeeplink(incomingLink) + } else { + // Store deferred deep link to invoke after onboarding screens and login + UserDefaults.standard.set(incomingLink.absoluteString, forKey: "lastDeferredLink") + } } - // Return true to let Adjust SDK attempt to open deep link immediately // upon receipt (for example: app has no ATT, onboarding screens, or login). - // Otherwise, return false. + // Otherwise, return false to prevent SDK from opening the deep link immediately. return false } -} ``` @@ -215,26 +212,24 @@ class AppDelegate: UIResponder, UIApplicationDelegate, } // Receive deferred deep link via AdjustDelegate method -- (BOOL)adjustDeferredDeeplinkReceived:(NSURL *)deeplink { +- (BOOL)adjustDeferredDeeplinkReceived:(nullable NSURL *)deeplink { if (deeplink) { - // Store incoming deferred deep link to invoke after - // onboarding screens and login. - [[NSUserDefaults standardUserDefaults] setObject:[deeplink absoluteString] - forKey:@"lastDeferredLink"]; - - // Post notification for app to handle deferred deep link - [[NSNotificationCenter defaultCenter] - postNotificationName:@"DeferredLinkReceived" - object:nil]; + if ([[NSUserDefaults standardUserDefaults] + boolForKey:@"HasCompletedOnboarding"]) { + // If onboarding is complete, handle deferred deep link immediately + [DeeplinkHandler handleDeeplink:deeplink]; + } else { + // Store deferred deep link to invoke after onboarding screens and login + [[NSUserDefaults standardUserDefaults] + setObject:deeplink.absoluteString + forKey:@"lastDeferredLink"]; + } } - // Return YES to let Adjust SDK attempt to open deep link immediately // upon receipt (for example: app has no ATT, onboarding screens, or login). - // Otherwise, return NO. + // Otherwise, return NO to prevent SDK from opening the deep link immediately. return NO; } - -@end ``` @@ -249,10 +244,10 @@ Most apps have some kind of onboarding process (for example: ATT prompt, onboard 3. The app begins its onboarding process. 4. After the ATT prompt response or timeout, the Adjust SDK sends session and attribution requests to Adjust's servers. 5. Adjust's servers respond with attribution data, including the deep link the user clicked on ("deferred deep link"). -6. The Adjust SDK triggers a deferred deep link callback in the app, which stores the deferred deep link (shown in the AppDelegate implementation above). -7. The app handles the deferred deep link in one of two ways (shown in the ViewController example class below): - - Once onboarding completes, the app checks for and handles any stored deferred deep link. - - If a deferred deep link arrives shortly after onboarding, a notification triggers the same check and handling. +6. The Adjust SDK triggers a deferred deep link callback in the app (shown in the AppDelegate implementation above). The callback checks whether onboarding is complete: + - If onboarding is complete, it handles the deep link immediately. + - If onboarding isn't complete, it stores the deep link. +7. Once onboarding completes, the app checks for and handles any stored deferred deep link (shown in the ViewController example class below). 8. The app navigates the user to the deep link screen. @@ -270,25 +265,13 @@ class ViewController: UIViewController { UserDefaults.standard.bool(forKey: "HasCompletedOnboarding") } set { - UserDefaults.standard.set( - newValue, forKey: "HasCompletedOnboarding") + UserDefaults.standard.set(newValue, forKey: "HasCompletedOnboarding") } } - override func viewDidLoad() { - super.viewDidLoad() - - // Observe deferred deep link notifications - NotificationCenter.default.addObserver( - self, - selector: #selector(onDeferredLinkReceived), - name: .deferredLinkReceived, - object: nil - ) - } - override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) + // Check if onboarding has been completed if !hasCompletedOnboarding { // Show onboarding screens and login prompt @@ -299,46 +282,23 @@ class ViewController: UIViewController { } // After onboarding, set hasCompletedOnboarding to true - // and check for stored deferred deep link hasCompletedOnboarding = true - checkForDeferredLink() - } else { - // Show main content - } - } - @objc private func onDeferredLinkReceived() { - // Check for deferred deep link that may arrive after onboarding - if hasCompletedOnboarding { - checkForDeferredLink() - } - } - - private func checkForDeferredLink() { - // Check for stored deferred deep link before handling - guard - let deferredLinkString = UserDefaults.standard.string( + // Check for deferred deep link that arrived during onboarding + if let deferredLinkString = UserDefaults.standard.string( forKey: "lastDeferredLink" ), - let deferredLink = URL(string: deferredLinkString) - else { return } - - // Remove stored deferred deep link to avoid handling again later - UserDefaults.standard.removeObject(forKey: "lastDeferredLink") - - // Handle deferred deep link - DeeplinkHandler.handleDeeplink(deferredLink) - } - - deinit { - NotificationCenter.default.removeObserver(self) + let deferredLink = URL(string: deferredLinkString) { + // Remove stored deferred deep link to avoid handling again later + UserDefaults.standard.removeObject(forKey: "lastDeferredLink") + // Handle deferred deep link + DeeplinkHandler.handleDeeplink(deferredLink) + } + } else { + // Show main content + } } } - -// Set deferred deep link notification name -extension Notification.Name { - static let deferredLinkReceived = Notification.Name("deferredLinkReceived") -} ``` @@ -357,23 +317,12 @@ extension Notification.Name { - (BOOL)hasCompletedOnboarding { return [[NSUserDefaults standardUserDefaults] - boolForKey:@"HasCompletedOnboarding"]; + boolForKey:@"HasCompletedOnboarding"]; } - (void)setHasCompletedOnboarding:(BOOL)hasCompletedOnboarding { [[NSUserDefaults standardUserDefaults] setBool:hasCompletedOnboarding - forKey:@"HasCompletedOnboarding"]; -} - -- (void)viewDidLoad { - [super viewDidLoad]; - - // Observe deferred deep link notifications - [[NSNotificationCenter defaultCenter] - addObserver:self - selector:@selector(onDeferredLinkReceived) - name:@"deferredLinkReceived" - object:nil]; + forKey:@"HasCompletedOnboarding"]; } - (void)viewDidAppear:(BOOL)animated { @@ -382,50 +331,30 @@ extension Notification.Name { // Check if onboarding has been completed if (!self.hasCompletedOnboarding) { // Show onboarding screens and login prompt - // Show ATT prompt after delay to ensure app is active dispatch_after( - dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), - dispatch_get_main_queue(), ^{ - [Adjust requestTrackingAuthorizationWithCompletionHandler:nil]; - }); + dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), + dispatch_get_main_queue(), ^{ + [Adjust requestTrackingAuthorizationWithCompletionHandler:nil]; + }); // After onboarding, set hasCompletedOnboarding to true - // and check for stored deferred deep link self.hasCompletedOnboarding = YES; - [self checkForDeferredLink]; - } else { - // Show main content - } -} - -- (void)onDeferredLinkReceived { - // Check for deferred deep link that may arrive after onboarding - if (self.hasCompletedOnboarding) { - [self checkForDeferredLink]; - } -} -- (void)checkForDeferredLink { - // Check for stored deferred deep link before handling - NSString *deferredLinkString = + // Check for deferred deep link that arrived during onboarding + NSString *deferredLinkString = [[NSUserDefaults standardUserDefaults] stringForKey:@"lastDeferredLink"]; - NSURL *deferredLink = [NSURL URLWithString:deferredLinkString]; - - if (!deferredLink) { - return; + NSURL *deferredLink = [NSURL URLWithString:deferredLinkString]; + if (deferredLink) { + // Remove stored deferred deep link to avoid handling again later + [[NSUserDefaults standardUserDefaults] + removeObjectForKey:@"lastDeferredLink"]; + // Handle deferred deep link + [DeeplinkHandler handleDeeplink:deferredLink]; + } + } else { + // Show main content } - - // Remove stored deferred deep link to avoid handling again later - [[NSUserDefaults standardUserDefaults] - removeObjectForKey:@"lastDeferredLink"]; - - // Handle deferred deep link - [DeeplinkHandler handleDeeplink:deferredLink]; -} - -- (void)dealloc { - [[NSNotificationCenter defaultCenter] removeObserver:self]; } @end @@ -684,19 +613,17 @@ class AppDelegate: UIResponder, UIApplicationDelegate, AdjustDelegate { // Receive deferred deep link via AdjustDelegate method func adjustDeferredDeeplinkReceived(_ deeplink: URL?) -> Bool { if let incomingLink = deeplink { - // Store incoming deferred deep link to invoke after - // onboarding screens and login. - UserDefaults.standard.set( - incomingLink.absoluteString, - forKey: "lastDeferredLink") - - // Post notification for app to handle deferred deep link - NotificationCenter.default.post(name: .deferredLinkReceived, object: nil) + if UserDefaults.standard.bool(forKey: "HasCompletedOnboarding") { + // If onboarding is complete, handle deferred deep link immediately + DeeplinkHandler.handleDeeplink(incomingLink) + } else { + // Store deferred deep link to invoke after onboarding screens and login + UserDefaults.standard.set(incomingLink.absoluteString, forKey: "lastDeferredLink") + } } - // Return true to let Adjust SDK attempt to open deep link immediately // upon receipt (for example: app has no ATT, onboarding screens, or login). - // Otherwise, return false. + // Otherwise, return false to prevent SDK from opening the deep link immediately. return false } } @@ -714,10 +641,10 @@ Most apps have some kind of onboarding process (for example: ATT prompt, onboard 3. The app begins its onboarding process. 4. After the ATT prompt response or timeout, the Adjust SDK sends session and attribution requests to Adjust's servers. 5. Adjust's servers respond with attribution data, including the deep link the user clicked on ("deferred deep link"). -6. The Adjust SDK triggers a deferred deep link callback in the app, which stores the deferred deep link (shown in the AppDelegate implementation above). -7. The app handles the deferred deep link in one of two ways (shown in the ContentView example class below): - - Once onboarding completes, the app checks for and handles any stored deferred deep link. - - If a deferred deep link arrives shortly after onboarding, a notification triggers the same check and handling. +6. The Adjust SDK triggers a deferred deep link callback in the app (shown in the AppDelegate implementation above). The callback checks whether onboarding is complete: + - If onboarding is complete, it handles the deep link immediately. + - If onboarding isn't complete, it stores the deep link. +7. Once onboarding completes, the app checks for and handles any stored deferred deep link (shown in the ContentView example class below). 8. The app navigates the user to the deep link screen. @@ -727,64 +654,42 @@ Most apps have some kind of onboarding process (for example: ATT prompt, onboard ```swift import Adjust +import Foundation import SwiftUI struct ContentView: View { - @State private var hasCompletedOnboarding = UserDefaults.standard.bool(forKey: "HasCompletedOnboarding") - var body: some View { NavigationStack { VStack { // Check if onboarding has been completed - if !hasCompletedOnboarding { + if !UserDefaults.standard.bool(forKey: "HasCompletedOnboarding") { // Show onboarding screens and login prompt - // Show ATT prompt after delay to ensure app is active .onAppear { DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) { Adjust.requestTrackingAuthorizationWithCompletionHandler { _ in } } - } - // After onboarding, set hasCompletedOnboarding to true - // and check for stored deferred deep link - UserDefaults.standard.set(true, forKey: "HasCompletedOnboarding") - hasCompletedOnboarding = true - checkForDeferredLink() + // After onboarding, set hasCompletedOnboarding to true + UserDefaults.standard.set(true, forKey: "HasCompletedOnboarding") + + // Check for deferred deep link that arrived during onboarding + if let deferredLinkString = UserDefaults.standard.string( + forKey: "lastDeferredLink" + ), + let deferredLink = URL(string: deferredLinkString) { + // Remove stored deferred deep link to avoid handling again later + UserDefaults.standard.removeObject(forKey: "lastDeferredLink") + // Handle deferred deep link + DeeplinkHandler.handleDeeplink(deferredLink) + } + } } else { // Show main content } } } - - // Check for deferred deep link that may arrive after onboarding - .onReceive( - NotificationCenter.default.publisher(for: .deferredLinkReceived) - ) { _ in - if hasCompletedOnboarding { - checkForDeferredLink() - } - } } - - private func checkForDeferredLink() { - // Check for stored deferred deep link before handling - guard let deferredLinkString = UserDefaults.standard.string( - forKey: "lastDeferredLink" - ), - let deferredLink = URL(string: deferredLinkString) else { return } - - // Remove stored deferred deep link to avoid handling again later - UserDefaults.standard.removeObject(forKey: "lastDeferredLink") - - // Handle deferred deep link - DeeplinkHandler.handleDeeplink(deferredLink) - } -} - -// Set deferred deep link notification name -extension Notification.Name { - static let deferredLinkReceived = Notification.Name("deferredLinkReceived") } ``` @@ -874,8 +779,7 @@ The class performs the following tasks: ```swift import Adjust -import UIKit -// import SwiftUI - if applicable +// import SwiftUI and/or UIKit class DeeplinkHandler { static func handleDeeplink(_ incomingLink: URL) { @@ -940,6 +844,7 @@ class DeeplinkHandler { // Example UIKit implementation: // let productVC = ProductViewController(productId: productId) // navigationController.pushViewController(productVC, animated: true) + // Example SwiftUI implementation: // let productView = ProductView(productId: productId) // navigationPath.append(productView) @@ -950,6 +855,7 @@ class DeeplinkHandler { // Example UIKit implementation: // let categoryVC = CategoryViewController(category: category) // navigationController.pushViewController(categoryVC, animated: true) + // Example SwiftUI implementation: // let categoryView = CategoryView(category: category) // navigationPath.append(categoryView) @@ -960,6 +866,7 @@ class DeeplinkHandler { // Example UIKit implementation: // let searchVC = SearchViewController(searchQuery: query) // navigationController.pushViewController(searchVC, animated: true) + // Example SwiftUI implementation: // let searchView = SearchView(searchQuery: query) // navigationPath.append(searchView) From edbff2d87e02144b61ffd3b4d46c54f7d5be86a5 Mon Sep 17 00:00:00 2001 From: Dave Mead Date: Mon, 2 Dec 2024 01:23:11 -0800 Subject: [PATCH 27/27] Fix formatting --- .../ios/v4/features/deep-links/configure-deep-link-settings.mdx | 2 +- .../ios/v5/features/deep-links/configure-deep-link-settings.mdx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/content/docs/sdk/ios/v4/features/deep-links/configure-deep-link-settings.mdx b/src/content/docs/sdk/ios/v4/features/deep-links/configure-deep-link-settings.mdx index b12c632f8..eb85e5bec 100644 --- a/src/content/docs/sdk/ios/v4/features/deep-links/configure-deep-link-settings.mdx +++ b/src/content/docs/sdk/ios/v4/features/deep-links/configure-deep-link-settings.mdx @@ -25,7 +25,7 @@ Please note that the Adjust dashboard only supports one bundle ID (`com.example. Add iOS in the platform settings for your app. This step requires entering the bundle ID. If you're not sure of the bundle ID for your app build, enter a temporary value (`com.example.app`) to save the platform settings. Follow the rest of this guide to collect all required data points, then return to platform settings in the dashboard to finish the configuration.
-## Set up a branded domain{" "} +## Set up a branded domain In the Adjust dashboard, [set up a branded domain](https://help.adjust.com/en/article/set-up-branded-domain) using Adjust's go.link domain (for example: `brandname.go.link`). diff --git a/src/content/docs/sdk/ios/v5/features/deep-links/configure-deep-link-settings.mdx b/src/content/docs/sdk/ios/v5/features/deep-links/configure-deep-link-settings.mdx index b01bd56e4..15a16e6c9 100644 --- a/src/content/docs/sdk/ios/v5/features/deep-links/configure-deep-link-settings.mdx +++ b/src/content/docs/sdk/ios/v5/features/deep-links/configure-deep-link-settings.mdx @@ -25,7 +25,7 @@ Please note that the Adjust dashboard only supports one bundle ID (`com.example. Add iOS in the platform settings for your app. This step requires entering the bundle ID. If you're not sure of the bundle ID for your app build, enter a temporary value (`com.example.app`) to save the platform settings. Follow the rest of this guide to collect all required data points, then return to platform settings in the dashboard to finish the configuration.
-## Set up a branded domain{" "} +## Set up a branded domain In the Adjust dashboard, [set up a branded domain](https://help.adjust.com/en/article/set-up-branded-domain) using Adjust's go.link domain (for example: `brandname.go.link`).