Navigator
Navigator 是一个通用的视图控制器导航框架。它可以解耦不同模块/组件/视图控制器之间的依赖关系。
特性
- 通过
PageObject
模型在不同视图控制器之间导航 - 通过
PageBizData
协议在视图控制器之间传递业务数据 - 支持使用
push
、present
和overlay
模式进行导航 - 支持深链接和通用链接
- 通过
goto
模式访问任何导航器的任何视图控制器 - 设置上下文数据并在视图控制器之间共享
- 自定义视图控制器过渡动画
架构
安装
Swift 包管理器
Swift 包管理器 是一个用于自动化 Swift 代码分发的工具,并集成到了 swift
编译器中。要将 Navigator 集成到您的 Xcode 项目中,请在您的 Package.swift
中指定它。
dependencies: [
.package(url: "https://github.com/ikrisliu/Navigator", .upToNextMajor(from: "1.0.0"))
]
CocoaPods
CocoaPods 是 Cocoa 项目的依赖管理器。要将 Navigator 集成到您的 Xcode 项目中使用 CocoaPods,请在您的 Podfile
中指定它。
pod 'SmartNavigator', '~> 1.0'
Carthage
Carthage 是一个去中心化的依赖管理器,它构建依赖项并提供二进制框架。要将 Navigator 集成到您的 Xcode 项目中使用 Carthage,请在您的 Cartfile
中指定它。
github "ikrisliu/Navigator" ~> 1.0
使用方法
初始化根视图控制器
导航控制器
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Decoupling Way: Recommend to use this way among modules
// View controller class name (The swift class name should be "ModuleName.ClassName")
let main = PageObject(vcName: .init(rawValue: "ModuleName.ViewController"), mode: .reset, options: .navName(.init(rawValue: "UINavigationController"))
// Coupling Way: Recommend to use this way inside one module
let main = PageObject(vcClass: ViewController.self, navCmode: .reset, options: .navClass(UINavigationController.self))
Navigator.root.window = window
Navigator.root.open(main)
return true
}
SplitViewControler
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
let master = PageObject(vcClass: MasterViewController.self, mode: .reset, options: .navClass(UINavigationController.self))
let detail = PageObject(vcClass: DetailViewController.self, mode: .reset, options: .navClass(UINavigationController.self))
let split = PageObject(vcClass: SplitViewController.self, mode: .reset, options: .children([master, detail]))
Navigator.root.window = window
Navigator.root.open(split)
return true
}
TabBarControler
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
let firstTab = PageObject(vcClass: TabItemViewController.self, mode: .reset, options: .navClass(UINavigationController.self))
let master = PageObject(vcClass: MasterViewController.self, mode: .reset, options: .navClass(UINavigationController.self))
let detail = PageObject(vcClass: DetailViewController.self, mode: .reset, options: .navClass(UINavigationController.self))
let secondTab = PageObject(vcClass: SplitViewController.self, mode: .reset, options: .children([master, detail]))
let tabs = PageObject(vcClass: UITabBarController.self, mode: .reset, options: .children([firstTab, secondTab]))
Navigator.root.window = window
Navigator.root.open(tabs)
return true
}
Open / Close
支持的导航模式: Push
、Present
、Overlay
和 Goto
class ContentPageData : PageBizData {
let message: String
init(message: String) {
self.message = message
}
}
class DetailViewController: UIViewController {
let data = ContentPageData(message: "You can pass any type object which need to implement PageBizData protocol")
@objc private func onTapShowViewControler() {
// Decoupling Way
let page = PageObject(vcName: .init(rawValue: "ModuleName.CustomViewController"), mode: .push)
// Coupling Way
let page = PageObject(
vcClass: UIViewController.self,
mode: .push,
options:
.title("Hello"),
.bizData(data)
)
navigator?.open(page)
}
@objc private func onTapShowPopoverViewControler() {
let size = view.bounds.size
// Show bottom sheet
let page = PageObject(
vcClass: UIViewController.self,
mode: .overlay,
options:
.title("Hello"),
.bizData(data)
.sourceRect(.init(origin: .init(x: 0, y: size.height - 500), size: .init(width: size.width, height: 500)))
)
// Show center popup
let page = PageObject(
vcClass: UIViewController.self,
mode: .present,
options:
.title("Hello"),
.bizData(data)
.presentationStyle(.custom),
.transitionClass(FadeTransition.self),
.sourceRect(.init(origin: .init(x: 20, y: (size.height - 300) / 2), size: .init(width: size.width - 40, height: 300)))
)
navigator?.open(page)
}
@objc private func onTapDismissViewControler() {
navigator?.close(data) // Close the current view controller
navigator?.backToRoot(data) // Back to root view controller of current navigator
navigator?.backTo(OneViewController.self) // Back to someone specific view controller which in navigtor stack
}
}
DeepLink
使用 Safari 或其他方法测试深度链接
func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool {
// Show top view controller base on current vc stack
let page = PageObject(vcClass: TopViewController.self)
Navigator.current.deepLink(page)
// Show top view controller base on current vc stack
Navigator.current.open(url: url) { _ -> PageObject? in
// Parse the deep link url to below page object for showing
return PageObject(vcClass: TopViewController.self)
}
// Show a chain of view controllers from root vc
Navigator.root.open(url: url) { _ -> PageObject? in
// Parse the deep link url to below page objects for showing
let root = PageObject(vcClass: MainViewController.self, mode: .reset, options: .navClass(UINavigationController.self))
let middle = PageObject(vcClass: MiddleViewController.self)
let top = PageObject(vcClass: TopViewController.self)
return root => middle => top
}
return true
}
过渡动画
创建自定义过渡类,继承 Transition
类并重写以下两个方法。然后通过页面对象传递带自定义过渡类名称的过渡类。
class CustomTransition: Transition {
@objc open func animatePresentationTransition(isShow: Bool, from fromView: UIView?, to toView: UIView?, completion: VoidClosure? = nil) { }
@objc open func animateNavigationTransition(isShow: Bool, from fromView: UIView?, to toView: UIView?, completion: VoidClosure? = nil) { }
}
class DetailViewController: UIViewController {
@objc private func onTapShowViewControler() {
let page = PageObject(vcClass: UIViewController.self, mode: .present, options: .transitionStyle(.flipHorizontal))
// or
let page = PageObject(vcClass: UIViewController.self, mode: .present, options: .transitionClass(CustomTransition.self))
navigator?.open(page)
}
}
数据传递
class DetailViewController: UIViewController, Navigatable {
private var data: PageBizData?
// Current VC receive page object from previous VC after the current one initialized (before `viewDidLoad`)
// - Note: Only called one time after the VC initialized
func onPageDidInitialize(_ page: PageObject, fromVC: UIViewController) {
title = page.title
data = page.bizData
}
// Current VC receive data before the current VC show (before `viewDidLoad`)
// - Note: May called multiple times since the view appear mutiple times
@objc optional func onDataReceiveBeforeShow(_ data: PageBizData?, fromVC: UIViewController) {
self.data = data
}
// Previous VC receive data from current VC after the current one dismiss animation end
func onDataReceiveAfterBack(_ data: PageBizData?) {
self.data = data
}
}
上下文数据
如果在上位控制器A中设置上下文数据并通过顺序打开控制器B=>C,您可以在控制器B或C中轻松获取上下文数据。
class ViewControllerA: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
setContext(["data": "This is context data."])
}
}
class ViewControllerC: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
print(context)
}
}