iOS - URLNavigator路由介紹

介紹

Elegant URL Routing for Swift

其實(shí)就是Swift版本優(yōu)雅的路由跳轉(zhuǎn)

實(shí)現(xiàn)原理

1、在APP啟動(dòng)的時(shí)候注冊(cè)URL和與之對(duì)應(yīng)的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) {
    // 根據(jù)傳入的URL路徑存儲(chǔ)對(duì)應(yīng)的閉包
    self.viewControllerFactories[pattern] = factory
  }

2、在需要跳轉(zhuǎn)的時(shí)候,調(diào)用navigatorpushpresent等方法,URL要與之前注冊(cè)的URL一致,這樣會(huì)根據(jù)URL進(jìn)行解析獲取controller和參數(shù)然后進(jìn)行pushpresent操作

 open func viewController(for url: URLConvertible, context: Any? = nil) -> UIViewController? {
    // 獲取所有的URL
    let urlPatterns = Array(self.viewControllerFactories.keys)
    // 匹配事先注冊(cè)的所有URL
    guard let match = self.matcher.match(url, from: urlPatterns) else { return nil }
    // 找到之后獲取對(duì)應(yīng)的視圖控制器
    guard let factory = self.viewControllerFactories[match.pattern] else { return nil }
    // 找到之后,回調(diào)閉包
    return factory(url, match.values, context)
  }

3、實(shí)現(xiàn)pushpresent主要是先獲取當(dāng)前頁(yè)面的controller,然后根據(jù)當(dāng)前頁(yè)面的controller進(jìn)行pushpresent操作

  @discardableResult
  public func pushURL(_ url: URLConvertible, context: Any? = nil, from: UINavigationControllerType? = nil, animated: Bool = true) -> UIViewController? {
    // 根據(jù)URL獲取對(duì)應(yīng)的視圖控制器
    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? {
    // 根據(jù)視圖控制器進(jìn)行push操作
    guard (viewController is UINavigationController) == false else { return nil }
    // from導(dǎo)航控制器存在使用當(dāng)前導(dǎo)航視圖控制器,不存在便在視圖控制器查找當(dāng)前頁(yè)面的navigationController
    guard let navigationController = from ?? UIViewController.topMost?.navigationController else { return nil }
    // 是否應(yīng)該進(jìn)行push操作
    guard self.delegate?.shouldPush(viewController: viewController, from: navigationController) != false else { return nil }
    // push操作
    navigationController.pushViewController(viewController, animated: animated)
    return viewController
  }

4、URL的匹配規(guī)則,是根據(jù)URL中組件各部分是否一樣

  open func match(_ url: URLConvertible, from candidates: [URLPattern]) -> URLMatchResult? {
    // 對(duì)URL進(jìn)行容錯(cuò)處理,保證URL符合規(guī)范
    let url = self.normalizeURL(url)
    let scheme = url.urlValue?.scheme

    // 獲取URL各部分組件
    let stringPathComponents = self.stringPathComponents(from :url)

    // 遍歷預(yù)先注冊(cè)的所有URL
    for candidate in candidates {
        // 首先確保scheme一樣
      guard scheme == candidate.urlValue?.scheme else { continue }
        // 匹配url的各個(gè)部分,如果一樣,URL匹配成功
      if let result = self.match(stringPathComponents, with: candidate) {
        return result
      }
    }

    return nil
  }

基本使用

1、在AppDelegate.swift文件中定義一個(gè)全局常量

import UIKit
import URLNavigator // 倒入庫(kù)

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

  var window: UIWindow?
  private var navigator: NavigatorType? // 導(dǎo)航類型,一個(gè)協(xié)議
  ...
}

2、創(chuàng)建NavigationMap.swift文件,并定義NavigationMap枚舉,定義一個(gè)初始化方法,在初始化方法中注冊(cè)controller對(duì)應(yīng)的URL,提前注冊(cè)是方便后序根據(jù)URL進(jìn)行解析,獲取對(duì)應(yīng)的controller和數(shù)據(jù)再進(jìn)行頁(yè)面跳轉(zhuǎn)

// 定義一個(gè)導(dǎo)航map
enum NavigationMap {

 // 注入navigator,進(jìn)行注冊(cè)所有需要操作的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、在需要的地方進(jìn)行調(diào)用進(jìn)行pushpresent,比如:點(diǎn)擊cell進(jìn)行跳轉(zhuǎn)

func tableView(_ tableView: UITableView, didSelectRowAt indexPath : IndexPath) {
    tableView.deselectRow(at: indexPath, animated: false)
    // 獲取對(duì)應(yīng)的數(shù)據(jù)
    let user = self.users[indexPath.row]
    // 根據(jù)URL進(jìn)行push操作,是否能夠進(jìn)行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)
    }
  }

以上內(nèi)容原自官方demo

適用情況

  • 遠(yuǎn)程推送的跳轉(zhuǎn)

  • 點(diǎn)擊連接打開(kāi)app定位具體頁(yè)面

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
}
  • 內(nèi)部頁(yè)面之間的跳轉(zhuǎn)

場(chǎng)景思考

單個(gè)參數(shù)我們可以直接寫在URL里面,那么多個(gè)參數(shù)如何進(jìn)行傳遞呢?

使用context參數(shù)進(jìn)行傳入

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參數(shù)傳入,在獲取參數(shù)的時(shí)候進(jìn)行model類型識(shí)別和轉(zhuǎn)換即可

//  將user對(duì)象作為`context`參數(shù)傳入
self.navigator.push(user.urlString, context: user)

navigator.register("navigator://user/answer") { url, values, context in
  // 獲取context
  if let user = context as? User { // 轉(zhuǎn)換為User模型
        print("user: \(user)")
  }
  return UserViewController()
}

參考

URLNavigator

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 雨天,天氣微涼,心情微涼,找不到動(dòng)力和激情了,也許是總生病的原因,已經(jīng)懶散了一個(gè)多月,人也逐漸沒(méi)有生機(jī)盎然的勁...
    Miss微微恩閱讀 148評(píng)論 0 0
  • 一個(gè)單純溫馨的學(xué)者家庭,相守相助,相聚相失。 放下書本已有段時(shí)日,來(lái)佛山閑來(lái)無(wú)事,就去東方書城買了...
    白_水閱讀 465評(píng)論 3 1

友情鏈接更多精彩內(nèi)容