URLNavigator是Swift的一个三方库,用来实现页面的导航。可以通过字符串的形式路由到一个新的页面(UIViewController),不需要引入这个新页面,从而实现解耦,以及动态路由。
试用URLNavigator,需要以下几个步骤:
1.注册路由
navigator.register("YXRouter://VC/TravelPlan") { url, values, context in
return TravelPlanViewController()
}
其中navigator是Navigator类型的对象。Navigator遵循NavigatorType协议,其声明了2个属性matcher,delegate,和register等若干方法(后面需要的时候再详细介绍),在类Navigator中register方法定义如下:
open func register(_ pattern: URLPattern, _ factory: @escaping ViewControllerFactory) {
self.viewControllerFactories[pattern] = factory
}
两个参数的类型定义如下:
public typealias URLPattern = String
public typealias ViewControllerFactory = (_ url: URLConvertible, _ values: [String: Any], _ context: Any?) -> UIViewController?
函数只有1行代码,
self.viewControllerFactories[pattern] = factory。
其中viewControllerFactories的定义是一个字典类型,key是URLPattern类型(也就是String),ViewControllerFactory就是上面的闭包类型。
private var viewControllerFactories = [URLPattern: ViewControllerFactory]()
整个reigster的含义就是,在Navigator对象的字典成员变量里,添加了一个键值对,键就是URL,值是一个闭包。当URL对应的窗口被push时,对应的闭包就会得到执行。
2.打开指定页面
当要打开指定的也面时,方法调用如下:
navigator.push("YXRouter://VC/TravelPlan")
navigator还是上面定义的Navigator类型的对象。其中对push的定义,以及被其调用的方法如下(在协议NavigatorType中):
//第一个方法
//后面三个参数都有默认值
public func push(_ url: URLConvertible, context: Any? = nil, from: UINavigationControllerType? = nil, animated: Bool = true) -> UIViewController? {
return self.pushURL(url, context: context, from: from, animated: animated)
}
//第二个方法
public func pushURL(_ url: URLConvertible, context: Any? = nil, from: UINavigationControllerType? = nil, animated: Bool = true) -> UIViewController? {
guard let viewController = self.viewController(for: url, context: context) else { return nil }
return self.pushViewController(viewController, from: from, animated: animated)
}
//第三个方法
public func pushViewController(_ viewController: UIViewController, from: UINavigationControllerType?, animated: Bool) -> UIViewController? {
guard (viewController is UINavigationController) == false else { return nil } //判断viewController不能是UINavigationController对象,否则return nil;
guard let navigationController = from ?? UIViewController.topMost?.navigationController else { return nil }
guard self.delegate?.shouldPush(viewController: viewController, from: navigationController) != false else { return nil }
navigationController.pushViewController(viewController, animated: animated)
return viewController
}
第一个方法不用解释,它直接调用了第二个方法。第二个方法先去获取一个viewcontroller,其定义在Navigator类中。
open func viewController(for url: URLConvertible, context: Any? = nil) -> UIViewController? {
let urlPatterns = Array(self.viewControllerFactories.keys) //取viewControllerFactories字典的所有key,也就是注册的url,组成的数组。
guard let match = self.matcher.match(url, from: urlPatterns) else { return nil } //查看是否和之前注册的url匹配的上,匹配不上直接return nil;
guard let factory = self.viewControllerFactories[match.pattern] else { return nil } //匹配上之后,取出配备的url对应的闭包
return factory(url, match.values, context) //调用这个闭包,这个闭包返回的是可选UIViewController对象
}
还记得我们第一段代码中提供的闭包吗,返回了一个TravelPlanViewController()构造的可选对象。
获得UIViewController的对象之后,第二个方法调用了第三个方法。接下来看看第三个方法干了啥?
首先,判断viewController不能是UINavigationController对象,否则return nil;
然后,从from参数中取navigationController,如果没有则取当前最顶层的UIViewController,然后取其navigationController。
最后,用这个navigationController push viewController。
到这里,最简单的路由以及实现了。接下来我们考虑一下,切换tabbar的情况。比如从tabbar的第一个,跳到第二个item。改如何实现呢。
3.切换UITabBarController的选中项
首先,这次我们注册一个URL对应的handle,当对应的这个字符串URL模版被匹配时,对应的handle会被执行
navigator.handle("YXRouter://Table/") { url, values, context in
if let tabbarVC = currentTabBarController() as? RAMAnimatedTabBarController{
guard let indexStr = values["index"] as? String else { return false }
guard let index = Int(indexStr) else {return false}
tabbarVC.setSelectIndex(from: tabbarVC.selectedIndex, to: index)
}
return false
}
handle方法在Navigator类中定义,跟上面介绍的register一样。
open func handle(_ pattern: URLPattern, _ factory: @escaping URLOpenHandlerFactory) {
self.handlerFactories[pattern] = factory
}
第一个参数已经介绍过,第二个其实也和ViewControllerFactory一样,是个闭包类型。
public typealias URLOpenHandlerFactory = (_ url: URLConvertible, _ values: [String: Any], _ context: Any?) -> Bool
handle方法中这一句代码的含义也就是
self.handlerFactories[pattern] = factory
向Navigator类的成员变量(一个字典类型),里面添加了一个键值对。
private var handlerFactories = [URLPattern: URLOpenHandlerFactory]()
然后,我们用下面的方法来切换tabbar
navigator.open("YXRouter://Table/3")
navigator还是上面定义的Navigator类型的对象。其中对open的定义,以及被其调用的方法如下(在协议NavigatorType中):
//第一个方法
public func open(_ url: URLConvertible, context: Any? = nil) -> Bool {
return self.openURL(url, context: context)
}
//第二个方法
public func openURL(_ url: URLConvertible, context: Any?) -> Bool {
guard let handler = self.handler(for: url, context: context) else { return false }
return handler()
}
//第三个方法
open func handler(for url: URLConvertible, context: Any?) -> URLOpenHandler? {
let urlPatterns = Array(self.handlerFactories.keys)
guard let match = self.matcher.match(url, from: urlPatterns) else { return nil }
guard let handler = self.handlerFactories[match.pattern] else { return nil }
return { handler(url, match.values, context) }
}
这里比路由到UIViewController还要简单一些。只是执行了我们在navigator.handle()注册的闭包。直接执行这个闭包就行。这个闭包完成了对tabbar的切换。
通过这里的介绍我们可以看出,我们其实可以注册任何模版字符串,再需要的时候来调用,统一执行一系列的动作。