SwiftUIライフサイクルアプリのAppDelegateでユニバーサルリンクとして受信されるリンクを受け取れない問題の対応策

2022.10.19

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

SwiftUIライフサイクルのアプリでFirebase Dynamic Linksを使用してユニバーサルリンクとして受信されるリンクを受け取ろうとしたのですが、受け取れなかったので調べてみました。

環境

  • Xcode 14
  • Firebase Apple SDK 9.6.0
  • iOS 16.0

はじめに

Firebaseライブラリのインストール、Dynamic Linksの設定は完了している前提で進めていきます。

初期設定でSwiftUIライフサイクルで作成したアプリには、AppDelegate がデフォルトで無い為、Firebaseのドキュメントに沿って追加しました。

import SwiftUI
import FirebaseCore


class AppDelegate: NSObject, UIApplicationDelegate {
  func application(_ application: UIApplication,
                   didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
    FirebaseApp.configure()

    return true
  }
}

@main
struct YourApp: App {
  // register app delegate for Firebase setup
  @UIApplicationDelegateAdaptor(AppDelegate.self) var delegate


  var body: some Scene {
    WindowGroup {
      NavigationView {
        ContentView()
      }
    }
  }
}

ユニバーサルリンクとして受信されるリンクを処理できない

Firebaseの公式ドキュメント iOS でダイナミック リンクを受信するにユニバーサルリンクとしてリンクを処理する方法が記載してあります。

application:continueUserActivity:restorationHandler: メソッドで、アプリがすでにインストールされている場合にユニバーサル リンクとして受信されるリンクを処理します。

しかし、SwiftUIライフサイクルアプリで下記のように実装しても、Dynamic Linkを踏んだ時にapplication:continueUserActivity:restorationHandler:が呼ばれることはありませんでした

func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool {

    print("called application continue")
    if let url = userActivity.webpageURL {
        print("ユニバーサルリンクとして受信する:", url)
    }
    return userActivity.webpageURL != nil
}

アプリ側の動きとしては、Dynamic Linkを踏んだ際にアプリに遷移しているので意図した動きはしていたので、遷移する際に踏んだDynamic Linkの情報は、application:continueUserActivity:restorationHandler:では確認出来ませんでした。

SceneDelegateを使用して試してみる

調べてみると、こちらのstackoverflow内で、userActivitySceneDelegate側で呼ばれるという記載があり、試してみました。

まずはSceneDelegateを呼ばれるようにする為に、AppDelegateに下記を追加しました。

AppDelegate

class AppDelegate: NSObject, UIApplicationDelegate {

    // ~省略

    /// SceneDelegateを呼ばれるようにする
    func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {

        let config = UISceneConfiguration(name: nil, sessionRole: connectingSceneSession.role)
        config.delegateClass = SceneDelegate.self
        return config
    }
}

SceneDelegate

ユニバーサルリンクを受信して起動した時にSceneDelegatescene(scene:, userActivity:)でリンクの受信を確認することにしました。確認の為、その他でリンクを受信できそうな箇所でもデバッグを試みてみました。

class SceneDelegate: UIResponder, UIWindowSceneDelegate {

    func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {

        print("called scene willConnectTo")
        if let url = connectionOptions.userActivities.first?.webpageURL {
            print(url)
        }
    }

    func scene(_ scene: UIScene, continue userActivity: NSUserActivity) {

        print("called scene continue")
        if let url = userActivity.webpageURL {
            print(url)
        }
    }

    func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {

        print("called scene openURLContexts")
        if let url = URLContexts.first?.url {
            print(url)
        }
    }
}

しかし、どのメソッドもDynamic Linkを踏んでアプリが表示される際に呼び出されることはありませんでした。

対応策

onOpenURLを使用すると、受け取ることが出来ました。

今回は下記のようにAppの箇所で使用しました。

@main
struct ExampleApp: App {

    @UIApplicationDelegateAdaptor(AppDelegate.self) var delegate

    var body: some Scene {
        WindowGroup {
            ContentView()
                .onOpenURL { url in
                    print("called onOpenURL:", url)
                }
        }
    }
}

onOpenURL

onOpenURLは、Viewが含まれているSceneまたはWindowのURLを受け取った時に呼び出されるハンドラーで、noteの箇所にも下記の記載がありました。

This method handles the reception of Universal Links, rather than a NSUserActivity.

NSUserActivityではなく、このメソッドはユニバーサルリンクの受信を処理します。

引用: onOpenURL(perform:)

iOS 14からのメソッドになりますが、このメソッドを使用することでユニバーサルリンクを受信することが出来ました。

onOpenURLはDeferred Deep Linkでは呼ばれない

アプリがまだインストールされてない場合に、Dynamic Linkを踏み、App Storeのダウンロードページに遷移し、そこでアプリをインストール後初めて起動した時には、onOpenURLメソッドは呼ばれませんでした。

Deferred Deep Linkの際には、onOpenURLは呼ばれず、AppDelegateapplication(_:open:options:)が呼ばれていたのでこのメソッド内で処理を行う必要があります。

終わりに

最終的には無事にリンクを受信することが出来てよかったです。

もし、「SwiftUIライフサイクルでもAppDelegateやSceneDelegateでリンクを受け取れる方法あるよ」という方がいましたら優しく教えていただければと思います。

参考