SmartNavigator 1.9.0

SmartNavigator 1.9.0

Kris Liu 维护。



  • Kris Liu

Navigator

badge-version badge-pms badge-languages badge-platforms

Navigator 是一个通用的视图控制器导航框架。它可以解耦不同模块/组件/视图控制器之间的依赖关系。

特性

  • 通过 PageObject 模型在不同视图控制器之间导航
  • 通过 PageBizData 协议在视图控制器之间传递业务数据
  • 支持使用 pushpresentoverlay 模式进行导航
  • 支持深链接和通用链接
  • 通过 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

支持的导航模式: PushPresentOverlayGoto

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)
    }
}