轻松实现路由 URL 方案。
交叉路程是一个专注于处理自定义 URL 方案或通用链接的 URL 路由器。当然,您还可以用于 Firebase Dynamic Link 或其他类似服务。
使用此方法,您可以轻松路由多个 URL 方案并检索参数。
这个库是在 Cookpad 的工作时间内开发的。
基本用法
您可以使用 DefaultRouter
来定义路由定义。
想象在 iOS 上实现 Pokédex。您可以通过 URL 方案访问某个地方。
import Crossroad
let customURLScheme: LinkSource = .customURLScheme("pokedex")
let universalLink: LinkSource = .universalLink(URL(string: "https://my-awesome-pokedex.com")!)
do {
let router = try DefaultRouter(accepting: [customURLScheme, universalLink]) { registry in
registry.route("/pokemons/:pokedexID") { context in
let pokedexID: Int = try context.argument(named: "pokedexID") // Parse 'pokedexID' from URL
if !Pokedex.isExist(pokedexID) { // Find the Pokémon by ID
throw PokedexError.pokemonIsNotExist(pokedexID) // If Pokémon is not found. Try next route definition.
}
presentPokedexDetailViewController(of: pokedexID)
}
registry.route("/pokemons") { context in
let type: Type? = context.queryParameters.type // If URL contains &type=fire, you can get Fire type.
presentPokedexListViewController(for: type)
}
// ...
}
} catch {
// If route definitions have some problems, routers fail initialization and raise reasons.
fatalError(error.localizedDescription)
}
// Pikachu(No. 25) is exist! so you can open Pikachu's page.
let canRespond25 = router.responds(to: URL(string: "pokedex://pokemons/25")!) // true
// No. 9999 is missing. so you can't open this page.
let canRespond9999 = router.responds(to: URL(string: "pokedex://pokemons/9999")!) // false
// You can also open the pages via universal links.
let canRespondUniversalLink = router.responds(to: URL(string: "https://my-awesome-pokedex.com/pokemons/25")!) // true
// Open Pikachu page
router.openIfPossible(URL(string: "pokedex://pokemons/25")!)
// Open list of fire Pokémons page
router.openIfPossible(URL(string: "pokedex://pokemons?type=fire")!)
使用 AppDelegate
在常规用例中,您应该在 UIApplicationDelegate
方法上调用 router.openIfPossible
。
func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey: Any]) -> Bool {
if router.responds(to: url, options: options) {
return router.openIfPossible(url, options: options)
}
return false
}
使用 SceneDelegate
或者,如果您使用现代应用程序的 SceneDelegate
,那么
func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {
guard let context = URLContexts.first else {
return
}
router.openIfPossible(context.url, options: context.options)
}
使用 NSApplicationDelegate (用于 macOS)
如果您正在开发 macOS 应用程序
class AppDelegate: NSObject, NSApplicationDelegate {
func applicationDidFinishLaunching(_ aNotification: Notification) {
let appleEventManager = NSAppleEventManager.shared()
appleEventManager.setEventHandler(self,
andSelector: #selector(handleURLEvent(event:replyEvent:)),
forEventClass: AEEventClass(kInternetEventClass),
andEventID: AEEventID(kAEGetURL))
}
@objc func handleURLEvent(event: NSAppleEventDescriptor?, replyEvent: NSAppleEventDescriptor?) {
guard let urlString = event?.paramDescriptor(forKeyword: AEKeyword(keyDirectObject))?.stringValue else { return }
guard let url = URL(string: urlString) else { return }
router.openIfPossible(context.url, options: [:])
}
}
参数和参数名
参数
:
前缀的组件在传入的 URL 模式上意味着 参数名。
例如,如果传入的 URL 与 pokedex://search/:keyword
匹配,您可以从 Context
中获取 keyword
。
// actual URL: pokedex://search/Pikachu
let keyword: String = try context.arguments(named: "keyword") // Pikachu
查询参数
此外,如果存在,您还可以获取查询参数。
// actual URL: pokedex://search/Pikachu?generation=1
let generation: Int? = context.queryParameters["generation"] // 1
// or you can also get value using DynamicMemberLookup
let generation: Int? = context.queryParameters.generation // 1
您可以请 arguments/query parameters 转换为任何类型。Crossroad 尝试将每个 String 值转换为类型。
// expected pattern: pokedex://search/:pokedexID
// actual URL: pokedex://search/25
let pokedexID: Int = try context.arguments(named: "keyword") // 25
目前支持的类型有 Int
,Int64
,Float
,Double
,Bool
,String
和 URL
。
枚举参数
您可以使用枚举作为参数,前提是符合 Parsable
。
enum Type: String, Parsable {
case normal
case fire
case water
case grass
// ....
}
// matches: pokedex://pokemons?type=fire
let type: Type? = context.queryParameters.type // .fire
逗号分隔列表
您可以将逗号分隔的查询字符串视为 Array
或 Set
。
// matches: pokedex://pokemons?types=water,grass
let types: [Type]? = context.queryParameters.types // [.water, .grass]
自定义参数
您还可以通过实现 Parsable
来定义自己的参数。以下是一个解析自定义结构的示例。
struct User {
let name: String
}
extension User: Parsable {
init?(from string: String) {
self.name = string
}
}
支持多个链路源
您可以定义如下的复杂路由定义:
let customURLScheme: LinkSource = .customURLScheme("pokedex")
let pokedexWeb: LinkSource = .universalLink(URL(string: "https://my-awesome-pokedex.com")!)
let anotherWeb: LinkSource = .universalLink(URL(string: "https://kanto.my-awesome-pokedex.com")!)
let router = try DefaultRouter(accepting: [customURLScheme, pokedexWeb, anotherWeb]) { registry in
// Pokémon detail pages can be opened from all sources.
registry.route("/pokemons/:pokedexID") { context in
let pokedexID: Int = try context.argument(named: "pokedexID") // Parse 'pokedexID' from URL
if !Pokedex.isExist(pokedexID) { // Find the Pokémon by ID
throw PokedexError.pokemonIsNotExist(pokedexID)
}
presentPokedexDetailViewController(of: pokedexID)
}
// Move related pages can be opened only from Custom URL Schemes
registry.group(accepting: [customURLScheme]) { group in
group.route("/moves/:move_name") { context in
let moveName: String = try context.argument(named: "move_name")
presentMoveViewController(for: moveName)
}
group.route("/pokemons/:pokedexID/move") { context in
let pokedexID: Int = try context.argument(named: "pokedexID")
presentPokemonMoveViewController(for: pokedexID)
}
}
// You can pass acceptPolicy for a specific page.
registry.route("/regions", accepting: .only(for: pokedexWeb)) { context in
presentRegionListViewController()
}
}
此路由器可以处理三个链路源。
自定义路由器
您可以向Router
添加任何有效负载。
struct UserInfo {
let userID: Int64
}
let router = try Router<UserInfo>(accepting: customURLScheme) { registry in
registry.route("pokedex://pokemons") { context in
let userInfo: UserInfo = context.userInfo
let userID = userInfo.userID
}
// ...
])
let userInfo = UserInfo(userID: User.current.id)
router.openIfPossible(url, userInfo: userInfo)
解析URL模式
如果您维护一个复杂的应用程序并且您想要使用独立的URL模式解析器而不使用路由器。您可以使用ContextParser
。
let parser = ContextParser()
let context = parser.parse(URL(string: "pokedex:/pokemons/25")!,
with: "pokedex://pokemons/:id")
安装
Swift包管理器
- 文件 > Swift包 > 添加包依赖
- 添加https://github.com/giginet/Crossroad.git 选择“更新到下一个主要版本”中的“4.0.0”
CocoaPods
use_frameworks!
pod 'Crossroad'
Carthage
github "giginet/Crossroad"
演示
- 在 Xcode 中打开
Demo/Demo.xcodeproj
。 - 编译
Demo
架构。
支持的版本
Crossroad的最新版本需要 Swift 5.2 或更高。
在 Swift 4.1 及以下版本中使用 1.x。
Crossroad 版本 | Swift 版本 | Xcode 版本 |
---|---|---|
4.x | 5.4 | Xcode 13.0 |
3.x | 5.0 | Xcode 10.3 |
2.x | 5.0 | Xcode 10.2 |
1.x | 4.0 ~ 4.2 | ~ Xcode 10.1 |
许可证
Crossroad 采用 MIT 许可证发布。
页眉徽标采用 CC BY 4.0 许可。由 @Arslanshn 设计。