Привет, Хабр!

На связи Алексей Поддубный, iOS-разработчик AGIMA. Я расскажу, как в iOS работают диплинки, и разберу тонкости настройки популярных сервисов: где создавать ссылки с динамическими параметрами, как настраивать конфигурацию приложений и что делать после настройки. Инструкции основаны на оригинальных туториалах, которые мы перевели и адаптировали.

Диплинки, или глубинные ссылки, часто используют в рекламных кампаниях соцсетей. Они нужны чтобы вовлекать новых пользователей или интегрировать приложения с различными источниками трафика. Такая ссылка отправляет пользователя сразу на определенный раздел или товар, который он увидел в рекламной компании без перехода в браузер.

Что такое глубинные ссылки?

Человек листал ленту Facebook и увидел рекламу: доставка бургеров за 15 минут. Он кликает по ссылке и попадает в браузер на главную страницу. Рекламных предложений с «быстрыми» бургерами там нет, а без авторизации написать менеджеру нельзя. В результате страница закрывается, человек остается голодным и недовольным, а бизнес недополучил прибыль.

Чтобы дать пользователю желаемое «здесь и сейчас» нужна глубинная ссылка — Deep Link. Или Deferred Deep Linking — отложенная глубинная ссылка — если приложение не установлено.  В отложенном варианте пользователь сначала попадает в App Store или Play Market для Android, а после установки — в нужный раздел приложения.

Как можно использовать диплинки

  1. Интегрировать в рекламные кампании и привлекать новых пользователей.

  2. Переносить пользователей из веба в мобайл: после установки приложения можно продолжить работу сразу с того места, на котором человек остановился.

  3. Перенаправлять с электронной почты или SMS-сообщений в приложение на нужный раздел или товар.

  4. Обмениваться данными между пользователями: люди могут делиться между собой приглашениями установить приложение или ссылками на конкретный товар. С помощью диплинков можно отследить поведение пользователей и оптимизировать будущие маркетинговые кампании. 

Сервисы для интеграции диплинков

Есть много сервисов по внедрению диплинков. Мы чаще всего используем Firebase, AppsFlyer и Facebook поэтому будем сравнивать их. Справедливости ради, можно обойтись и без сторонних сервисов, но в этой статье такой подход мы рассматривать не будем.


Firebase

AppsFlyer (OneLink)

Facebook

Описание

Сервис содержит модуль для интеграции диплинков, Crashlytics, Аналитику, Push-уведомления и другие популярные модули

Мощный и легкий в использовании инструмент для настройки диплинков, отслеживания установок и аналитики 

Механизм для настройки рекламных диплинков внутри продуктов Facebook

Для чего использовать

Для рекламных диплинков ведущих на веб- и мобильные устройства

Для рекламных диплинков, ведущих на веб- и мобильные устройства

Используется только в рекламных кампаниях Facebook и Instagram (сторис, лента)

Плюсы


+ Ссылки могут участвовать в поисковой индексации 

+ Можно создать ссылку с динамическими параметрами, например когда каждому пользователю нужно выдать по ссылке с уникальным параметром

+Можно добавлять UTM-параметры

+ Работает на всех платформах

+ Легко интегрируется

+ Гибкая настройка диплинков, можно указать множество дополнительных параметров

+ Есть OneLink API для создания персонализированных ссылок и автоматизации процесса

+ Возможно создавать ссылки с динамическими параметрами

+ Deferred Deeplink без проблем работает в последних версиях iOS

+ Позволяет интегрировать в рекламу Facebook

+ Удобно тестировать через тестовые устройства

Минусы

- Настраивается только для мобильных приложений на базе iOS / Android

- Нет API для генерации ссылок

- Нет возможности настраивать рекламные кампании в консоли Firebase

Не выявлено

- Тестирование возможно только через установку приложения Facebook Messenger

- Из-за изменений в iOS 14 отложенный переход по глубинной ссылке больше не поддерживается

Удобство ЛК для отслеживания аналитики

Можно отслеживать  количество кликов по ссылке после установки приложения

Система отслеживания аналитики с множеством параметров: географическое распределение пользователей, источники трафика, установки по дням, переходы по ссылкам, показы, открытия приложения, показатели конверсии, неорганические и органические установки

Отображает и разделяет количество органических и неорганических установок. Показывает время последней установки на каждой из платформ iOS / Android

Дальше разберем, как интегрировать диплинки через описанные фреймворки.

#1: Настройка через Firebase

Создание ссылки в консоли Firebase

  1. В консоли Firebase откройте раздел «Динамические ссылки». Создайте базовый домен, который будет использоваться в диплинках.


 2. Нажмите на New Dynamic Link и перейдите к созданию диплинка.

Сформируйте вид короткой ссылки и нажмите Next.

3. Укажите ссылку, которая будет открываться у пользователей веба и в мобильном приложении. Правая часть ссылки — та, из которой будем извлекать параметры.

4. В следующем пункте укажите «Open the deep link in your iOS App» и выберите приложение из выпадающего списка.

5. Если ссылка будет использоваться для обеих платформ, то укажите и Android-приложение.

6. Укажите метатеги, UTM-метки или другие дополнительные параметры, нажмите «Сохранить».

Все, диплинк готов к дальнейшему использованию.

Конфигурация приложения 

Откройте проект в Xcode и перейдите во вкладку Signing & Capabilities, допишите префикс applinks и добавьте ваш домен в Associated Domains.

Чтобы проверить правильность настройки, установите приложение на телефон и перейдите по ссылке в виде  https://your_dynamic_links_domain/apple-app-site-association. В нашем случае — https://tr4d1.page.link/apple-app-site-association. После нажатия на ссылку вы должны попасть в приложение.

Для получения и обработки диплинков добавьте Firebase SDK.

 1. Добавьте и установите Firebase SDK через cocoapods выполнив pod install.

pod 'Firebase/Analytics'

pod 'Firebase/DynamicLinks'

2. Импортируйте модуль Firebase в AppDelegate

import Firebase

3.  В методе application:didFinishLaunchingWithOptions: вызовите FirebaseApp.configure() для инициализации SDK.

4.  Реализуйте метод для открытия диплинков если приложение уже установлено у пользователя.

func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool {
    AppsFlyerLib.shared().continue(userActivity, restorationHandler: nil)
    let handled = DynamicLinks.dynamicLinks().handleUniversalLink(userActivity.webpageURL!) { [weakself] (dynamicLink, error) in
        if let dynamicLink = dynamicLink, let deepUrl = dynamicLink.url {
            self?.processDeepLink(url: deepUrl)
        }
    }
    return handled
    }

Настройка Deferred Deep Link для случаев когда приложение не было раньше установлено:

func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any]) -> Bool {
    application(
        app, open: url,
        sourceApplication: options[UIApplication.OpenURLOptionsKey.sourceApplication] as? String,
        annotation: ""
    )
}
func application(_ application: UIApplication, open url: URL, sourceApplication: String?, annotation: Any) -> Bool {
    guard
        let dynamicLink = DynamicLinks.dynamicLinks().dynamicLink(fromCustomSchemeURL: url),
        let deepUrl = dynamicLink.url else {
        return false
    }
    processDeepLink(url: deepUrl)
    return true
}

Метод для обработки диплинка:

private func processDeepLink(url: URL) {
    let componets = url.absoluteString.components(separatedBy: "/")
    guard
        let type = componets.last,
        let linkType = DeepLinkType(rawValue: type) else {
        return
    }   
    //используем linkType для дальнейшей навигации
    //записываем linkType в переменную синглтона для дальнейшего открытия в базовом контроллере
    DeepLinkHelper.sharedInstance.deepLinkType = linkType
   //создаем уведомлении о диплинке, если базовый контроллер уже загружен - пользователь перешел по диплинку, когда приложение было запущено 
    NotificationCenter.default.post(name: Notification.Name("deepLinkNotification"), object: nil)
}

Опишем enum

enum DeepLinkType : String {
    case subscription
}

Опишем синглтон для хранения диплинка

class DeepLinkHelper : NSObject {
    static let sharedInstance =DeepLinkHelper()
    var deepLinkType: DeepLinkType?
}

Опишем базовый контроллер

class ViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()    
        // подписываемся на обработку уведомлений, в случае перехода по ним в запущенном приложении
        NotificationCenter.default.addObserver(self, selector: #selector(proccessDeepLink), name: "deepLinkNotification", object: nil)
       // вызываем метод обработки диплинка при загрузке контроллера - проверяем  было ли запущено приложение по переходу через диплинк 
       processDeepLink()
    }  
    @objc func proccessDeepLink() {
        guard let deepLinkType = DeepLinkHelper.sharedInstance.deepLinkType else {
            return
        }  
        switch deepLinkType {
        case .subscription:
	     // отображаем нужный контроллер
            showSubscription()
        default:
            break 
        }
        DeepLinkHelper.sharedInstance.deepLinkType = nil
    }
 }
func showSubscription() {
    let vc = SubscriptionViewController()
    let vm = SubscriptionViewModel()
    vc.viewModel = vm
    viewController.navigationController?.pushViewController(vc, animated: true)
}

Таким образом, когда пользователь переходит по короткой ссылке вида https://tr4d1.page.link/subscription, в обработчик попадает внутренняя ссылка https://deeplinkexample/subscription. После ее успешной обработки получается enumDeepLinkType.subscription, который можно использовать для открытия соответствующего раздела приложения.

Оригинал инструкции: https://firebase.google.com/docs/dynamic-links/ios/receive

#2: Настройка через AppsFlyer (Onelink)

Создание шаблона OneLink

1.  Перейдите по ссылке https://hq1.appsflyer.com/onelink/setup?onelinkId=new создайте базовый шаблон и укажите его название.

2. Укажите поддомен, который будет использоваться в диплинках.

3. Из выпадающего списка выберите название приложения. Если ссылка будет использоваться для обеих платформ, то укажите и Android-приложение.

4. Настройте поведение ссылки для случаев если приложение не установлено. Здесь по умолчанию открывается приложение в AppStore, и этот параметр изменять не нужно, — он уже сконфигурирован на открытие приложения в AppStore.

5.  Выберите действия, которые необходимо выполнить если приложение установлено. Здесь нужно изменить на запуск приложения с использованием Universal Links указав Team Id и Bundle Id приложения.

6. Если на вебе нужно открывать другую ссылку, а не перенаправлять пользователей в магазины приложений, укажите веб-ссылку. После этого шага нажмите «Сохранить» и перейдите к созданию самого диплинка.

Создание ссылки OneLink

1. Сформируйте вид короткий ссылки

2. Укажите название кампании 

3. Добавьте дополнительные параметры атрибуции. Они могут быть предустановленными, например af_ad (имя рекламы), af_channel (канал рекламы) или свои собственные. Все параметры будут доступны в приложении после переходу по ссылке и ее обработки.

4. После добавления параметров сохраните ссылку. Диплинк готов к использованию. Ссылка доступна в коротком и длинном варианте. 

Конфигурация приложения 

Откройте проект в Xcode и перейдите во вкладку Signing & Capabilities, добавьте ваш домен в Associated Domains, дописав префикс applinks: по аналогии с конфигурацией Firebase.

Для получения и обработки диплинков необходимо добавить AppsFlyer SDK.

 1.     Добавьте и установите AppSlyer SDK через cocoapods выполнив pod install

pod 'AppsFlyerFramework'

 2.     Импортируйте модуль AppsFlyer в AppDelegate

import AppsFlyerLib

 В методе application:didFinishLaunchingWithOptions: установите appsFlyerDevKey и appleAppID       

AppsFlyerLib.shared().appsFlyerDevKey = AppConstants.appsFlyerDevKey
AppsFlyerLib.shared().appleAppID = AppConstants.appleAppID
AppsFlyerLib.shared().delegate = self

3.  Реализуйте методы для извлечения ссылки и передачи ее в обработчик AppsFlyer

func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping([UIUserActivityRestoring]?) -> Void) -> Bool {
    AppsFlyerLib.shared().continue(userActivity, restorationHandler: nil)
}
func application(_ application: UIApplication, open url: URL, sourceApplication: String?, annotation: Any) -> Bool {
    AppsFlyerLib.shared().handleOpen(url, sourceApplication: sourceApplication, withAnnotation: annotation)
}
func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
    AppsFlyerLib.shared().handleOpen(url, options: options)
    application(
        app, open: url,
        sourceApplication: options[UIApplication.OpenURLOptionsKey.sourceApplication] as? String,
        annotation: ""
    )
}

4. Также реализуйте методы SDK AppsFlyer onConversionDataSuccess и onAppOpenAttribution для обработки диплинков

extension AppDelegate: AppsFlyerLibDelegate {
     // Метод вызывается при переходе по ссылке, если приложение не было ранее установлено и пользователь попал в него после установки из AppStore
    func onConversionDataSuccess(_ conversionInfo: [AnyHashable : Any]) {
    // Проверяем, что ссылка была впервые открыта, используется, чтобы не было ложных срабатываний при последующих запусках приложения
        guard let is_first_launch = conversionInfo["is_first_launch"] as? Bool, is_first_launch else { return }
    // Извлекаем из словаря conversionInfo указанные при создании ссылки параметры и передаем их в обработчик
    }
   // Метод вызывается при переходе по ссылке если приложение установлено 
    func onAppOpenAttribution(_ attributionData: [AnyHashable: Any]) {
   // Извлекаем из словаря attributionData указанные при создании ссылки параметры и передаем их в обработчик
    }
}

Переходя по короткой ссылке вида https://tr4d1.onelink.me/Jvu2/subscription, в обработчик попадает развернутая ссылка https://tr4d1.onelink.me/Jvu2?pid=subscription&c=subscription&custom_value=1&af_ad=subscription.  Все параметры этой ссылки находятся в словаре. Извлекая параметры, можно выполнить соответствующие действия в приложении. 

Оригинал инструкции для AppsFlyer https://support.appsflyer.com/hc/en-us/articles/207032066-AppsFlyer-SDK-Integration-iOS#core-apis-get-conversion-data

#3: Настройка через Facebook

Конфигурация приложения 

Откройте проект в Xcode и перейдите в Info.plist. Здесь необходимо добавить данные вашего приложения из Facebook.

<key>FacebookAppID</key>
<string>1847859691656630</string>
<key>FacebookDisplayName</key>
<string>DeeplinkExample</string>

Для получения и обработки диплинков необходимо добавить Facebook SDK.

1.     Добавьте и установите Facebook SDK через cocoapods выполнив pod install.

pod 'FacebookSDK'

2.     Импортируйте модуль Facebook в AppDelegate.

import FBSDKCoreKit

3.  В Facebook для любого вида диплинков используется всего один метод: 

func applicationDidBecomeActive(_ application: UIApplication) {
    AppLinkUtility.fetchDeferredAppLink { [weak self] (url, error) in
        guard let deepUrl = url else { return }
        // передаем в обработчик полученный диплинк
        self?.processDeepLink(url: deepUrl)
    }
}

Диплинки в Facebook работают только при переходе по ним из рекламы. Для их тестирования нужно настроить тестовую рекламную кампанию. Скачать Facebook Messenger и авторизоваться под той же учетной записью, в которой настраиваются диплинки. Затем найти в ленте запись вашей рекламной кампании и нажать на нее для перехода по диплинку.

Откладка диплинков

Для тестирования и дебаггинга глубинных отложенных диплинков нужно удалить приложение с телефона. Перейти по диплинку и попасть в AppStore на страницу приложения, но не скачивать его. После чего установить приложение через Xcode на девайс, установить брейкпоинты на методах извлечения диплинков соответствующих SDK и произвести отладку. 

Для отладки обычных отложенных диплинков, когда приложение уже установлено и выполняется просто переход по ссылке, нужно предварительно установить приложение через Xcode, но не запускать его. Это делается с помощью нажатия Option+Cmd+R. Откроется окно, в котором нужно поставить галочку Wait for the executable to be launched. 

В данном случае Xcode установит приложение, но будет ждать на открытие его пользователем. Далее так же установите брейкпоинты на нужных методах и перейдите по диплинку.

Оригинал инструкции для Facebook

Рекомендации

  • Детально тестируйте каждую ссылку на открытие нужного раздела или продукта в приложении перед отправкой новой версии приложения с AppStore. Это экономит время на более быстрый старт будущих рекламных кампаний.

  • При добавлении каждого SDK или новой ссылки проверьте поведение ссылки при установленном приложении и, если оно отсутствует. 

  • Интегрируйте сразу несколько SKD в одно приложение, например Facebook, Firebase и AppsFlyer. Тогда нужно смотреть чтобы добавление нового, не сломало работоспособность предыдущего. В таком случае хорошо иметь один обработчик, который будет вызываться каждым SDK.