URLNavigator

Swift CocoaPods Build Status CodeCov

⛵️ URLNavigator 提供了一种优雅的方式,通过 URL 来导航视图控制器。可以使用 URLNavigator.register(_:_:) 函数来映射 URL 模式。

URLNavigator 可以用于将 URL 模式映射到两种类型:URLNavigableURLOpenHandlerURLNavigable 是一种定义了自定义初始化方法的类型,而 URLOpenHandler 是一个可以执行的闭包。初始化方法和闭包都会接收一个 URL 和占位符的值。

开始使用

1. 理解 URL 模式

URL 模式可以包含占位符。占位符将被来自 URL 的匹配值替换。使用 <> 来创建占位符。占位符可以有以下类型:string(默认)、intfloatpath

例如,myapp://user/<int:id> 匹配

但是它不匹配

2. 映射视图控制器和 URL 打开处理器

URLNavigator 允许将视图控制器和 URL 打开处理器与 URL 模式进行映射。这是一个将 URL 模式与视图控制器和闭包进行映射的示例。每个闭包都有三个参数:urlvaluescontext

let navigator = Navigator()

// register view controllers
navigator.register("myapp://user/<int:id>") { url, values, context in
  guard let userID = values["id"] as? Int else { return nil }
  return UserViewController(userID: userID)
}
navigator.register("myapp://post/<title>") { url, values, context in
  return storyboard.instantiateViewController(withIdentifier: "PostViewController")
}

// register url open handlers
navigator.handle("myapp://alert") { url, values, context in
  let title = url.queryParameters["title"]
  let message = url.queryParameters["message"]
  presentAlertController(title: title, message: message)
  return true
}

3. 推入、呈现和打开 URL

URLNavigator 可以使用 URL 推入和呈现视图控制器,以及执行闭包。

提供 from 参数给 push() 以指定新的视图控制器将被推入的导航控制器。类似地,提供 from 参数给 present() 以指定新的视图控制器将被呈现的视图控制器。如果传递 nil,这是默认值,当前应用程序的最顶层视图控制器将被用于推入或呈现视图控制器。

present() 接受一个额外的参数:wrap。如果指定了一个 UINavigationController 类,新的视图控制器将被该类包装。默认值为 nil

Navigator.push("myapp://user/123")
Navigator.present("myapp://post/54321", wrap: UINavigationController.self)

Navigator.open("myapp://alert?title=Hello&message=World")

安装

URLNavigator 官方仅支持 CocoaPods。

Podfile

pod 'URLNavigator'

示例

您可以在这里找到一个示例应用程序。

  1. 构建并安装该示例应用程序。
  2. 打开 Safari 应用程序
  3. 在 URL 栏中输入 navigator://user/devxoul
  4. 示例应用程序将被启动。

技巧和窍门

在哪里初始化 Navigator 实例

  1. 定义为全局常量

    let navigator = Navigator()
    
    class AppDelegate: UIResponder, UIApplicationDelegate {
      // ...
    }
  2. 注册到 IoC 容器

    container.register(NavigatorProtocol.self) { _ in Navigator() } // Swinject
    let navigator = container.resolve(NavigatorProtocol.self)!
  3. 从组合根注入依赖项。

在哪里映射 URL

我更喜欢使用单独的 URL 映射文件。

struct URLNavigationMap {
  static func initialize(navigator: NavigatorProtocol) {
    navigator.register("myapp://user/<int:id>") { ... }
    navigator.register("myapp://post/<title>") { ... }
    navigator.handle("myapp://alert") { ... }
  }
}

然后在 AppDelegateapplication:didFinishLaunchingWithOptions: 中调用 initialize()

@UIApplicationMain
final class AppDelegate: UIResponder, UIApplicationDelegate {
  func application(
    _ application: UIApplication,
    didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?
  ) -> Bool {
    // Navigator
    URLNavigationMap.initialize(navigator: navigator)
    
    // Do something else...
  }
}

实现 AppDelegate 启动选项 URL

如果注册了自定义 schemes,则可以使用 URL 打开您的应用程序。为了使用 URL 导航到视图控制器,您需要实现 application:didFinishLaunchingWithOptions: 方法。

func application(
  _ application: UIApplication,
  didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?
) -> Bool {
  // ...
  if let url = launchOptions?[.url] as? URL {
    if let opened = navigator.open(url)
    if !opened {
      navigator.present(url)
    }
  }
  return true
}

实现 AppDelegate Open URL 方法

您可能想要实现自定义 URL 打开处理器。这是一个将 URLNavigator 与其他 URL 打开处理器一起使用的示例。

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
}

在推入、呈现和打开时传递额外的值

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)

定义自定义 URL 值转换器

您可以为 URL 占位符定义自定义 URL 值转换器。

例如,占位符 <region> 仅允许字符串 ["us-west-1", "ap-northeast-2", "eu-west-3"]。如果它不包含任何这些字符串,则 URL 模式不应匹配。

将自定义值转换器添加到 Navigator 实例上的 [String: URLValueConverter] 字典中。

navigator.matcher.valueConverters["region"] = { pathComponents, index in
  let allowedRegions = ["us-west-1", "ap-northeast-2", "eu-west-3"]
  if allowedRegions.contains(pathComponents[index]) {
    return pathComponents[index]
  } else {
    return nil
  }
}

使用上面的代码,例如,myapp://region/<region:_> 匹配

但是它不匹配

有关更多信息,请参阅默认 URL 值转换器的实现

许可证

URLNavigator 基于 MIT 许可证。 有关更多信息,请参见 LICENSE 文件。