Introduction

何の因果か Swift のモバイルアプリの解析と DeepLink の検証をすることになった。
そんな中、DeepLink の実装の一つである Universal Link を実装し、その処理をハンドリングする処理を実装していたが、このハンドリングが動かない。
通常、AppDelegate クラス内で下記のようにすれば Universal Link から呼ばれた際に、処理に介入できる。

Swift 5 以前なら

1
2
3
4
5
6
7
8
9
@main
class AppDelegate: UIResponder, UIApplicationDelegate {

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

Swift 5 なら

1
2
3
4
5
6
7
8
9
@main
class AppDelegate: UIResponder, UIApplicationDelegate {

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

しかし、ブレークポイントに停止しない。
何故?

How to resolve?

iOS 13 空サポートされた UIScene API が原因。
詳細は省くが、Xcode 11 から、この UIScene API のライフサイクルを使用したものがデフォルトテンプレートになってしまっている。
そのため、前述の ApoDegate に追加しメソッドが無視されてしまう。

Universal Link のハンドリングを行うには、

  • UIScene API を無効にする
  • UIScene API に基づいた実装を行う

という選択肢がある。

UIScene API を無効にする

1. Info.plist の修正

UIApplicationSceneManifest を削除する

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
- <key>UIApplicationSceneManifest</key>
- <dict>
- <key>UIApplicationSupportsMultipleScenes</key>
- <false/>
- <key>UISceneConfigurations</key>
- <dict>
- <key>UIWindowSceneSessionRoleApplication</key>
- <array>
- <dict>
- <key>UISceneConfigurationName</key>
- <string>Default Configuration</string>
- <key>UISceneDelegateClassName</key>
- <string>$(PRODUCT_MODULE_NAME).SceneDelegate</string>
- <key>UISceneStoryboardFile</key>
- <string>Main</string>
- </dict>
- </array>
- </dict>
- </dict>
</dict>
</plist>

2. SceneDelegate.swift の削除

SceneDelegate.swift をプロジェクトから削除してファイルも削除

3. SceneDelegate.swift の削除

AppDelegate に記述されている UISceneSession 周りの処理を削除し、var window: UIWindow? を追加する。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import UIKit

@main
class AppDelegate: UIResponder, UIApplicationDelegate {

+ var window: UIWindow?


func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
return true
}

- // MARK: UISceneSession Lifecycle
-
- func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
- // Called when a new scene session is being created.
- // Use this method to select a configuration to create the new scene with.
- return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
- }
-
- func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set<UISceneSession>) {
- // Called when the user discards a scene session.
- // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions.
- // Use this method to release any resources that were specific to the discarded scenes, as they will not return.
- }

UIScene API に基づいた実装を行う

こちらのシンプルだろう。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class SceneDelegate: UIResponder, UIWindowSceneDelegate {

+ func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions)
+ {
+ if let url = connectionOptions.userActivities.first?.webpageURL {
+ print(url)
+ }
+ }

+ func scene(_ scene: UIScene, continue userActivity: NSUserActivity)
+ {
+ guard userActivity.activityType == NSUserActivityTypeBrowsingWeb else { return }
+ guard let url = userActivity.webpageURL else { return }
+ if url.scheme == "https" && url.host == "example.jp" && url.path == "hogehoge" {
+ window?.rootViewController?.present(HogeHogeViewController(), animated: true, completion: nil)
+ }
+ }

前者はアプリがバックグラウンドで起動中、後者は新規にアプリが起動するときを制御する。