ディープリンク対応

ディープリンクは、URL やプッシュ通知からアプリ内の特定の画面に直接遷移する機能です。NavigationStack の path を活用することで、SwiftUI でも簡単に実装できます。

ディープリンクの基本

URL スキームを設定し、onOpenURL でリンクを処理します。

struct ContentView: View {
    @State private var path = NavigationPath()
    
    var body: some View {
        NavigationStack(path: $path) {
            HomeView()
                .navigationDestination(for: String.self) { value in
                    DetailView(id: value)
                }
        }
        .onOpenURL { url in
            handleDeepLink(url)
        }
    }
    
    func handleDeepLink(_ url: URL) {
        // myapp://detail/123 のような URL を処理
        guard let host = url.host else { return }
        
        if host == "detail" {
            let id = url.lastPathComponent
            path.append(id)
        }
    }
}

URL パーサーの実装

複雑な URL 構造に対応するパーサーを作成します。

enum DeepLink: Hashable {
    case home
    case profile(userId: String)
    case article(articleId: String)
    case settings
    
    static func parse(_ url: URL) -> [DeepLink]? {
        guard url.scheme == "myapp" else { return nil }
        
        let pathComponents = url.pathComponents.filter { $0 != "/" }
        
        switch url.host {
        case "profile":
            guard let userId = pathComponents.first else { return nil }
            return [.profile(userId: userId)]
            
        case "article":
            guard let articleId = pathComponents.first else { return nil }
            return [.article(articleId: articleId)]
            
        case "settings":
            return [.settings]
            
        default:
            return [.home]
        }
    }
}

path を使った遷移

struct ContentView: View {
    @State private var path: [DeepLink] = []
    
    var body: some View {
        NavigationStack(path: $path) {
            HomeView()
                .navigationDestination(for: DeepLink.self) { link in
                    switch link {
                    case .home:
                        HomeView()
                    case .profile(let userId):
                        ProfileView(userId: userId)
                    case .article(let articleId):
                        ArticleView(articleId: articleId)
                    case .settings:
                        SettingsView()
                    }
                }
        }
        .onOpenURL { url in
            if let links = DeepLink.parse(url) {
                path = links
            }
        }
    }
}
onOpenURL

アプリが URL で起動されたときに呼ばれる。バックグラウンドからの復帰時も発火する。

path の直接設定

path に配列を代入することで、複数階層を一度に構築できる。戻るボタンも正しく動作する。

プッシュ通知からの遷移

プッシュ通知のペイロードに遷移先情報を含め、タップ時に画面遷移を実行します。

class AppDelegate: NSObject, UIApplicationDelegate, UNUserNotificationCenterDelegate {
    var deepLinkHandler: ((DeepLink) -> Void)?
    
    func userNotificationCenter(
        _ center: UNUserNotificationCenter,
        didReceive response: UNNotificationResponse
    ) async {
        let userInfo = response.notification.request.content.userInfo
        
        if let type = userInfo["type"] as? String,
           let id = userInfo["id"] as? String {
            switch type {
            case "article":
                deepLinkHandler?(.article(articleId: id))
            case "profile":
                deepLinkHandler?(.profile(userId: id))
            default:
                break
            }
        }
    }
}

ユニバーサルリンク対応

ユニバーサルリンク(https://example.com/article/123)も同様に onOpenURL で処理できます。

.onOpenURL { url in
    // URL スキームとユニバーサルリンク両方を処理
    if url.scheme == "myapp" {
        // カスタム URL スキーム
        if let links = DeepLink.parse(url) {
            path = links
        }
    } else if url.host == "example.com" {
        // ユニバーサルリンク
        if let links = DeepLink.parseWebURL(url) {
            path = links
        }
    }
}
URL スキーム(myapp://)

アプリ固有のスキーム。設定が簡単だが、他のアプリが同じスキームを使う可能性がある。

ユニバーサルリンク(https://)

Web ドメインに紐付いたリンク。設定が複雑だが、より安全で SEO にも有利。

状態の復元

アプリ終了後も遷移状態を復元するには、path を永続化します。

struct ContentView: View {
    @SceneStorage("navigationPath") private var pathData: Data?
    @State private var path = NavigationPath()
    
    var body: some View {
        NavigationStack(path: $path) {
            HomeView()
                .navigationDestination(for: DeepLink.self) { /* ... */ }
        }
        .onAppear {
            if let data = pathData {
                path = try? JSONDecoder().decode(
                    NavigationPath.CodableRepresentation.self,
                    from: data
                ).map { NavigationPath($0) } ?? NavigationPath()
            }
        }
        .onChange(of: path) { _, newPath in
            pathData = try? JSONEncoder().encode(newPath.codable)
        }
    }
}

ディープリンクは UX を向上させる重要な機能です。NavigationStack の path を活用することで、従来よりもシンプルに実装できるようになりました。