iOS - URLNavigator路由介绍

介绍

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、在需要跳转的时候,调用navigatorpushpresent等方法,URL要与之前注册的URL一致,这样会根据URL进行解析获取controller和参数然后进行pushpresent操作

 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、实现pushpresent主要是先获取当前页面的controller,然后根据当前页面的controller进行pushpresent操作

  @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、在AppDelegateapplication:didFinishLaunchingWithOptions:方法中初始化NavigationMap

  func application(
    _ application: UIApplication,
    didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?
  ) -> Bool {

    // Initialize navigation map
    NavigationMap.initialize(navigator: navigator)

    //...
    return true
  }

4、在需要的地方进行调用进行pushpresent,比如:点击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

扫描二维码关注公众号,回复: 6446743 查看本文章

适用情况

  • 远程推送的跳转

  • 点击连接打开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()
}

参考

URLNavigator

转载于:https://www.jianshu.com/p/8fdf0c1d2e4a

猜你喜欢

转载自blog.csdn.net/weixin_33898876/article/details/91264096