URLSchemeRouter

使用 URLSchemeRouter,你可以为你的应用程序的 URL Scheme 创建一个路由器并添加路由,类似于 JavaScript 中的 Express 或 Hono 路由器。路由输入从 URL 查询项中解码。

首先,创建一个 URLSchemeRouter,传入你在应用程序的 Info.plist 中声明的 Scheme。

let router = URLSchemeRouter(scheme: "notesapp")

接下来,向你的路由器添加一些路由。为你想要在应用程序中支持的每个动作添加一个路由。这是一个处理以下 URL 的示例:notesapp:///search

注意

URL Scheme 的 host 组件会被忽略。这意味着 notesapp:///search 等价于 notesapp://x-callback-url/search

router.route("/search") {
    print("Navigate to search view")
}

最后,当你的应用程序收到 URL 时调用 router.handle(_:) 来触发匹配的路由。

router.handle(url)

输入项

要从 URL 解析查询项,创建一个与你想要解析的项匹配的 Decodable 类型。然后,在路由处理程序中指定你想要解码的类型。这是一个处理 notesapp:///create?title=My%20note 的示例

struct Note: Decodable {
    let title: String
    let body: String?
}

router.route("/create") { (note: Note) in
    print("Create note with title: \(note.title), body: \(note.body ?? "empty")")
}

x-callback-url

URLSchemeRouter 开箱即用地支持 x-callback-url,因此如果在路由处理程序中抛出错误或输入解码失败,则会调用 x-error,并在成功时调用 x-success。你也可以在路由处理程序中返回一个 Encodable 类型,以将输出传递给 x-success

struct Note: Encodable {
    let title: String
    let body: String?
}

router.route("/fetchNote") {
    let note: Note = try database.fetchNote()
    return note
}

当打开 notesapp:///fetchnotes?x-success=otherapp:// 时,路由器会自动打开 otherapp:///?title=...&body=...,并从你的 Encodable 填充成功参数。

错误

从你的路由处理程序抛出的错误会自动作为参数传递给指定的 x-error 回调。如果未指定 x-error,默认情况下,URLSchemeRouter 在抛出错误时将不执行任何操作。要处理错误,你可以在创建路由器时指定一个可选的 onError 闭包。在此示例中,当 URLSchemeRouter 遇到解码问题或错误时,会显示一个警报

let router = URLSchemeRouter(scheme: "notesapp") { [weak self] error in
    guard let self else { return }
    let alertController = UIAlertController(
        title: error.localizedDescription,
        message: nil,
        preferredStyle: .alert
    )
    alertController.addAction(.init(title: "OK", style: .default))
    window?.rootViewController?.present(
        alertController,
        animated: true
    )
}

示例

在实践中,你的 URLSchemeRouter 可能会位于 scene(_:openURLContexts:)onOpenURL (SwiftUI) 中。 这是一个如何在 iOS 应用程序中处理 URL 的完整示例

class SceneDelegate: UIResponder, UIWindowSceneDelegate {
    var window: UIWindow?

    func scene(
        _ scene: UIScene,
        willConnectTo _: UISceneSession,
        options connectionOptions: UIScene.ConnectionOptions
    ) {
        // App setup code...
    
        for urlContext in connectionOptions.urlContexts {
            handleURL(urlContext.url)
        }
    }
    
    func scene(_: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {
        for context in URLContexts {
            handleURL(context.url)
        }
    }

    func handleURL(_ url: URL) {
        let router = URLSchemeRouter(scheme: "readlaterapp") { [weak self] error in
            guard let self else { return }
            let alertController = UIAlertController(
                title: error.localizedDescription,
                message: nil,
                preferredStyle: .alert
            )
            alertController.addAction(.init(title: "OK", style: .default))
            window?.rootViewController?.present(
                alertController,
                animated: true
            )
        }

        struct SaveParameters: Decodable {
            let url: String
        }
        router.route("/save") { (parameters: SaveParameters) in
            guard let url = URL(string: parameters.url) else {
                struct InvalidURLError: Error, LocalizedError {
                    var errorDescription: String? { "Invalid URL" }
                }
                throw InvalidURLError()
            }
            try database.save(url)
        }

        struct FetchParameters: Encodable {
            let urls: [String]
        }
        router.route("/fetch") {
            return FetchParameters(
                urls: try database.fetchURLs().map(\.absoluteString)
            )
        }

        router.handle(url)
    }
}