Swiftpack.co - Package - iKrisLiu/Navigator

Swiftpack.co is a collection of thousands of indexed Swift packages. Search packages.

iKrisLiu/Navigator

Navigator is a generic navigation framework for view controllers. It can decouple the dependency of different modules/components/view controllers.

Navigator

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

Navigator is a generic navigation framework for view controllers. It can decouple the dependency of different modules/components/view controllers.

Features

  • Data passing between view controllers bidirectional, inject data provider implementation for mocking data.
  • Navigation between view controllers with system default or custom transition animation
  • Support deep link and universal link
  • Goto any view controller of any navigator
  • Set context data and share it among view controllers
  • Custmize view controller transition animation

Architecture

Installation

Swift Package Manager

Swift Package Manager is a tool for automating the distribution of Swift code and is integrated into the swift compiler. To integrate Navigator into your Xcode project, specify it in your Package.swift.

dependencies: [
    .package(url: "https://github.com/iKrisLiu/Navigator", .upToNextMajor(from: "1.0.0"))
]

CocoaPods

CocoaPods is a dependency manager for Cocoa projects. To integrate Navigator into your Xcode project using CocoaPods, specify it in your Podfile:

pod 'SmartNavigator', '~> 1.0'

Carthage

Carthage is a decentralized dependency manager that builds your dependencies and provides you with binary frameworks. To integrate Navigator into your Xcode project using Carthage, specify it in your Cartfile:

github "iKrisLiu/Navigator" ~> 1.0

Usage

Initialize Root View Controller

NavigatonControler
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: "ModuleName.ViewController", navName: "UINavigationController", mode: .reset)
    
    // Coupling Way: Recommend to use this way inside one module
    let main = PageObject(vcClass: ViewController.self, navClass: UINavigationController.self, mode: .reset)
    
    // If present view controller without passing any `UINavigationController`, use it as default one.
    Navigator.defaultNavigationControllerClass = UINavigationController.self
    
    Navigator.root.window = window
    Navigator.root.show(main)
    
    return true
}
SplitViewControler
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
    let master = PageObject(vcClass: MasterViewController.self, navClass: UINavigationController.self, mode: .reset)
    let detail = PageObject(vcClass: DetailViewController.self, navClass: UINavigationController.self, mode: .reset)
    let split = PageObject(vcClass: SplitViewController.self, navClass: nil, mode: .reset, children: [master, detail])
    
    Navigator.root.window = window
    Navigator.root.show(split)
    
    return true
}
TabBarControler
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
    let firstTab = PageObject(vcClass: TabItemViewController.self, navClass: UINavigationController.self, mode: .reset)
    
    let master = PageObject(vcClass: MasterViewController.self, navClass: UINavigationController.self, mode: .reset)
    let detail = PageObject(vcClass: DetailViewController.self, navClass: UINavigationController.self, mode: .reset)
    let secondTab = PageObject(vcClass: SplitViewController.self, navClass: nil, mode: .reset, children: [master, detail])
    
    let tabs = PageObject(vcClass: UITabBarController.self, navClass: nil, mode: .reset, children: [firstTab, secondTab])
    
    Navigator.root.window = window
    Navigator.root.show(tabs)
    
    return true
}

Show / Dismiss

Supported navigation mode: Push, Present, Overlay, Popover and Goto

class DetailViewController: UIViewController {
    @objc private func onTapShowViewControler() {
        // Decoupling Way
        let page = PageObject(vcName: "UIViewController"), mode: .push)
        
        // Coupling Way
        // If present a view contoller without passing any `UINavigationController`, it will use `Navigator.defaultNavigationControllerClass`.
        let page = PageObject(vcClass: UIViewController.self, mode: .present, title: "Hello", extraData: "You can pass any type object")
        
        navigator?.show(page)
    }
    
    @objc private func onTapShowPopoverViewControler() {
        // Show from bottom
        let page = PageObject(vcClass: UIViewController.self, mode: .overlay, title: "Hello", extraData: "You can pass any type object")
        page.sourceRect = CGRect(origin: .zero, size: .init(width: 0, height: 500))
        
        // Show in center
        let page = PageObject(vcClass: UIViewController.self, mode: .popover, title: "Hello", extraData: "You can pass any type object")
        page.sourceRect = CGRect(origin: .zero, size: .init(width: 300, height: 500))
        
        navigator?.show(page)
    }
    
    @objc private func onTapDismissViewControler() {
        let data = "You can pass any type object/struct, e.g. string, tuple, dictionary and so on"
        
        navigator?.pop(data)            // Pop the top view controller (like system navigation controller pop)
        navigator?.dismiss(data)        // Dismiss the presented view controller (like system view controller dismiss)
        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

Use Safari or other approaches to test the deep link

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 data models for showing
        let root = PageObject(vcClass: MainViewController.self, navClass: UINavigationController.self, mode: .reset)
        let middle = PageObject(vcClass: MiddleViewController.self)
        let top = PageObject(vcClass: TopViewController.self)

        return root --> middle --> top
    }

    return true
}

Transition Animation

Create custom transition class inherits the Transition class and override below two methods. Then pass transition class with custom transition class name in data model.

class CustomTransition: Transition {
    public override func animateNavigationTransition(from fromView: UIView?, to toView: UIView?) { }
    public override func animatePresentingTransition(from fromView: UIView?, to toView: UIView?) { }
}

class DetailViewController: UIViewController {
    @objc private func onTapShowViewControler() {
        let page = PageObject(vcClass: UIViewController.self, mode: .present)
        page.transitionStyle = .flipHorizontal
        
        let page = PageObject(vcClass: UIViewController.self, mode: .present)
        page.transitionName = "CustomTransition"

        navigator?.show(page)
    }
}

Data Passing

class DetailViewController: UIViewController, Navigatable {
    private var data: Any?
    
    // Receive page object from previous vc after current vc initialized (before `viewDidLoad`)
    // - Note: Only called one time after vc initialized
    func onPageDidInitialize(_ page: PageObject, fromVC: UIViewController?) {
        title = page.title
        data = page.extraData
    }
    
    // Receive data before the current vc show (before `viewDidLoad`)
    // - Note: May called multiple times since appear mutiple times
    @objc optional func onDataReceiveBeforeShow(_ data: Any?, fromVC: UIViewController?) {}
    
    // Receive data from next vc before the next vc dismiss start
    @objc optional func onDataReceiveBeforeBack(_ data: Any?, fromVC: UIViewController?) {}
    
    // Receive data from next vc after the next vc dismiss animation end
    func onDataReceiveAfterBack(_ data: Any?) {
        self.data = data
    }
}

Context Data

If set context data in view controller A and open view controller B -> C by sequence, you can easily get the context data in view controller B or 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)
    }
}

Github

link
Stars: 2
Last commit: Yesterday

Releases

Custom push mode - 2021-01-09T06:03:10

Add customPush navigator mode for simulating system push mode, but each view controller has a standalone navigation bar, not share the same one navigation bar.