介绍
Elegant URL Routing for Swift
其实就是Swift
版本优雅的路由跳转
实现原理
1、在APP
启动的时候注册URL
和与之对应的controller
private var viewControllerFactories = [URLPattern: ViewControllerFactory]()
public typealias ViewControllerFactory = (_ url: URLConvertible, _ values: [String: Any], _ context: Any?) -> UIViewController?
open func register(_ pattern: URLPattern,
_ factory: @escaping ViewControllerFactory) {
// 根据传入的URL路径存储对应的闭包
self.viewControllerFactories[pattern] = factory
}
2、在需要跳转的时候,调用navigator
的push
或present
等方法,URL
要与之前注册的URL
一致,这样会根据URL
进行解析获取controller
和参数然后进行push
和present
操作
open func viewController(for url: URLConvertible, context: Any? = nil) -> UIViewController? {
// 获取所有的URL
let urlPatterns = Array(self.viewControllerFactories.keys)
// 匹配事先注册的所有URL
guard let match = self.matcher.match(url, from: urlPatterns) else { return nil }
// 找到之后获取对应的视图控制器
guard let factory = self.viewControllerFactories[match.pattern] else { return nil }
// 找到之后,回调闭包
return factory(url, match.values, context)
}
3、实现push
和present
主要是先获取当前页面的controller
,然后根据当前页面的controller
进行push
或present
操作
@discardableResult
public func pushURL(_ url: URLConvertible, context: Any? = nil, from: UINavigationControllerType? = nil, animated: Bool = true) -> UIViewController? {
// 根据URL获取对应的视图控制器
guard let viewController = self.viewController(for: url, context: context) else { return nil }
return self.pushViewController(viewController, from: from, animated: animated)
}
@discardableResult
public func pushViewController(_ viewController: UIViewController, from: UINavigationControllerType?, animated: Bool) -> UIViewController? {
// 根据视图控制器进行push操作
guard (viewController is UINavigationController) == false else { return nil }
// from导航控制器存在使用当前导航视图控制器,不存在便在视图控制器查找当前页面的navigationController
guard let navigationController = from ?? UIViewController.topMost?.navigationController else { return nil }
// 是否应该进行push操作
guard self.delegate?.shouldPush(viewController: viewController, from: navigationController) != false else { return nil }
// push操作
navigationController.pushViewController(viewController, animated: animated)
return viewController
}
4、URL
的匹配规则,是根据URL
中组件各部分是否一样
open func match(_ url: URLConvertible, from candidates: [URLPattern]) -> URLMatchResult? {
// 对URL进行容错处理,保证URL符合规范
let url = self.normalizeURL(url)
let scheme = url.urlValue?.scheme
// 获取URL各部分组件
let stringPathComponents = self.stringPathComponents(from :url)
// 遍历预先注册的所有URL
for candidate in candidates {
// 首先确保scheme一样
guard scheme == candidate.urlValue?.scheme else { continue }
// 匹配url的各个部分,如果一样,URL匹配成功
if let result = self.match(stringPathComponents, with: candidate) {
return result
}
}
return nil
}
基本使用
1、在AppDelegate.swift
文件中定义一个全局常量
import UIKit
import URLNavigator // 倒入库
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
private var navigator: NavigatorType? // 导航类型,一个协议
...
}
2、创建NavigationMap.swift
文件,并定义NavigationMap
枚举,定义一个初始化方法,在初始化方法中注册controller
对应的URL
,提前注册是方便后序根据URL
进行解析,获取对应的controller
和数据再进行页面跳转
// 定义一个导航map
enum NavigationMap {
// 注入navigator,进行注册所有需要操作的URL路径
static func initialize(navigator: NavigatorType) {
navigator.register("navigator://user/answer?<string:username>&<int:qid>") { url, values, context in
guard let username = url.queryParameters["username"] as? String,
let qid = url.queryParameters["qid"] as? String else { return nil }
return UserViewController(navigator: navigator, username: username)
}
return true
}
}
3、在AppDelegate
的application:didFinishLaunchingWithOptions:
方法中初始化NavigationMap
func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?
) -> Bool {
// Initialize navigation map
NavigationMap.initialize(navigator: navigator)
//...
return true
}
4、在需要的地方进行调用进行push
或present
,比如:点击cell
进行跳转
func tableView(_ tableView: UITableView, didSelectRowAt indexPath : IndexPath) {
tableView.deselectRow(at: indexPath, animated: false)
// 获取对应的数据
let user = self.users[indexPath.row]
// 根据URL进行push操作,是否能够进行push
let isPushed = self.navigator.push(user.urlString) != nil
if isPushed {
print("[Navigator] push: \(user.urlString)")
} else {
print("[Navigator] open: \(user.urlString)")
self.navigator.open(user.urlString)
}
}
以上内容原自官方demo
适用情况
远程推送的跳转
点击连接打开app定位具体页面
func application(_ application: UIApplication, open url: URL, sourceApplication: String?, annotation: Any) -> Bool {
// If you're using Facebook SDK
let fb = FBSDKApplicationDelegate.sharedInstance()
if fb.application(application, open: url, sourceApplication: sourceApplication, annotation: annotation) {
return true
}
// URLNavigator Handler
if navigator.open(url) {
return true
}
// URLNavigator View Controller
if navigator.present(url, wrap: UINavigationController.self) != nil {
return true
}
return false
}
- 内部页面之间的跳转
场景思考
单个参数我们可以直接写在URL里面,那么多个参数如何进行传递呢?
使用context
参数进行传入
let context: [AnyHashable: Any] = [
"fromViewController": self
]
Navigator.push("myapp://user/10", context: context)
Navigator.present("myapp://user/10", context: context)
Navigator.open("myapp://alert?title=Hi", context: context)
如果需要传递model
呢?将model
作为context
参数传入,在获取参数的时候进行model
类型识别和转换即可
// 将user对象作为`context`参数传入
self.navigator.push(user.urlString, context: user)
navigator.register("navigator://user/answer") { url, values, context in
// 获取context
if let user = context as? User { // 转换为User模型
print("user: \(user)")
}
return UserViewController()
}