├── CRNativeRouterDemo ├── CRNativeRouter.xcodeproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcuserdata │ │ │ └── yixing.xcuserdatad │ │ │ └── UserInterfaceState.xcuserstate │ └── project.pbxproj ├── CRNativeRouter │ ├── NativeRouterGroup.plist │ ├── Assets.xcassets │ │ └── AppIcon.appiconset │ │ │ └── Contents.json │ ├── ViewController4.swift │ ├── ViewController2.swift │ ├── ViewController3.swift │ ├── Info.plist │ ├── NativeRouter.plist │ ├── ViewController.swift │ ├── ViewController5.swift │ ├── Base.lproj │ │ ├── LaunchScreen.storyboard │ │ └── Main.storyboard │ ├── AppDelegate.swift │ └── CRNativeRouter.swift └── CRNativeRouterTests │ ├── Info.plist │ └── CRNativeRouterTests.swift ├── LICENSE ├── README.md └── CRNativeRouter └── CRNativeRouter.swift /CRNativeRouterDemo/CRNativeRouter.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /CRNativeRouterDemo/CRNativeRouter.xcodeproj/project.xcworkspace/xcuserdata/yixing.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CrashRain/CRNativeRouter/HEAD/CRNativeRouterDemo/CRNativeRouter.xcodeproj/project.xcworkspace/xcuserdata/yixing.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /CRNativeRouterDemo/CRNativeRouter/NativeRouterGroup.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | NativeRouter 6 | 7 | 8 | -------------------------------------------------------------------------------- /CRNativeRouterDemo/CRNativeRouterTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /CRNativeRouterDemo/CRNativeRouter/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | } 43 | ], 44 | "info" : { 45 | "version" : 1, 46 | "author" : "xcode" 47 | } 48 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 CrashRain 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /CRNativeRouterDemo/CRNativeRouterTests/CRNativeRouterTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CRNativeRouterTests.swift 3 | // CRNativeRouterTests 4 | // 5 | // Created by CrashRain on 16/7/1. 6 | // Copyright © 2016年 CrashRain. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import CRNativeRouter 11 | 12 | class CRNativeRouterTests: XCTestCase { 13 | 14 | override func setUp() { 15 | super.setUp() 16 | // Put setup code here. This method is called before the invocation of each test method in the class. 17 | } 18 | 19 | override func tearDown() { 20 | // Put teardown code here. This method is called after the invocation of each test method in the class. 21 | super.tearDown() 22 | } 23 | 24 | func testExample() { 25 | // This is an example of a functional test case. 26 | // Use XCTAssert and related functions to verify your tests produce the correct results. 27 | } 28 | 29 | func testPerformanceExample() { 30 | // This is an example of a performance test case. 31 | self.measure { 32 | // Put the code you want to measure the time of here. 33 | } 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /CRNativeRouterDemo/CRNativeRouter/ViewController4.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController4.swift 3 | // CRNativeRouter 4 | // 5 | // Created by 易行 on 16/7/30. 6 | // Copyright © 2016年 Demeijia. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class ViewController4: UIViewController { 12 | 13 | fileprivate var test = 0 14 | fileprivate var value = 0 15 | 16 | @IBOutlet weak var testLabel: UILabel! 17 | @IBOutlet weak var valueLabel: UILabel! 18 | 19 | override func viewDidLoad() { 20 | super.viewDidLoad() 21 | 22 | // Do any additional setup after loading the view. 23 | 24 | testLabel.text = "test=\(test)" 25 | valueLabel.text = "value=\(value)" 26 | } 27 | 28 | override func didReceiveMemoryWarning() { 29 | super.didReceiveMemoryWarning() 30 | // Dispose of any resources that can be recreated. 31 | } 32 | 33 | @IBAction func dismiss(_ sender: UIBarButtonItem) { 34 | self.dismiss(animated: true, completion: nil) 35 | } 36 | 37 | } 38 | 39 | extension ViewController4: CRNativeRouterProtocol { 40 | func getParametersFromRouter(_ parameter: [String : Any]) { 41 | test = parameter["test"] as! Int 42 | value = parameter["value"] as! Int 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /CRNativeRouterDemo/CRNativeRouter/ViewController2.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController2.swift 3 | // CRNativeRouter 4 | // 5 | // Created by CrashRain on 16/7/5. 6 | // Copyright © 2016年 CrashRain. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class ViewController2: UIViewController { 12 | 13 | fileprivate var temp = 0 14 | fileprivate var test = 0 15 | fileprivate var url = "" 16 | 17 | @IBOutlet weak var tempLabel: UILabel! 18 | @IBOutlet weak var testLabel: UILabel! 19 | @IBOutlet weak var urlLabel: UILabel! 20 | 21 | override func viewDidLoad() { 22 | super.viewDidLoad() 23 | 24 | // Do any additional setup after loading the view. 25 | 26 | tempLabel.text = "temp=\(temp)" 27 | testLabel.text = "test=\(test)" 28 | urlLabel.text = "URL: \(url)" 29 | } 30 | 31 | override func didReceiveMemoryWarning() { 32 | super.didReceiveMemoryWarning() 33 | // Dispose of any resources that can be recreated. 34 | } 35 | } 36 | 37 | extension ViewController2: CRNativeRouterProtocol { 38 | func getParametersFromRouter(_ parameter: [String : Any]) { 39 | temp = parameter["temp"] as! Int 40 | test = parameter["test"] as! Int 41 | url = parameter["url"] as! String 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /CRNativeRouterDemo/CRNativeRouter/ViewController3.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController3.swift 3 | // CRNativeRouter 4 | // 5 | // Created by CrashRain on 16/7/21. 6 | // Copyright © 2016年 CrashRain. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class ViewController3: UIViewController { 12 | 13 | fileprivate var test = 0 14 | fileprivate var temp = 0 15 | fileprivate var url = "" 16 | 17 | @IBOutlet weak var testLabel: UILabel! 18 | @IBOutlet weak var tempLabel: UILabel! 19 | @IBOutlet weak var urlLabel: UILabel! 20 | 21 | override func viewDidLoad() { 22 | super.viewDidLoad() 23 | 24 | // Do any additional setup after loading the view. 25 | 26 | testLabel.text = "test=\(test)" 27 | tempLabel.text = "temp=\(temp)" 28 | urlLabel.text = "URL: \(url)" 29 | } 30 | 31 | override func didReceiveMemoryWarning() { 32 | super.didReceiveMemoryWarning() 33 | // Dispose of any resources that can be recreated. 34 | } 35 | } 36 | 37 | extension ViewController3: CRNativeRouterDelegate { 38 | func getParameters(from router: CRNativeRouter, parameters: CRNativeRouterParam) { 39 | test = parameters.test as? Int ?? 0 40 | temp = parameters.temp as? Int ?? 0 41 | url = parameters.url as? String ?? "" 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /CRNativeRouterDemo/CRNativeRouter/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1.0.0 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UIRequiredDeviceCapabilities 30 | 31 | armv7 32 | 33 | UISupportedInterfaceOrientations 34 | 35 | UIInterfaceOrientationPortrait 36 | UIInterfaceOrientationLandscapeLeft 37 | UIInterfaceOrientationLandscapeRight 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /CRNativeRouterDemo/CRNativeRouter/NativeRouter.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | identifier 7 | ViewController5 8 | parameters 9 | 10 | temp 11 | test 12 | url 13 | 14 | storyboard 15 | Main 16 | type 17 | ViewController5 18 | name 19 | vc5.md 20 | 21 | 22 | parameters 23 | 24 | value 25 | test 26 | 27 | identifier 28 | ViewController4 29 | storyboard 30 | Main 31 | type 32 | ViewController4 33 | name 34 | vc4.md 35 | 36 | 37 | parameters 38 | 39 | url 40 | test 41 | temp 42 | 43 | identifier 44 | ViewController3 45 | storyboard 46 | Main 47 | type 48 | ViewController3 49 | name 50 | vc3.md 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /CRNativeRouterDemo/CRNativeRouter/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // CRNativeRouter 4 | // 5 | // Created by CrashRain on 16/7/1. 6 | // Copyright © 2016年 CrashRain. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class ViewController: UIViewController { 12 | 13 | override func viewDidLoad() { 14 | super.viewDidLoad() 15 | // Do any additional setup after loading the view, typically from a nib. 16 | } 17 | 18 | override func didReceiveMemoryWarning() { 19 | super.didReceiveMemoryWarning() 20 | // Dispose of any resources that can be recreated. 21 | } 22 | 23 | @IBAction func jump(_ sender: UIButton) { 24 | CRNativeRouter.shared.show("Medical://vc2.md?temp=1", parameters: ["test": 1, "url": "http://xxxx.com?id=1&fdfd=2&fdg=3"]) 25 | } 26 | 27 | @IBAction func jumpToView3(_ sender: UIButton) { 28 | CRNativeRouter.shared.show("Medical://vc3.md?temp=3", parameters: ["test": 2, "url": "http://yyyy.com?id=1&haha=2&hello=3"]) 29 | } 30 | 31 | @IBAction func jumpToView4(_ sender: UIButton) { 32 | CRNativeRouter.shared.present("Medical://vc4.md?test=1&value=3") 33 | } 34 | 35 | @IBAction func jumpToView5(_ sender: UIButton) { 36 | CRNativeRouter.shared.present("Medical://vc5.md?test=1&temp=2", parameters: ["url": "www.crashrain.com"], inNavigation: true) 37 | } 38 | } 39 | 40 | extension ViewController: CRNativeRouterProtocol { 41 | func getParametersFromRouter(_ parameter: [String : Any]) { 42 | 43 | } 44 | } 45 | 46 | -------------------------------------------------------------------------------- /CRNativeRouterDemo/CRNativeRouter/ViewController5.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController5.swift 3 | // CRNativeRouter 4 | // 5 | // Created by 易行 on 2017/3/17. 6 | // Copyright © 2017年 Demeijia. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class ViewController5: UIViewController { 12 | 13 | @IBOutlet weak var urlLabel: UILabel! 14 | @IBOutlet weak var tempLabel: UILabel! 15 | @IBOutlet weak var testLabel: UILabel! 16 | 17 | var url = "" 18 | var temp = 0 19 | var test = 0 20 | 21 | override func viewDidLoad() { 22 | super.viewDidLoad() 23 | 24 | // Do any additional setup after loading the view. 25 | urlLabel.text = "url=\(url)" 26 | testLabel.text = "test=\(test)" 27 | tempLabel.text = "temp=\(temp)" 28 | } 29 | 30 | override func didReceiveMemoryWarning() { 31 | super.didReceiveMemoryWarning() 32 | // Dispose of any resources that can be recreated. 33 | } 34 | 35 | @IBAction func testNavigation(_ sender: UIButton) { 36 | let viewController = UIViewController() 37 | viewController.view.backgroundColor = UIColor.green 38 | 39 | navigationController?.pushViewController(viewController, animated: true) 40 | } 41 | 42 | @IBAction func dismiss(_ sender: UIBarButtonItem) { 43 | navigationController?.dismiss(animated: true, completion: nil) 44 | } 45 | } 46 | 47 | extension ViewController5: CRNativeRouterDelegate { 48 | func getParameters(from router: CRNativeRouter, parameters: CRNativeRouterParam) { 49 | url = parameters.url as? String ?? "" 50 | temp = parameters.temp as? Int ?? 0 51 | test = parameters.test as? Int ?? 0 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /CRNativeRouterDemo/CRNativeRouter/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /CRNativeRouterDemo/CRNativeRouter/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // CRNativeRouter 4 | // 5 | // Created by CrashRain on 16/7/1. 6 | // Copyright © 2016年 CrashRain. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | 17 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 18 | // Override point for customization after application launch. 19 | 20 | CRNativeRouter.shared.setURLModifyFormat("^(Medical://)(\\w+\\.md)(\\?(([a-zA-Z]+\\w*=\\w+)(&[a-zA-Z]+\\w*=\\w+)*)|([a-zA-Z]+\\w*=\\w+))?$") 21 | 22 | CRNativeRouter.shared.registerGroupModules(fromConfiguration: "NativeRouterGroup") 23 | 24 | CRNativeRouter.shared.registerModule("vc.md", type: ViewController.self, storyboard: "Main", identifier: "ViewController", parameters: nil) 25 | CRNativeRouter.shared.registerModule("vc2.md", type: ViewController2.self, storyboard: "Main", identifier: "ViewController2", parameters: ["temp", "test", "url"]) 26 | 27 | return true 28 | } 29 | 30 | func applicationWillResignActive(_ application: UIApplication) { 31 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 32 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. 33 | } 34 | 35 | func applicationDidEnterBackground(_ application: UIApplication) { 36 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 37 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 38 | } 39 | 40 | func applicationWillEnterForeground(_ application: UIApplication) { 41 | // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. 42 | } 43 | 44 | func applicationDidBecomeActive(_ application: UIApplication) { 45 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 46 | } 47 | 48 | func applicationWillTerminate(_ application: UIApplication) { 49 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 50 | } 51 | 52 | 53 | } 54 | 55 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CRNativeRouter 2 | 统跳协议的实现,主要用来降低模块间的耦合度,通过一个公共的组件来切换视图显示和传递数据,支持代码生成、XIB、StoryBoard界面的管理。 3 | 4 | [TOC] 5 | 6 | ##使用方法 7 | ###1、设置统跳协议需要辨识的路径正则表达式 8 | Router需要一个Internal URL来辨识需要显示的界面和要传递的参数,并且需要对传入的URL判断其合法性。在使用前需要先传入一个URL通配的正则表达式给Router。正则表达式可以用如下的模板: 9 | 10 | ``` 11 | ^(Module://)(\\w+\\.md)(\\?(([a-zA-Z]+\\w*=\\w+)(&[a-zA-Z]+\\w*=\\w+)*)|([a-zA-Z]+\\w*=\\w+))?$ 12 | ``` 13 | 14 | ###2、注册ViewController到Router 15 | 之后需要注册已有的或者需要显示的ViewController到Router,注册针对实现有三种方法,分别对应代码生成界面、Xib或者Nib、StoryBoard。 16 | 17 | 1. 代码生成界面注册API 18 | 19 | `registerNewModule(_ name: String, type: AnyClass, parameters: [String]?)` 20 | 21 | 通过这个函数传入该ViewController对应的Module名称、类型以及需要传入的参数名称。 22 | 23 | 2. Xib或者Nib注册API 24 | 25 | `registerNewModule(_ name: String, type: AnyClass, nib: String, parameters: [String]?)` 26 | 27 | 通过这个函数传入该ViewController的Module名称、类型、对应的nib名称以及需要传入的参数名称。 28 | 29 | 3. StoryBoard注册API 30 | 31 | `registerNewModule(_ name: String, type: AnyClass, storyboard: String, identifier: String, parameters: [String]?)` 32 | 33 | 通过这个函数传入该ViewController的Module名称、类型、对应的storyboard名称和其中的identifier字符串,以及需要传入的参数名称。 34 | 35 | ####团队协作 36 | 37 | 考虑到开发过程中会多人团队协作,如果单纯用代码进行注册会碰到git容易冲突的情况,因此引入用plist文件来整体管理每名成员的模块并进行批量注册。 38 | 39 | 1. 指定全局管理开发成员plist文件名称的plist文件 40 | 41 | 这个plist文件管理项目中需要注册的子文件,只有文件名称在这个plist文件中才会被Router进行注册。该plist文件内容为一个数组即可,示例内容如下: 42 | 43 | ```plist 44 | 45 | 46 | 47 | 48 | NativeRouter 49 | 50 | 51 | ``` 52 | 53 | 2. 每名成员生成自己的plist文件,将模块添加到该文件中,并将文件名称注册到全局plist文件 54 | 55 | 项目中的每位成员需要生成自己的plist文件,并且将自己开发的模块注册到该plist文件中。plist文件首先需要指定一个名为Modules的array类型数组,之后将每个ViewController注册到文件中。name和type是必须指定的字段,parameters为可选字段,若没有则默认不需要参数。StoryBoard组件额外需要storyboard和identifier字段,nib组件额外需要nib字段。示例内容如下: 56 | 57 | ```plist 58 | 59 | 60 | 61 | 62 | Modules 63 | 64 | 65 | parameters 66 | 67 | value 68 | test 69 | 70 | identifier 71 | ViewController4 72 | storyboard 73 | Main 74 | type 75 | ViewController4 76 | name 77 | vc4.md 78 | 79 | 80 | parameters 81 | 82 | url 83 | test 84 | temp 85 | 86 | identifier 87 | ViewController3 88 | storyboard 89 | Main 90 | type 91 | ViewController3 92 | name 93 | vc3.md 94 | 95 | 96 | 97 | 98 | ``` 99 | 3. 调用API进行注册 100 | 之后调用如下API,传入全局plist文件名进行注册即可: 101 | `registerModulesFromDeveloperGroupConfiguration(_ filename: String)` 102 | 103 | ###3、界面跳转 104 | 105 | 之后界面切换调用API即可,支持navigation的Show、ShowDetail、Popup三种方式,另外支持Modally显示方式,具体查看API名称即可。 106 | 107 | 同时也支持是否传入当前navigation的选项,如果不传入则会递归查找当前显示的navigation,建议传入减少性能开销。 108 | 109 | ###4、统跳原理 110 | 111 | 欢迎访问我的私人Blog [CRNativeRouter原理](http://crashrain.com/?p=259) 112 | 113 | 114 | -------------------------------------------------------------------------------- /CRNativeRouterDemo/CRNativeRouter.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 9D53F4671D4093FC004FE794 /* NativeRouter.plist in Resources */ = {isa = PBXBuildFile; fileRef = 9D53F4661D4093FC004FE794 /* NativeRouter.plist */; }; 11 | 9D53F4691D4099FF004FE794 /* ViewController3.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9D53F4681D4099FF004FE794 /* ViewController3.swift */; }; 12 | 9D53F46B1D40A090004FE794 /* NativeRouterGroup.plist in Resources */ = {isa = PBXBuildFile; fileRef = 9D53F46A1D40A090004FE794 /* NativeRouterGroup.plist */; }; 13 | 9D8B5DA21D4C72D10059E6C2 /* ViewController4.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9D8B5DA11D4C72D10059E6C2 /* ViewController4.swift */; }; 14 | 9D9E14641D2B7F30006459EA /* ViewController2.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9D9E14631D2B7F30006459EA /* ViewController2.swift */; }; 15 | 9DA76A021D26421B00D21910 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DA76A011D26421B00D21910 /* AppDelegate.swift */; }; 16 | 9DA76A041D26421B00D21910 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DA76A031D26421B00D21910 /* ViewController.swift */; }; 17 | 9DA76A071D26421B00D21910 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 9DA76A051D26421B00D21910 /* Main.storyboard */; }; 18 | 9DA76A091D26421B00D21910 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 9DA76A081D26421B00D21910 /* Assets.xcassets */; }; 19 | 9DA76A0C1D26421B00D21910 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 9DA76A0A1D26421B00D21910 /* LaunchScreen.storyboard */; }; 20 | 9DA76A171D26421B00D21910 /* CRNativeRouterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DA76A161D26421B00D21910 /* CRNativeRouterTests.swift */; }; 21 | 9DA76A221D26430000D21910 /* CRNativeRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DA76A211D26430000D21910 /* CRNativeRouter.swift */; }; 22 | 9DF8534E1E7BAF0400A170BB /* ViewController5.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DF8534D1E7BAF0400A170BB /* ViewController5.swift */; }; 23 | /* End PBXBuildFile section */ 24 | 25 | /* Begin PBXContainerItemProxy section */ 26 | 9DA76A131D26421B00D21910 /* PBXContainerItemProxy */ = { 27 | isa = PBXContainerItemProxy; 28 | containerPortal = 9DA769F61D26421A00D21910 /* Project object */; 29 | proxyType = 1; 30 | remoteGlobalIDString = 9DA769FD1D26421A00D21910; 31 | remoteInfo = CRNativeRouter; 32 | }; 33 | /* End PBXContainerItemProxy section */ 34 | 35 | /* Begin PBXFileReference section */ 36 | 9D53F4661D4093FC004FE794 /* NativeRouter.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = NativeRouter.plist; sourceTree = ""; }; 37 | 9D53F4681D4099FF004FE794 /* ViewController3.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ViewController3.swift; sourceTree = ""; }; 38 | 9D53F46A1D40A090004FE794 /* NativeRouterGroup.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = NativeRouterGroup.plist; sourceTree = ""; }; 39 | 9D8B5DA11D4C72D10059E6C2 /* ViewController4.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ViewController4.swift; sourceTree = ""; }; 40 | 9D9E14631D2B7F30006459EA /* ViewController2.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ViewController2.swift; sourceTree = ""; }; 41 | 9DA769FE1D26421A00D21910 /* CRNativeRouter.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = CRNativeRouter.app; sourceTree = BUILT_PRODUCTS_DIR; }; 42 | 9DA76A011D26421B00D21910 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 43 | 9DA76A031D26421B00D21910 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 44 | 9DA76A061D26421B00D21910 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 45 | 9DA76A081D26421B00D21910 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 46 | 9DA76A0B1D26421B00D21910 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 47 | 9DA76A0D1D26421B00D21910 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 48 | 9DA76A121D26421B00D21910 /* CRNativeRouterTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = CRNativeRouterTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 49 | 9DA76A161D26421B00D21910 /* CRNativeRouterTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CRNativeRouterTests.swift; sourceTree = ""; }; 50 | 9DA76A181D26421B00D21910 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 51 | 9DA76A211D26430000D21910 /* CRNativeRouter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CRNativeRouter.swift; sourceTree = ""; }; 52 | 9DF8534D1E7BAF0400A170BB /* ViewController5.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ViewController5.swift; sourceTree = ""; }; 53 | /* End PBXFileReference section */ 54 | 55 | /* Begin PBXFrameworksBuildPhase section */ 56 | 9DA769FB1D26421A00D21910 /* Frameworks */ = { 57 | isa = PBXFrameworksBuildPhase; 58 | buildActionMask = 2147483647; 59 | files = ( 60 | ); 61 | runOnlyForDeploymentPostprocessing = 0; 62 | }; 63 | 9DA76A0F1D26421B00D21910 /* Frameworks */ = { 64 | isa = PBXFrameworksBuildPhase; 65 | buildActionMask = 2147483647; 66 | files = ( 67 | ); 68 | runOnlyForDeploymentPostprocessing = 0; 69 | }; 70 | /* End PBXFrameworksBuildPhase section */ 71 | 72 | /* Begin PBXGroup section */ 73 | 9DA769F51D26421A00D21910 = { 74 | isa = PBXGroup; 75 | children = ( 76 | 9DA76A001D26421B00D21910 /* CRNativeRouter */, 77 | 9DA76A151D26421B00D21910 /* CRNativeRouterTests */, 78 | 9DA769FF1D26421A00D21910 /* Products */, 79 | ); 80 | sourceTree = ""; 81 | }; 82 | 9DA769FF1D26421A00D21910 /* Products */ = { 83 | isa = PBXGroup; 84 | children = ( 85 | 9DA769FE1D26421A00D21910 /* CRNativeRouter.app */, 86 | 9DA76A121D26421B00D21910 /* CRNativeRouterTests.xctest */, 87 | ); 88 | name = Products; 89 | sourceTree = ""; 90 | }; 91 | 9DA76A001D26421B00D21910 /* CRNativeRouter */ = { 92 | isa = PBXGroup; 93 | children = ( 94 | 9DA76A011D26421B00D21910 /* AppDelegate.swift */, 95 | 9DA76A211D26430000D21910 /* CRNativeRouter.swift */, 96 | 9DA76A031D26421B00D21910 /* ViewController.swift */, 97 | 9D9E14631D2B7F30006459EA /* ViewController2.swift */, 98 | 9D53F4681D4099FF004FE794 /* ViewController3.swift */, 99 | 9D8B5DA11D4C72D10059E6C2 /* ViewController4.swift */, 100 | 9DF8534D1E7BAF0400A170BB /* ViewController5.swift */, 101 | 9DA76A051D26421B00D21910 /* Main.storyboard */, 102 | 9DA76A081D26421B00D21910 /* Assets.xcassets */, 103 | 9DA76A0A1D26421B00D21910 /* LaunchScreen.storyboard */, 104 | 9DA76A0D1D26421B00D21910 /* Info.plist */, 105 | 9D53F4661D4093FC004FE794 /* NativeRouter.plist */, 106 | 9D53F46A1D40A090004FE794 /* NativeRouterGroup.plist */, 107 | ); 108 | path = CRNativeRouter; 109 | sourceTree = ""; 110 | }; 111 | 9DA76A151D26421B00D21910 /* CRNativeRouterTests */ = { 112 | isa = PBXGroup; 113 | children = ( 114 | 9DA76A161D26421B00D21910 /* CRNativeRouterTests.swift */, 115 | 9DA76A181D26421B00D21910 /* Info.plist */, 116 | ); 117 | path = CRNativeRouterTests; 118 | sourceTree = ""; 119 | }; 120 | /* End PBXGroup section */ 121 | 122 | /* Begin PBXNativeTarget section */ 123 | 9DA769FD1D26421A00D21910 /* CRNativeRouter */ = { 124 | isa = PBXNativeTarget; 125 | buildConfigurationList = 9DA76A1B1D26421B00D21910 /* Build configuration list for PBXNativeTarget "CRNativeRouter" */; 126 | buildPhases = ( 127 | 9DA769FA1D26421A00D21910 /* Sources */, 128 | 9DA769FB1D26421A00D21910 /* Frameworks */, 129 | 9DA769FC1D26421A00D21910 /* Resources */, 130 | ); 131 | buildRules = ( 132 | ); 133 | dependencies = ( 134 | ); 135 | name = CRNativeRouter; 136 | productName = CRNativeRouter; 137 | productReference = 9DA769FE1D26421A00D21910 /* CRNativeRouter.app */; 138 | productType = "com.apple.product-type.application"; 139 | }; 140 | 9DA76A111D26421B00D21910 /* CRNativeRouterTests */ = { 141 | isa = PBXNativeTarget; 142 | buildConfigurationList = 9DA76A1E1D26421B00D21910 /* Build configuration list for PBXNativeTarget "CRNativeRouterTests" */; 143 | buildPhases = ( 144 | 9DA76A0E1D26421B00D21910 /* Sources */, 145 | 9DA76A0F1D26421B00D21910 /* Frameworks */, 146 | 9DA76A101D26421B00D21910 /* Resources */, 147 | ); 148 | buildRules = ( 149 | ); 150 | dependencies = ( 151 | 9DA76A141D26421B00D21910 /* PBXTargetDependency */, 152 | ); 153 | name = CRNativeRouterTests; 154 | productName = CRNativeRouterTests; 155 | productReference = 9DA76A121D26421B00D21910 /* CRNativeRouterTests.xctest */; 156 | productType = "com.apple.product-type.bundle.unit-test"; 157 | }; 158 | /* End PBXNativeTarget section */ 159 | 160 | /* Begin PBXProject section */ 161 | 9DA769F61D26421A00D21910 /* Project object */ = { 162 | isa = PBXProject; 163 | attributes = { 164 | LastSwiftUpdateCheck = 0730; 165 | LastUpgradeCheck = 1420; 166 | ORGANIZATIONNAME = Demeijia; 167 | TargetAttributes = { 168 | 9DA769FD1D26421A00D21910 = { 169 | CreatedOnToolsVersion = 7.3.1; 170 | DevelopmentTeam = 755CA3W3VN; 171 | LastSwiftMigration = 0820; 172 | }; 173 | 9DA76A111D26421B00D21910 = { 174 | CreatedOnToolsVersion = 7.3.1; 175 | LastSwiftMigration = 0820; 176 | TestTargetID = 9DA769FD1D26421A00D21910; 177 | }; 178 | }; 179 | }; 180 | buildConfigurationList = 9DA769F91D26421A00D21910 /* Build configuration list for PBXProject "CRNativeRouter" */; 181 | compatibilityVersion = "Xcode 3.2"; 182 | developmentRegion = en; 183 | hasScannedForEncodings = 0; 184 | knownRegions = ( 185 | en, 186 | Base, 187 | ); 188 | mainGroup = 9DA769F51D26421A00D21910; 189 | productRefGroup = 9DA769FF1D26421A00D21910 /* Products */; 190 | projectDirPath = ""; 191 | projectRoot = ""; 192 | targets = ( 193 | 9DA769FD1D26421A00D21910 /* CRNativeRouter */, 194 | 9DA76A111D26421B00D21910 /* CRNativeRouterTests */, 195 | ); 196 | }; 197 | /* End PBXProject section */ 198 | 199 | /* Begin PBXResourcesBuildPhase section */ 200 | 9DA769FC1D26421A00D21910 /* Resources */ = { 201 | isa = PBXResourcesBuildPhase; 202 | buildActionMask = 2147483647; 203 | files = ( 204 | 9D53F4671D4093FC004FE794 /* NativeRouter.plist in Resources */, 205 | 9DA76A0C1D26421B00D21910 /* LaunchScreen.storyboard in Resources */, 206 | 9DA76A091D26421B00D21910 /* Assets.xcassets in Resources */, 207 | 9D53F46B1D40A090004FE794 /* NativeRouterGroup.plist in Resources */, 208 | 9DA76A071D26421B00D21910 /* Main.storyboard in Resources */, 209 | ); 210 | runOnlyForDeploymentPostprocessing = 0; 211 | }; 212 | 9DA76A101D26421B00D21910 /* Resources */ = { 213 | isa = PBXResourcesBuildPhase; 214 | buildActionMask = 2147483647; 215 | files = ( 216 | ); 217 | runOnlyForDeploymentPostprocessing = 0; 218 | }; 219 | /* End PBXResourcesBuildPhase section */ 220 | 221 | /* Begin PBXSourcesBuildPhase section */ 222 | 9DA769FA1D26421A00D21910 /* Sources */ = { 223 | isa = PBXSourcesBuildPhase; 224 | buildActionMask = 2147483647; 225 | files = ( 226 | 9D53F4691D4099FF004FE794 /* ViewController3.swift in Sources */, 227 | 9D8B5DA21D4C72D10059E6C2 /* ViewController4.swift in Sources */, 228 | 9DA76A041D26421B00D21910 /* ViewController.swift in Sources */, 229 | 9D9E14641D2B7F30006459EA /* ViewController2.swift in Sources */, 230 | 9DA76A221D26430000D21910 /* CRNativeRouter.swift in Sources */, 231 | 9DA76A021D26421B00D21910 /* AppDelegate.swift in Sources */, 232 | 9DF8534E1E7BAF0400A170BB /* ViewController5.swift in Sources */, 233 | ); 234 | runOnlyForDeploymentPostprocessing = 0; 235 | }; 236 | 9DA76A0E1D26421B00D21910 /* Sources */ = { 237 | isa = PBXSourcesBuildPhase; 238 | buildActionMask = 2147483647; 239 | files = ( 240 | 9DA76A171D26421B00D21910 /* CRNativeRouterTests.swift in Sources */, 241 | ); 242 | runOnlyForDeploymentPostprocessing = 0; 243 | }; 244 | /* End PBXSourcesBuildPhase section */ 245 | 246 | /* Begin PBXTargetDependency section */ 247 | 9DA76A141D26421B00D21910 /* PBXTargetDependency */ = { 248 | isa = PBXTargetDependency; 249 | target = 9DA769FD1D26421A00D21910 /* CRNativeRouter */; 250 | targetProxy = 9DA76A131D26421B00D21910 /* PBXContainerItemProxy */; 251 | }; 252 | /* End PBXTargetDependency section */ 253 | 254 | /* Begin PBXVariantGroup section */ 255 | 9DA76A051D26421B00D21910 /* Main.storyboard */ = { 256 | isa = PBXVariantGroup; 257 | children = ( 258 | 9DA76A061D26421B00D21910 /* Base */, 259 | ); 260 | name = Main.storyboard; 261 | sourceTree = ""; 262 | }; 263 | 9DA76A0A1D26421B00D21910 /* LaunchScreen.storyboard */ = { 264 | isa = PBXVariantGroup; 265 | children = ( 266 | 9DA76A0B1D26421B00D21910 /* Base */, 267 | ); 268 | name = LaunchScreen.storyboard; 269 | sourceTree = ""; 270 | }; 271 | /* End PBXVariantGroup section */ 272 | 273 | /* Begin XCBuildConfiguration section */ 274 | 9DA76A191D26421B00D21910 /* Debug */ = { 275 | isa = XCBuildConfiguration; 276 | buildSettings = { 277 | ALWAYS_SEARCH_USER_PATHS = NO; 278 | CLANG_ANALYZER_NONNULL = YES; 279 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 280 | CLANG_CXX_LIBRARY = "libc++"; 281 | CLANG_ENABLE_MODULES = YES; 282 | CLANG_ENABLE_OBJC_ARC = YES; 283 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 284 | CLANG_WARN_BOOL_CONVERSION = YES; 285 | CLANG_WARN_COMMA = YES; 286 | CLANG_WARN_CONSTANT_CONVERSION = YES; 287 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 288 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 289 | CLANG_WARN_EMPTY_BODY = YES; 290 | CLANG_WARN_ENUM_CONVERSION = YES; 291 | CLANG_WARN_INFINITE_RECURSION = YES; 292 | CLANG_WARN_INT_CONVERSION = YES; 293 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 294 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 295 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 296 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 297 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 298 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 299 | CLANG_WARN_STRICT_PROTOTYPES = YES; 300 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 301 | CLANG_WARN_UNREACHABLE_CODE = YES; 302 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 303 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 304 | COPY_PHASE_STRIP = NO; 305 | DEBUG_INFORMATION_FORMAT = dwarf; 306 | ENABLE_STRICT_OBJC_MSGSEND = YES; 307 | ENABLE_TESTABILITY = YES; 308 | GCC_C_LANGUAGE_STANDARD = gnu99; 309 | GCC_DYNAMIC_NO_PIC = NO; 310 | GCC_NO_COMMON_BLOCKS = YES; 311 | GCC_OPTIMIZATION_LEVEL = 0; 312 | GCC_PREPROCESSOR_DEFINITIONS = ( 313 | "DEBUG=1", 314 | "$(inherited)", 315 | ); 316 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 317 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 318 | GCC_WARN_UNDECLARED_SELECTOR = YES; 319 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 320 | GCC_WARN_UNUSED_FUNCTION = YES; 321 | GCC_WARN_UNUSED_VARIABLE = YES; 322 | IPHONEOS_DEPLOYMENT_TARGET = 11.0; 323 | MTL_ENABLE_DEBUG_INFO = YES; 324 | ONLY_ACTIVE_ARCH = YES; 325 | SDKROOT = iphoneos; 326 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 327 | }; 328 | name = Debug; 329 | }; 330 | 9DA76A1A1D26421B00D21910 /* Release */ = { 331 | isa = XCBuildConfiguration; 332 | buildSettings = { 333 | ALWAYS_SEARCH_USER_PATHS = NO; 334 | CLANG_ANALYZER_NONNULL = YES; 335 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 336 | CLANG_CXX_LIBRARY = "libc++"; 337 | CLANG_ENABLE_MODULES = YES; 338 | CLANG_ENABLE_OBJC_ARC = YES; 339 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 340 | CLANG_WARN_BOOL_CONVERSION = YES; 341 | CLANG_WARN_COMMA = YES; 342 | CLANG_WARN_CONSTANT_CONVERSION = YES; 343 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 344 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 345 | CLANG_WARN_EMPTY_BODY = YES; 346 | CLANG_WARN_ENUM_CONVERSION = YES; 347 | CLANG_WARN_INFINITE_RECURSION = YES; 348 | CLANG_WARN_INT_CONVERSION = YES; 349 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 350 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 351 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 352 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 353 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 354 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 355 | CLANG_WARN_STRICT_PROTOTYPES = YES; 356 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 357 | CLANG_WARN_UNREACHABLE_CODE = YES; 358 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 359 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 360 | COPY_PHASE_STRIP = NO; 361 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 362 | ENABLE_NS_ASSERTIONS = NO; 363 | ENABLE_STRICT_OBJC_MSGSEND = YES; 364 | GCC_C_LANGUAGE_STANDARD = gnu99; 365 | GCC_NO_COMMON_BLOCKS = YES; 366 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 367 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 368 | GCC_WARN_UNDECLARED_SELECTOR = YES; 369 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 370 | GCC_WARN_UNUSED_FUNCTION = YES; 371 | GCC_WARN_UNUSED_VARIABLE = YES; 372 | IPHONEOS_DEPLOYMENT_TARGET = 11.0; 373 | MTL_ENABLE_DEBUG_INFO = NO; 374 | SDKROOT = iphoneos; 375 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 376 | VALIDATE_PRODUCT = YES; 377 | }; 378 | name = Release; 379 | }; 380 | 9DA76A1C1D26421B00D21910 /* Debug */ = { 381 | isa = XCBuildConfiguration; 382 | buildSettings = { 383 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 384 | DEVELOPMENT_TEAM = 755CA3W3VN; 385 | INFOPLIST_FILE = CRNativeRouter/Info.plist; 386 | IPHONEOS_DEPLOYMENT_TARGET = 11.0; 387 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 388 | PRODUCT_BUNDLE_IDENTIFIER = com.Demeijia.CRNativeRouter; 389 | PRODUCT_NAME = "$(TARGET_NAME)"; 390 | SWIFT_VERSION = 5.0; 391 | }; 392 | name = Debug; 393 | }; 394 | 9DA76A1D1D26421B00D21910 /* Release */ = { 395 | isa = XCBuildConfiguration; 396 | buildSettings = { 397 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 398 | DEVELOPMENT_TEAM = 755CA3W3VN; 399 | INFOPLIST_FILE = CRNativeRouter/Info.plist; 400 | IPHONEOS_DEPLOYMENT_TARGET = 11.0; 401 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 402 | PRODUCT_BUNDLE_IDENTIFIER = com.Demeijia.CRNativeRouter; 403 | PRODUCT_NAME = "$(TARGET_NAME)"; 404 | SWIFT_VERSION = 5.0; 405 | }; 406 | name = Release; 407 | }; 408 | 9DA76A1F1D26421B00D21910 /* Debug */ = { 409 | isa = XCBuildConfiguration; 410 | buildSettings = { 411 | BUNDLE_LOADER = "$(TEST_HOST)"; 412 | INFOPLIST_FILE = CRNativeRouterTests/Info.plist; 413 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 414 | PRODUCT_BUNDLE_IDENTIFIER = com.Demeijia.CRNativeRouterTests; 415 | PRODUCT_NAME = "$(TARGET_NAME)"; 416 | SWIFT_VERSION = 5.0; 417 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/CRNativeRouter.app/CRNativeRouter"; 418 | }; 419 | name = Debug; 420 | }; 421 | 9DA76A201D26421B00D21910 /* Release */ = { 422 | isa = XCBuildConfiguration; 423 | buildSettings = { 424 | BUNDLE_LOADER = "$(TEST_HOST)"; 425 | INFOPLIST_FILE = CRNativeRouterTests/Info.plist; 426 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 427 | PRODUCT_BUNDLE_IDENTIFIER = com.Demeijia.CRNativeRouterTests; 428 | PRODUCT_NAME = "$(TARGET_NAME)"; 429 | SWIFT_VERSION = 5.0; 430 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/CRNativeRouter.app/CRNativeRouter"; 431 | }; 432 | name = Release; 433 | }; 434 | /* End XCBuildConfiguration section */ 435 | 436 | /* Begin XCConfigurationList section */ 437 | 9DA769F91D26421A00D21910 /* Build configuration list for PBXProject "CRNativeRouter" */ = { 438 | isa = XCConfigurationList; 439 | buildConfigurations = ( 440 | 9DA76A191D26421B00D21910 /* Debug */, 441 | 9DA76A1A1D26421B00D21910 /* Release */, 442 | ); 443 | defaultConfigurationIsVisible = 0; 444 | defaultConfigurationName = Release; 445 | }; 446 | 9DA76A1B1D26421B00D21910 /* Build configuration list for PBXNativeTarget "CRNativeRouter" */ = { 447 | isa = XCConfigurationList; 448 | buildConfigurations = ( 449 | 9DA76A1C1D26421B00D21910 /* Debug */, 450 | 9DA76A1D1D26421B00D21910 /* Release */, 451 | ); 452 | defaultConfigurationIsVisible = 0; 453 | defaultConfigurationName = Release; 454 | }; 455 | 9DA76A1E1D26421B00D21910 /* Build configuration list for PBXNativeTarget "CRNativeRouterTests" */ = { 456 | isa = XCConfigurationList; 457 | buildConfigurations = ( 458 | 9DA76A1F1D26421B00D21910 /* Debug */, 459 | 9DA76A201D26421B00D21910 /* Release */, 460 | ); 461 | defaultConfigurationIsVisible = 0; 462 | defaultConfigurationName = Release; 463 | }; 464 | /* End XCConfigurationList section */ 465 | }; 466 | rootObject = 9DA769F61D26421A00D21910 /* Project object */; 467 | } 468 | -------------------------------------------------------------------------------- /CRNativeRouter/CRNativeRouter.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CRNativeRouter.swift 3 | // CRNativeRouter 4 | // 5 | // Created by CrashRain on 16/7/1. 6 | // Copyright © 2016年 CrashRain. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | private func ~= (lhs: String, rhs: String) -> Bool { 12 | if let result = ((try? NSRegularExpression(pattern: rhs, options: .caseInsensitive).firstMatch(in: lhs, options: NSRegularExpression.MatchingOptions(rawValue: 0), range: NSRange(location: 0, length: lhs.count))) as NSTextCheckingResult??) { 13 | return result != nil 14 | } 15 | 16 | return false 17 | } 18 | 19 | @objc public class CRNativeRouterPresentOptions: NSObject { 20 | var presentationStyle = UIModalPresentationStyle.overCurrentContext 21 | var transitionStyle = UIModalTransitionStyle.coverVertical 22 | } 23 | 24 | @dynamicMemberLookup 25 | public struct CRNativeRouterParamT where Key: Hashable & ExpressibleByStringLiteral { 26 | var wrappedValue: [Key: Value] = [:] 27 | 28 | init(dict: [Key: Value]) { 29 | wrappedValue = dict 30 | } 31 | 32 | subscript(key: Key) -> Value? { 33 | get { 34 | return wrappedValue[key] 35 | } 36 | set { 37 | wrappedValue[key] = newValue 38 | } 39 | } 40 | 41 | subscript(dynamicMember key: Key) -> Value? { 42 | return wrappedValue[key] 43 | } 44 | } 45 | 46 | public typealias CRNativeRouterParam = CRNativeRouterParamT 47 | 48 | public protocol CRNativeRouterProtocol: NSObjectProtocol { 49 | func getParametersFromRouter(_ parameter: [String: Any]) 50 | } 51 | 52 | public protocol CRNativeRouterDelegate { 53 | func getParameters(from router: CRNativeRouter, parameters: CRNativeRouterParam) 54 | } 55 | 56 | public class CRNativeRouter: NSObject { 57 | 58 | // 视图控制器类型枚举 59 | private enum CRNativeRouterViewControllerType { 60 | case normal(type: AnyClass) 61 | case nib(type: AnyClass, name: String) 62 | case storyboard(type: AnyClass, name: String, identifier: String) 63 | } 64 | 65 | // 视图显示方式 66 | private enum CRNativeRouterViewPresentType { 67 | case show 68 | case showDetail 69 | case presentModally 70 | case presentAsPopover 71 | } 72 | 73 | private enum CRNativeRouterKey: String { 74 | case module = "CRNativeRouterModuleKey" 75 | case parameters = "CRNativeRouterParametersKey" 76 | } 77 | 78 | // 映射关系 79 | private var mapClass: [String: CRNativeRouterViewControllerType] = [:] 80 | private var mapParameters: [String: [String]] = [:] 81 | 82 | // 预设的URL匹配正则表达式 83 | private var regularFormat = "^(Module://)(\\w+\\.md)(\\?(([a-zA-Z]+\\w*=\\w+)(&[a-zA-Z]+\\w*=\\w+)*)|([a-zA-Z]+\\w*=\\w+))?$" 84 | 85 | // 单例 86 | public static let shared = CRNativeRouter() 87 | 88 | @available(iOS, deprecated, message: "Use shared instead") 89 | @objc public class func sharedInstance() -> CRNativeRouter! { 90 | return shared 91 | } 92 | 93 | /** 94 | 分离URL中的模块名称和参数队列 95 | 96 | - parameter url: URL 97 | 98 | - returns: 分离后的字典数据 99 | */ 100 | private func divideComponentsFromUrl(_ url: String) -> [CRNativeRouterKey: String] { 101 | var compResult: [CRNativeRouterKey: String] = [:] 102 | 103 | do { 104 | // 分离模块名称 105 | var regularExpression = try NSRegularExpression(pattern: "://\\w+\\.md", options: []) 106 | var components = regularExpression.matches(in: url, options: .reportCompletion, range: NSMakeRange(0, url.count)) 107 | 108 | if components.count > 0 { 109 | let tempRange = components[0].range 110 | compResult[.module] = String(url[url.index(url.startIndex, offsetBy: tempRange.location + 3) ..< url.index(url.startIndex, offsetBy: tempRange.location + tempRange.length)]) 111 | } 112 | 113 | // 分离参数 114 | regularExpression = try NSRegularExpression(pattern: "\\?[\\w|&|=]*$", options: []) 115 | components = regularExpression.matches(in: url, options: .reportCompletion, range: NSMakeRange(0, url.count)) 116 | 117 | if components.count > 0 { 118 | let tempRange = components[0].range 119 | compResult[.parameters] = String(url[url.index(url.startIndex, offsetBy: tempRange.location + 1) ..< url.index(url.startIndex, offsetBy: tempRange.location + tempRange.length)]) 120 | } 121 | } catch { 122 | // exception catched 123 | } 124 | 125 | return compResult 126 | } 127 | 128 | /** 129 | 通过module名称映射到对应的视图控制器 130 | 131 | - parameter module: module名称 132 | 133 | - returns: 对应的视图控制器 134 | */ 135 | private func reflectViewController(_ module: String) -> UIViewController? { 136 | guard let type = mapClass[module] else { return nil } 137 | 138 | var viewController: UIViewController? = nil 139 | 140 | switch type { 141 | case .normal(let vcType): 142 | viewController = (vcType as! UIViewController.Type).init() 143 | case .nib(let vcType, let nib): 144 | viewController = (vcType as! UIViewController.Type).init(nibName: nib, bundle: nil) 145 | case .storyboard(_, let name, let identifier): 146 | viewController = UIStoryboard(name: name, bundle: nil).instantiateViewController(withIdentifier: identifier) 147 | } 148 | 149 | return viewController 150 | } 151 | 152 | /** 153 | 视图控制器参数校验 154 | 155 | - parameter module: module名称 156 | - parameter parameter: URL中的参数队列字符串 157 | 158 | - returns: 校验结果 159 | */ 160 | private func viewControllerParametersCheck(_ module: String, parameter: String, paramDict: [String: Any]? = nil) -> Bool { 161 | guard let requiredList = mapParameters[module], requiredList.count > 0 else { return true } 162 | 163 | let components = parameter.components(separatedBy: "&") 164 | var params = Set() 165 | 166 | components.filter { $0.count > 0 }.forEach { params.insert($0.components(separatedBy: "=")[0]) } 167 | 168 | if let additionalParams = paramDict { 169 | params.formUnion(Set(additionalParams.keys)) 170 | } 171 | 172 | return Set(requiredList).intersection(params).count == requiredList.count 173 | } 174 | 175 | /** 176 | 生成视图控制器参数对应的字典数据 177 | 178 | - parameter parameter: 参数队列字符串 179 | 180 | - returns: 参数字典数据 181 | */ 182 | private func viewControllerParameterGenerate(_ parameter: String, paramDict: [String: Any]? = nil) -> [String: Any] { 183 | guard parameter != "" else { return paramDict ?? [:] } 184 | 185 | let components = parameter.components(separatedBy: "&") 186 | var params: [String: Any] = [:] 187 | 188 | components.forEach { item in 189 | let refs = item.components(separatedBy: "=") 190 | 191 | if let intValue = Int(refs[1]), "\(intValue)" == refs[1] { 192 | params[refs[0]] = intValue 193 | } else if let doubleValue = Double(refs[1]), "\(doubleValue)" == refs[1] { 194 | params[refs[0]] = doubleValue 195 | } else { 196 | params[refs[0]] = refs[1] 197 | } 198 | } 199 | 200 | if let additionalParam = paramDict { 201 | params.merge(additionalParam) { (_, new) -> Any in new } 202 | } 203 | 204 | return params 205 | } 206 | 207 | /** 208 | 返回URL对应的视图控制器,并且完成对应的参数初始化 209 | 210 | - parameter url: URL 211 | 212 | - returns: 视图控制器 213 | */ 214 | private func configureModule(_ url: String, parameters: [String: Any]? = nil) -> UIViewController? { 215 | guard url ~= regularFormat else { return nil } 216 | 217 | let components = divideComponentsFromUrl(url) 218 | guard let module = components[.module] else { return nil } 219 | let parameterStr = components[.parameters] ?? "" 220 | 221 | guard viewControllerParametersCheck(module, parameter: parameterStr, paramDict: parameters), let viewController = reflectViewController(module) else { return nil } 222 | 223 | if let p = mapParameters[module], p.count > 0 && !(viewController is CRNativeRouterProtocol || viewController is CRNativeRouterDelegate) { 224 | return nil 225 | } 226 | 227 | let paramDict = viewControllerParameterGenerate(parameterStr, paramDict: parameters) 228 | if viewController is CRNativeRouterDelegate { 229 | (viewController as! CRNativeRouterDelegate).getParameters(from: self, parameters: CRNativeRouterParam(dict: paramDict)) 230 | } else if viewController is CRNativeRouterProtocol { 231 | (viewController as! CRNativeRouterProtocol).getParametersFromRouter(paramDict) 232 | } 233 | 234 | return viewController 235 | } 236 | 237 | /** 238 | 获取当前显示的视图控制器 239 | 240 | - returns: 当前显示的视图控制器 241 | */ 242 | public func currentViewController() -> UIViewController? { 243 | guard let rootViewController = UIApplication.shared.keyWindow?.rootViewController else { return nil } 244 | 245 | return recursionTopViewController(rootViewController) 246 | } 247 | 248 | /** 249 | 递归查找显示的视图控制器 250 | 251 | - parameter rootViewController: 开始查找的视图控制器结点 252 | 253 | - returns: 视图控制器 254 | */ 255 | private func recursionTopViewController(_ rootViewController: UIViewController) -> UIViewController { 256 | if let navigationController = rootViewController as? UINavigationController, let topViewController = navigationController.topViewController { 257 | return recursionTopViewController(topViewController) 258 | } else if let tabBarController = rootViewController as? UITabBarController { 259 | if let viewControllers = tabBarController.viewControllers { 260 | return recursionTopViewController(viewControllers[tabBarController.selectedIndex]) 261 | } else { 262 | return tabBarController 263 | } 264 | } 265 | 266 | guard let presentedViewController = rootViewController.presentedViewController else { return rootViewController } 267 | 268 | return recursionTopViewController(presentedViewController) 269 | } 270 | 271 | // MARK: API 272 | 273 | /** 274 | 设置统跳URL格式,以正则表达式表示 275 | 内部使用固定格式正则,暂不提供该接口 276 | 277 | - parameter format: URL格式(正则表达式) 278 | */ 279 | public func setURLModifyFormat(_ format: String) { 280 | regularFormat = format 281 | } 282 | 283 | /** 284 | 注册新的视图控制器 285 | 286 | - parameter name: 视图控制器名称 287 | - parameter type: 视图控制器类型 288 | - parameter parameters: 对应的参数名称列表 289 | 290 | - returns: 注册结果 291 | */ 292 | @discardableResult 293 | public func registerModule(_ name: String, type: AnyClass, parameters: [String]?) -> Bool { 294 | if type is UIViewController.Type { 295 | mapClass[name] = .normal(type: type) 296 | mapParameters[name] = parameters ?? [] 297 | 298 | return true 299 | } 300 | 301 | return false 302 | } 303 | 304 | /** 305 | 注册新的nib视图控制器 306 | 307 | - parameter name: 视图控制器名称 308 | - parameter type: 视图控制器类型 309 | - parameter nib: nib名称 310 | - parameter parameters: 对应的参数名称列表 311 | 312 | - returns: 注册结果 313 | */ 314 | @discardableResult 315 | public func registerModule(_ name: String, type: AnyClass, nib: String, parameters: [String]?) -> Bool { 316 | if type is UIViewController.Type { 317 | mapClass[name] = .nib(type: type, name: nib) 318 | mapParameters[name] = parameters ?? [] 319 | 320 | return true 321 | } 322 | 323 | return false 324 | } 325 | 326 | /** 327 | 注册新的Storyboard视图控制器 328 | 329 | - parameter name: 视图控制器名称 330 | - parameter type: 视图控制器类型 331 | - parameter storyboard: storyboard名称 332 | - parameter identifier: identifier标识 333 | - parameter parameters: 对应的参数名称列表 334 | 335 | - returns: 注册结果 336 | */ 337 | @discardableResult 338 | public func registerModule(_ name: String, type: AnyClass, storyboard: String, identifier: String, parameters: [String]?) -> Bool { 339 | if type is UIViewController.Type { 340 | mapClass[name] = .storyboard(type: type, name: storyboard, identifier: identifier) 341 | mapParameters[name] = parameters ?? [] 342 | 343 | return true 344 | } 345 | 346 | return false 347 | } 348 | 349 | /** 350 | 从plist文件注册视图控制器 351 | 352 | - parameter filename: plist文件名称 353 | */ 354 | public func registerModules(fromConfiguration configuration: String) { 355 | guard let plistPath = Bundle.main.path(forResource: configuration, ofType: "plist") else { return } 356 | 357 | var modules: [[String: Any]]? 358 | if let dict = NSDictionary(contentsOfFile: plistPath) { 359 | modules = dict["Modules"] as? [[String:Any]] 360 | } else if let array = NSArray(contentsOfFile: plistPath), let arr = array as? [[String: Any]] { 361 | modules = arr 362 | } 363 | 364 | guard let modules, !modules.isEmpty else { return } 365 | 366 | modules.forEach { module in 367 | guard let name = module["name"] as? String else { return } 368 | guard let type = module["type"] as? String else { return } 369 | 370 | guard let namespace = Bundle.main.infoDictionary!["CFBundleExecutable"] as? String else { return } 371 | guard let className = NSClassFromString(namespace + "." + type) ?? NSClassFromString(type) else { return } 372 | 373 | let parameters = module["parameters"] as? [String] 374 | 375 | if let storyboard = module["storyboard"] as? String { // storyboard 376 | guard let identifier = module["identifier"] as? String else { return } 377 | 378 | _ = registerModule(name, type: className, storyboard: storyboard, identifier: identifier, parameters: parameters) 379 | } else if let nib = module["nib"] as? String { 380 | _ = registerModule(name, type: className, nib: nib, parameters: parameters) 381 | } else { 382 | _ = registerModule(name, type: className, parameters: parameters) 383 | } 384 | } 385 | } 386 | 387 | /** 388 | 从plist总文件中获取各个分plist文件,并注册视图控制器 389 | 390 | - parameter filename: plist文件名称 391 | */ 392 | public func registerGroupModules(fromConfiguration configuration: String) { 393 | guard let plistPath = Bundle.main.path(forResource: configuration, ofType: "plist") else { return } 394 | guard let groupArray = NSArray(contentsOfFile: plistPath) as? [String] else { return } 395 | 396 | groupArray.forEach { registerModules(fromConfiguration: $0) } 397 | } 398 | 399 | /** 400 | Navigation controller show a new view controller 401 | 402 | - parameter url: URL 403 | - parameter navigationController: navigation controller 404 | - parameter delegate: navigation controller delegate 405 | */ 406 | @discardableResult 407 | private func showViewController(_ url: String, parameters: [String: Any]? = nil, pushTo navigation: UINavigationController? = nil, delegate: UINavigationControllerDelegate? = nil, type: CRNativeRouterViewPresentType = .show) -> UIViewController? { 408 | guard let viewController = configureModule(url, parameters: parameters) else { return nil } 409 | guard let navigationController = navigation ?? currentViewController()?.navigationController else { return nil } 410 | 411 | navigationController.delegate = delegate 412 | if type == .showDetail { 413 | navigationController.showDetailViewController(viewController, sender: self) 414 | } else { 415 | navigationController.show(viewController, sender: self) 416 | } 417 | 418 | return viewController 419 | } 420 | 421 | @discardableResult 422 | @objc public func present(_ url: String, parameters: [String: Any]? = nil, from current: UIViewController? = nil, inNavigation: Bool = false, params: CRNativeRouterPresentOptions = .init()) -> UIViewController? { 423 | guard let viewController = configureModule(url, parameters: parameters) else { return nil } 424 | guard let from = current ?? currentViewController() else { return nil } 425 | 426 | let newViewController = inNavigation ? (viewController.navigationController ?? UINavigationController(rootViewController: viewController)) : viewController 427 | newViewController.modalPresentationStyle = params.presentationStyle 428 | newViewController.modalTransitionStyle = params.transitionStyle 429 | from.present(newViewController, animated: true, completion: nil) 430 | 431 | return viewController 432 | } 433 | 434 | @discardableResult 435 | @objc public func popover(_ url: String, parameters: [String: Any]? = nil, from current: UIViewController? = nil, sourceRect: CGRect) -> UIViewController? { 436 | guard let viewController = configureModule(url, parameters: parameters) else { return nil } 437 | guard let from = current ?? currentViewController() else { return nil } 438 | guard let popoverController = from.popoverPresentationController else { return nil } 439 | 440 | viewController.navigationController?.modalPresentationStyle = .popover 441 | viewController.modalPresentationStyle = .popover 442 | 443 | popoverController.sourceView = from.view 444 | popoverController.sourceRect = sourceRect 445 | from.present(viewController.navigationController ?? viewController, animated: true, completion: nil) 446 | 447 | return viewController 448 | } 449 | 450 | @discardableResult 451 | @objc public func show(_ url: String, parameters: [String: Any]? = nil, navigation: UINavigationController? = nil, delegate: UINavigationControllerDelegate? = nil) -> UIViewController? { 452 | return showViewController(url, parameters: parameters, pushTo: navigation, delegate: delegate) 453 | } 454 | 455 | @discardableResult 456 | @objc public func showDetail(_ url: String, parameters: [String: Any]? = nil, navigation: UINavigationController? = nil, delegate: UINavigationControllerDelegate? = nil) -> UIViewController? { 457 | return showViewController(url, parameters: parameters, pushTo: navigation, delegate: delegate, type: .showDetail) 458 | } 459 | 460 | /** 461 | Navigation controller show a new view controller 462 | 463 | - parameter url: URL 464 | - parameter navigationController: navigation controller 465 | */ 466 | @available(iOS, deprecated: 8.0, message: "API renamed, use show instead") 467 | @discardableResult 468 | @objc public func navigationControllerShowViewController(_ url: String, navigationController: UINavigationController?, delegate: UINavigationControllerDelegate?) -> UIViewController? { 469 | return show(url, navigation: navigationController, delegate: delegate) 470 | } 471 | 472 | /** 473 | Navigation controller show a new view controller 474 | 475 | - parameter url: URL 476 | - parameter parameters: additional parameters 477 | - parameter navigationController: navigation controller 478 | */ 479 | @available(iOS, deprecated: 8.0, message: "API renamed, use show instead") 480 | @discardableResult 481 | @objc public func navigationControllerShowViewController(_ url: String, parameters: [String: Any], navigationController: UINavigationController, delegate: UINavigationControllerDelegate?) -> UIViewController? { 482 | return show(url, parameters: parameters, navigation: navigationController, delegate: delegate) 483 | } 484 | 485 | @available(iOS, deprecated: 8.0, message: "API renamed, use show instead") 486 | @discardableResult 487 | @objc public func navigationControllerShowViewController(_ url: String, parameters: [String: Any], navigationController: UINavigationController?) -> UIViewController? { 488 | return show(url, parameters: parameters, navigation: navigationController) 489 | } 490 | 491 | /** 492 | Navigation controller show a new view controller 493 | 494 | - parameter url: URL 495 | - parameter parameters: additional parameters 496 | */ 497 | @available(iOS, deprecated: 8.0, message: "API renamed, use show instead") 498 | @discardableResult 499 | @objc public func showViewController(_ url: String, parameters: [String: Any]?, delegate: UINavigationControllerDelegate?) -> UIViewController? { 500 | return show(url, parameters: parameters, delegate: delegate) 501 | } 502 | 503 | @available(iOS, deprecated: 8.0, message: "API renamed, use show instead") 504 | @discardableResult 505 | @objc public func showViewController(_ url: String, parameters: [String: Any]?) -> UIViewController? { 506 | return show(url, parameters: parameters) 507 | } 508 | 509 | /** 510 | Navigation controller show detail a new view controller 511 | 512 | - parameter url: URL 513 | - parameter navigationController: navigation controller 514 | */ 515 | @available(iOS, deprecated: 8.0, message: "API renamed, use showDetail instead") 516 | @discardableResult 517 | @objc public func navigationControllerShowDetailViewController(_ url: String, navigationController: UINavigationController?, delegate: UINavigationControllerDelegate?) -> UIViewController? { 518 | return showDetail(url, navigation: navigationController, delegate: delegate) 519 | } 520 | 521 | @available(iOS, deprecated: 8.0, message: "API renamed, use showDetail instead") 522 | @discardableResult 523 | @objc public func navigationControllerShowDetailViewController(_ url: String, navigationController: UINavigationController?) -> UIViewController? { 524 | return showDetail(url, navigation: navigationController) 525 | } 526 | 527 | /** 528 | Navigation controller show detail a new view controller 529 | 530 | - parameter url: URL 531 | - parameter parameters: additional parameters 532 | - parameter navigationController: navigation controller 533 | */ 534 | @available(iOS, deprecated: 8.0, message: "API renamed, use showDetail instead") 535 | @discardableResult 536 | @objc public func navigationControllerShowDetailViewController(_ url: String, parameters: [String: Any], navigationController: UINavigationController?, delegate: UINavigationControllerDelegate?) -> UIViewController? { 537 | return showDetail(url, parameters: parameters, navigation: navigationController, delegate: delegate) 538 | } 539 | 540 | @available(iOS, deprecated: 8.0, message: "API renamed, use showDetail instead") 541 | @discardableResult 542 | @objc public func navigationControllerShowDetailViewController(_ url: String, parameters: [String: Any], navigationController: UINavigationController?) -> UIViewController? { 543 | return showDetail(url, parameters: parameters, navigation: navigationController) 544 | } 545 | 546 | /** 547 | Navigation controller show detail a new view controller 548 | 549 | - parameter url: URL 550 | - parameter parameters: additional parameters 551 | */ 552 | @available(iOS, deprecated: 8.0, message: "API renamed, use showDetail instead") 553 | @discardableResult 554 | @objc public func showDetailViewController(_ url: String, parameters: [String: Any]?, delegate: UINavigationControllerDelegate?) -> UIViewController? { 555 | return showDetail(url, parameters: parameters, delegate: delegate) 556 | } 557 | 558 | @available(iOS, deprecated: 8.0, message: "API renamed, use showDetail instead") 559 | @discardableResult 560 | @objc public func showDetailViewController(_ url: String, parameters: [String: Any]? = nil) -> UIViewController? { 561 | return showDetail(url, parameters: parameters) 562 | } 563 | 564 | /** 565 | Show a view controller modally 566 | 567 | - parameter url: URL 568 | - parameter viewController: view controller where new one show from 569 | */ 570 | @available(iOS, deprecated: 8.0, message: "API renamed, use present instead") 571 | @discardableResult 572 | @objc public func showModallyViewController(_ url: String, fromViewController viewController: UIViewController) -> UIViewController? { 573 | return present(url, from: viewController) 574 | } 575 | 576 | 577 | /// Show a view controller within navigation modally 578 | /// 579 | /// - Parameters: 580 | /// - url: URL 581 | /// - viewController: view controller where new one show from 582 | @available(iOS, deprecated: 8.0, message: "API renamed, use present instead") 583 | @discardableResult 584 | @objc public func showModallyViewControllerInNavigation(_ url: String, fromViewController viewController: UIViewController) -> UIViewController? { 585 | return present(url, from: viewController, inNavigation: true) 586 | } 587 | 588 | /** 589 | Show a view controller modally 590 | 591 | - parameter url: URL 592 | - parameter viewController: view controller where new one show from 593 | - parameter parameters: additional parameters 594 | */ 595 | @available(iOS, deprecated: 8.0, message: "API renamed, use present instead") 596 | @discardableResult 597 | @objc public func showModallyViewController(_ url: String, fromViewController viewController: UIViewController, parameters: [String: Any]) -> UIViewController? { 598 | return present(url, parameters: parameters, from: viewController) 599 | } 600 | 601 | 602 | /// Show a view controller within navigation modally 603 | /// 604 | /// - Parameters: 605 | /// - url: URL 606 | /// - viewController: view controller where new one show from 607 | /// - parameters: additional parameters 608 | @available(iOS, deprecated: 8.0, message: "API renamed, use present instead") 609 | @discardableResult 610 | @objc public func showModallyViewControllerInNavigation(_ url: String, fromViewController viewController: UIViewController, parameters: [String: Any]) -> UIViewController? { 611 | return present(url, parameters: parameters, from: viewController, inNavigation: true) 612 | } 613 | 614 | /** 615 | Show a view controller modally 616 | 617 | - parameter url: URL 618 | - parameter parameters: additional parameters 619 | */ 620 | @available(iOS, deprecated: 8.0, message: "API renamed, use present instead") 621 | @discardableResult 622 | @objc public func showModallyViewController(_ url: String, parameters: [String: Any]? = nil) -> UIViewController? { 623 | return present(url, parameters: parameters) 624 | } 625 | 626 | /// Show a view controller within navigation modally 627 | /// 628 | /// - Parameters: 629 | /// - url: URL 630 | /// - parameters: additional parameters 631 | @available(iOS, deprecated: 8.0, message: "API renamed, use present instead") 632 | @discardableResult 633 | @objc public func showModallyViewControllerInNavigation(_ url: String, parameters: [String: Any]? = nil) -> UIViewController? { 634 | return present(url, parameters: parameters, inNavigation: true) 635 | } 636 | 637 | /** 638 | Pop over a new view controller 639 | 640 | - parameter url: URL 641 | - parameter viewController: view controller where new one show from 642 | - parameter sourceRect: source area rect 643 | */ 644 | @available(iOS, deprecated: 8.0, message: "API renamed, use popover instead") 645 | @discardableResult 646 | @objc public func popoverViewController(_ url: String, fromViewController viewController: UIViewController, sourceRect: CGRect) -> UIViewController? { 647 | return popover(url, from: viewController, sourceRect: sourceRect) 648 | } 649 | 650 | /** 651 | Pop over a new view controller 652 | 653 | - parameter url: URL 654 | - parameter viewController: view controller where new one show from 655 | - parameter parameters: additional parameters 656 | - parameter sourceRect: source area rect 657 | */ 658 | @available(iOS, deprecated: 8.0, message: "API renamed, use popover instead") 659 | @discardableResult 660 | @objc public func popoverViewController(_ url: String, fromViewController viewController: UIViewController, parameters: [String: Any], sourceRect: CGRect) -> UIViewController? { 661 | return popover(url, parameters: parameters, from: viewController, sourceRect: sourceRect) 662 | } 663 | 664 | /** 665 | Pop over a new view controller 666 | 667 | - parameter url: URL 668 | - parameter sourceRect: source area rect 669 | - parameter parameters: additional parameters 670 | */ 671 | @available(iOS, deprecated: 8.0, message: "API renamed, use popover instead") 672 | @discardableResult 673 | @objc public func popoverViewController(_ url: String, sourceRect: CGRect, parameters: [String: Any]? = nil) -> UIViewController? { 674 | return popover(url, parameters: parameters, sourceRect: sourceRect) 675 | } 676 | } 677 | -------------------------------------------------------------------------------- /CRNativeRouterDemo/CRNativeRouter/CRNativeRouter.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CRNativeRouter.swift 3 | // CRNativeRouter 4 | // 5 | // Created by CrashRain on 16/7/1. 6 | // Copyright © 2016年 CrashRain. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | private func ~= (lhs: String, rhs: String) -> Bool { 12 | if let result = ((try? NSRegularExpression(pattern: rhs, options: .caseInsensitive).firstMatch(in: lhs, options: NSRegularExpression.MatchingOptions(rawValue: 0), range: NSRange(location: 0, length: lhs.count))) as NSTextCheckingResult??) { 13 | return result != nil 14 | } 15 | 16 | return false 17 | } 18 | 19 | @objc public class CRNativeRouterPresentOptions: NSObject { 20 | var presentationStyle = UIModalPresentationStyle.overCurrentContext 21 | var transitionStyle = UIModalTransitionStyle.coverVertical 22 | } 23 | 24 | @dynamicMemberLookup 25 | public struct CRNativeRouterParamT where Key: Hashable & ExpressibleByStringLiteral { 26 | var wrappedValue: [Key: Value] = [:] 27 | 28 | init(dict: [Key: Value]) { 29 | wrappedValue = dict 30 | } 31 | 32 | subscript(key: Key) -> Value? { 33 | get { 34 | return wrappedValue[key] 35 | } 36 | set { 37 | wrappedValue[key] = newValue 38 | } 39 | } 40 | 41 | subscript(dynamicMember key: Key) -> Value? { 42 | return wrappedValue[key] 43 | } 44 | } 45 | 46 | public typealias CRNativeRouterParam = CRNativeRouterParamT 47 | 48 | public protocol CRNativeRouterProtocol: NSObjectProtocol { 49 | func getParametersFromRouter(_ parameter: [String: Any]) 50 | } 51 | 52 | public protocol CRNativeRouterDelegate { 53 | func getParameters(from router: CRNativeRouter, parameters: CRNativeRouterParam) 54 | } 55 | 56 | public class CRNativeRouter: NSObject { 57 | 58 | // 视图控制器类型枚举 59 | private enum CRNativeRouterViewControllerType { 60 | case normal(type: AnyClass) 61 | case nib(type: AnyClass, name: String) 62 | case storyboard(type: AnyClass, name: String, identifier: String) 63 | } 64 | 65 | // 视图显示方式 66 | private enum CRNativeRouterViewPresentType { 67 | case show 68 | case showDetail 69 | case presentModally 70 | case presentAsPopover 71 | } 72 | 73 | private enum CRNativeRouterKey: String { 74 | case module = "CRNativeRouterModuleKey" 75 | case parameters = "CRNativeRouterParametersKey" 76 | } 77 | 78 | // 映射关系 79 | private var mapClass: [String: CRNativeRouterViewControllerType] = [:] 80 | private var mapParameters: [String: [String]] = [:] 81 | 82 | // 预设的URL匹配正则表达式 83 | private var regularFormat = "^(Module://)(\\w+\\.md)(\\?(([a-zA-Z]+\\w*=\\w+)(&[a-zA-Z]+\\w*=\\w+)*)|([a-zA-Z]+\\w*=\\w+))?$" 84 | 85 | // 单例 86 | public static let shared = CRNativeRouter() 87 | 88 | @available(iOS, deprecated, message: "Use shared instead") 89 | @objc public class func sharedInstance() -> CRNativeRouter! { 90 | return shared 91 | } 92 | 93 | /** 94 | 分离URL中的模块名称和参数队列 95 | 96 | - parameter url: URL 97 | 98 | - returns: 分离后的字典数据 99 | */ 100 | private func divideComponentsFromUrl(_ url: String) -> [CRNativeRouterKey: String] { 101 | var compResult: [CRNativeRouterKey: String] = [:] 102 | 103 | do { 104 | // 分离模块名称 105 | var regularExpression = try NSRegularExpression(pattern: "://\\w+\\.md", options: []) 106 | var components = regularExpression.matches(in: url, options: .reportCompletion, range: NSMakeRange(0, url.count)) 107 | 108 | if components.count > 0 { 109 | let tempRange = components[0].range 110 | compResult[.module] = String(url[url.index(url.startIndex, offsetBy: tempRange.location + 3) ..< url.index(url.startIndex, offsetBy: tempRange.location + tempRange.length)]) 111 | } 112 | 113 | // 分离参数 114 | regularExpression = try NSRegularExpression(pattern: "\\?[\\w|&|=]*$", options: []) 115 | components = regularExpression.matches(in: url, options: .reportCompletion, range: NSMakeRange(0, url.count)) 116 | 117 | if components.count > 0 { 118 | let tempRange = components[0].range 119 | compResult[.parameters] = String(url[url.index(url.startIndex, offsetBy: tempRange.location + 1) ..< url.index(url.startIndex, offsetBy: tempRange.location + tempRange.length)]) 120 | } 121 | } catch { 122 | // exception catched 123 | } 124 | 125 | return compResult 126 | } 127 | 128 | /** 129 | 通过module名称映射到对应的视图控制器 130 | 131 | - parameter module: module名称 132 | 133 | - returns: 对应的视图控制器 134 | */ 135 | private func reflectViewController(_ module: String) -> UIViewController? { 136 | guard let type = mapClass[module] else { return nil } 137 | 138 | var viewController: UIViewController? = nil 139 | 140 | switch type { 141 | case .normal(let vcType): 142 | viewController = (vcType as! UIViewController.Type).init() 143 | case .nib(let vcType, let nib): 144 | viewController = (vcType as! UIViewController.Type).init(nibName: nib, bundle: nil) 145 | case .storyboard(_, let name, let identifier): 146 | viewController = UIStoryboard(name: name, bundle: nil).instantiateViewController(withIdentifier: identifier) 147 | } 148 | 149 | return viewController 150 | } 151 | 152 | /** 153 | 视图控制器参数校验 154 | 155 | - parameter module: module名称 156 | - parameter parameter: URL中的参数队列字符串 157 | 158 | - returns: 校验结果 159 | */ 160 | private func viewControllerParametersCheck(_ module: String, parameter: String, paramDict: [String: Any]? = nil) -> Bool { 161 | guard let requiredList = mapParameters[module], requiredList.count > 0 else { return true } 162 | 163 | let components = parameter.components(separatedBy: "&") 164 | var params = Set() 165 | 166 | components.filter { $0.count > 0 }.forEach { params.insert($0.components(separatedBy: "=")[0]) } 167 | 168 | if let additionalParams = paramDict { 169 | params.formUnion(Set(additionalParams.keys)) 170 | } 171 | 172 | return Set(requiredList).intersection(params).count == requiredList.count 173 | } 174 | 175 | /** 176 | 生成视图控制器参数对应的字典数据 177 | 178 | - parameter parameter: 参数队列字符串 179 | 180 | - returns: 参数字典数据 181 | */ 182 | private func viewControllerParameterGenerate(_ parameter: String, paramDict: [String: Any]? = nil) -> [String: Any] { 183 | guard parameter != "" else { return paramDict ?? [:] } 184 | 185 | let components = parameter.components(separatedBy: "&") 186 | var params: [String: Any] = [:] 187 | 188 | components.forEach { item in 189 | let refs = item.components(separatedBy: "=") 190 | 191 | if let intValue = Int(refs[1]), "\(intValue)" == refs[1] { 192 | params[refs[0]] = intValue 193 | } else if let doubleValue = Double(refs[1]), "\(doubleValue)" == refs[1] { 194 | params[refs[0]] = doubleValue 195 | } else { 196 | params[refs[0]] = refs[1] 197 | } 198 | } 199 | 200 | if let additionalParam = paramDict { 201 | params.merge(additionalParam) { (_, new) -> Any in new } 202 | } 203 | 204 | return params 205 | } 206 | 207 | /** 208 | 返回URL对应的视图控制器,并且完成对应的参数初始化 209 | 210 | - parameter url: URL 211 | 212 | - returns: 视图控制器 213 | */ 214 | private func configureModule(_ url: String, parameters: [String: Any]? = nil) -> UIViewController? { 215 | guard url ~= regularFormat else { return nil } 216 | 217 | let components = divideComponentsFromUrl(url) 218 | guard let module = components[.module] else { return nil } 219 | let parameterStr = components[.parameters] ?? "" 220 | 221 | guard viewControllerParametersCheck(module, parameter: parameterStr, paramDict: parameters), let viewController = reflectViewController(module) else { return nil } 222 | 223 | if let p = mapParameters[module], p.count > 0 && !(viewController is CRNativeRouterProtocol || viewController is CRNativeRouterDelegate) { 224 | return nil 225 | } 226 | 227 | let paramDict = viewControllerParameterGenerate(parameterStr, paramDict: parameters) 228 | if viewController is CRNativeRouterDelegate { 229 | (viewController as! CRNativeRouterDelegate).getParameters(from: self, parameters: CRNativeRouterParam(dict: paramDict)) 230 | } else if viewController is CRNativeRouterProtocol { 231 | (viewController as! CRNativeRouterProtocol).getParametersFromRouter(paramDict) 232 | } 233 | 234 | return viewController 235 | } 236 | 237 | /** 238 | 获取当前显示的视图控制器 239 | 240 | - returns: 当前显示的视图控制器 241 | */ 242 | public func currentViewController() -> UIViewController? { 243 | guard let rootViewController = UIApplication.shared.keyWindow?.rootViewController else { return nil } 244 | 245 | return recursionTopViewController(rootViewController) 246 | } 247 | 248 | /** 249 | 递归查找显示的视图控制器 250 | 251 | - parameter rootViewController: 开始查找的视图控制器结点 252 | 253 | - returns: 视图控制器 254 | */ 255 | private func recursionTopViewController(_ rootViewController: UIViewController) -> UIViewController { 256 | if let navigationController = rootViewController as? UINavigationController, let topViewController = navigationController.topViewController { 257 | return recursionTopViewController(topViewController) 258 | } else if let tabBarController = rootViewController as? UITabBarController { 259 | if let viewControllers = tabBarController.viewControllers { 260 | return recursionTopViewController(viewControllers[tabBarController.selectedIndex]) 261 | } else { 262 | return tabBarController 263 | } 264 | } 265 | 266 | guard let presentedViewController = rootViewController.presentedViewController else { return rootViewController } 267 | 268 | return recursionTopViewController(presentedViewController) 269 | } 270 | 271 | // MARK: API 272 | 273 | /** 274 | 设置统跳URL格式,以正则表达式表示 275 | 内部使用固定格式正则,暂不提供该接口 276 | 277 | - parameter format: URL格式(正则表达式) 278 | */ 279 | public func setURLModifyFormat(_ format: String) { 280 | regularFormat = format 281 | } 282 | 283 | /** 284 | 注册新的视图控制器 285 | 286 | - parameter name: 视图控制器名称 287 | - parameter type: 视图控制器类型 288 | - parameter parameters: 对应的参数名称列表 289 | 290 | - returns: 注册结果 291 | */ 292 | @discardableResult 293 | public func registerModule(_ name: String, type: AnyClass, parameters: [String]?) -> Bool { 294 | if type is UIViewController.Type { 295 | mapClass[name] = .normal(type: type) 296 | mapParameters[name] = parameters ?? [] 297 | 298 | return true 299 | } 300 | 301 | return false 302 | } 303 | 304 | /** 305 | 注册新的nib视图控制器 306 | 307 | - parameter name: 视图控制器名称 308 | - parameter type: 视图控制器类型 309 | - parameter nib: nib名称 310 | - parameter parameters: 对应的参数名称列表 311 | 312 | - returns: 注册结果 313 | */ 314 | @discardableResult 315 | public func registerModule(_ name: String, type: AnyClass, nib: String, parameters: [String]?) -> Bool { 316 | if type is UIViewController.Type { 317 | mapClass[name] = .nib(type: type, name: nib) 318 | mapParameters[name] = parameters ?? [] 319 | 320 | return true 321 | } 322 | 323 | return false 324 | } 325 | 326 | /** 327 | 注册新的Storyboard视图控制器 328 | 329 | - parameter name: 视图控制器名称 330 | - parameter type: 视图控制器类型 331 | - parameter storyboard: storyboard名称 332 | - parameter identifier: identifier标识 333 | - parameter parameters: 对应的参数名称列表 334 | 335 | - returns: 注册结果 336 | */ 337 | @discardableResult 338 | public func registerModule(_ name: String, type: AnyClass, storyboard: String, identifier: String, parameters: [String]?) -> Bool { 339 | if type is UIViewController.Type { 340 | mapClass[name] = .storyboard(type: type, name: storyboard, identifier: identifier) 341 | mapParameters[name] = parameters ?? [] 342 | 343 | return true 344 | } 345 | 346 | return false 347 | } 348 | 349 | /** 350 | 从plist文件注册视图控制器 351 | 352 | - parameter filename: plist文件名称 353 | */ 354 | public func registerModules(fromConfiguration configuration: String) { 355 | guard let plistPath = Bundle.main.path(forResource: configuration, ofType: "plist") else { return } 356 | 357 | var modules: [[String: Any]]? 358 | if let dict = NSDictionary(contentsOfFile: plistPath) { 359 | modules = dict["Modules"] as? [[String:Any]] 360 | } else if let array = NSArray(contentsOfFile: plistPath), let arr = array as? [[String: Any]] { 361 | modules = arr 362 | } 363 | 364 | guard let modules, !modules.isEmpty else { return } 365 | 366 | modules.forEach { module in 367 | guard let name = module["name"] as? String else { return } 368 | guard let type = module["type"] as? String else { return } 369 | 370 | guard let namespace = Bundle.main.infoDictionary!["CFBundleExecutable"] as? String else { return } 371 | guard let className = NSClassFromString(namespace + "." + type) ?? NSClassFromString(type) else { return } 372 | 373 | let parameters = module["parameters"] as? [String] 374 | 375 | if let storyboard = module["storyboard"] as? String { // storyboard 376 | guard let identifier = module["identifier"] as? String else { return } 377 | 378 | _ = registerModule(name, type: className, storyboard: storyboard, identifier: identifier, parameters: parameters) 379 | } else if let nib = module["nib"] as? String { 380 | _ = registerModule(name, type: className, nib: nib, parameters: parameters) 381 | } else { 382 | _ = registerModule(name, type: className, parameters: parameters) 383 | } 384 | } 385 | } 386 | 387 | /** 388 | 从plist总文件中获取各个分plist文件,并注册视图控制器 389 | 390 | - parameter filename: plist文件名称 391 | */ 392 | public func registerGroupModules(fromConfiguration configuration: String) { 393 | guard let plistPath = Bundle.main.path(forResource: configuration, ofType: "plist") else { return } 394 | guard let groupArray = NSArray(contentsOfFile: plistPath) as? [String] else { return } 395 | 396 | groupArray.forEach { registerModules(fromConfiguration: $0) } 397 | } 398 | 399 | /** 400 | Navigation controller show a new view controller 401 | 402 | - parameter url: URL 403 | - parameter navigationController: navigation controller 404 | - parameter delegate: navigation controller delegate 405 | */ 406 | @discardableResult 407 | private func showViewController(_ url: String, parameters: [String: Any]? = nil, pushTo navigation: UINavigationController? = nil, delegate: UINavigationControllerDelegate? = nil, type: CRNativeRouterViewPresentType = .show) -> UIViewController? { 408 | guard let viewController = configureModule(url, parameters: parameters) else { return nil } 409 | guard let navigationController = navigation ?? currentViewController()?.navigationController else { return nil } 410 | 411 | navigationController.delegate = delegate 412 | if type == .showDetail { 413 | navigationController.showDetailViewController(viewController, sender: self) 414 | } else { 415 | navigationController.show(viewController, sender: self) 416 | } 417 | 418 | return viewController 419 | } 420 | 421 | @discardableResult 422 | @objc public func present(_ url: String, parameters: [String: Any]? = nil, from current: UIViewController? = nil, inNavigation: Bool = false, params: CRNativeRouterPresentOptions = .init()) -> UIViewController? { 423 | guard let viewController = configureModule(url, parameters: parameters) else { return nil } 424 | guard let from = current ?? currentViewController() else { return nil } 425 | 426 | let newViewController = inNavigation ? (viewController.navigationController ?? UINavigationController(rootViewController: viewController)) : viewController 427 | newViewController.modalPresentationStyle = params.presentationStyle 428 | newViewController.modalTransitionStyle = params.transitionStyle 429 | from.present(newViewController, animated: true, completion: nil) 430 | 431 | return viewController 432 | } 433 | 434 | @discardableResult 435 | @objc public func popover(_ url: String, parameters: [String: Any]? = nil, from current: UIViewController? = nil, sourceRect: CGRect) -> UIViewController? { 436 | guard let viewController = configureModule(url, parameters: parameters) else { return nil } 437 | guard let from = current ?? currentViewController() else { return nil } 438 | guard let popoverController = from.popoverPresentationController else { return nil } 439 | 440 | viewController.navigationController?.modalPresentationStyle = .popover 441 | viewController.modalPresentationStyle = .popover 442 | 443 | popoverController.sourceView = from.view 444 | popoverController.sourceRect = sourceRect 445 | from.present(viewController.navigationController ?? viewController, animated: true, completion: nil) 446 | 447 | return viewController 448 | } 449 | 450 | @discardableResult 451 | @objc public func show(_ url: String, parameters: [String: Any]? = nil, navigation: UINavigationController? = nil, delegate: UINavigationControllerDelegate? = nil) -> UIViewController? { 452 | return showViewController(url, parameters: parameters, pushTo: navigation, delegate: delegate) 453 | } 454 | 455 | @discardableResult 456 | @objc public func showDetail(_ url: String, parameters: [String: Any]? = nil, navigation: UINavigationController? = nil, delegate: UINavigationControllerDelegate? = nil) -> UIViewController? { 457 | return showViewController(url, parameters: parameters, pushTo: navigation, delegate: delegate, type: .showDetail) 458 | } 459 | 460 | /** 461 | Navigation controller show a new view controller 462 | 463 | - parameter url: URL 464 | - parameter navigationController: navigation controller 465 | */ 466 | @available(iOS, deprecated: 8.0, message: "API renamed, use show instead") 467 | @discardableResult 468 | @objc public func navigationControllerShowViewController(_ url: String, navigationController: UINavigationController?, delegate: UINavigationControllerDelegate?) -> UIViewController? { 469 | return show(url, navigation: navigationController, delegate: delegate) 470 | } 471 | 472 | /** 473 | Navigation controller show a new view controller 474 | 475 | - parameter url: URL 476 | - parameter parameters: additional parameters 477 | - parameter navigationController: navigation controller 478 | */ 479 | @available(iOS, deprecated: 8.0, message: "API renamed, use show instead") 480 | @discardableResult 481 | @objc public func navigationControllerShowViewController(_ url: String, parameters: [String: Any], navigationController: UINavigationController, delegate: UINavigationControllerDelegate?) -> UIViewController? { 482 | return show(url, parameters: parameters, navigation: navigationController, delegate: delegate) 483 | } 484 | 485 | @available(iOS, deprecated: 8.0, message: "API renamed, use show instead") 486 | @discardableResult 487 | @objc public func navigationControllerShowViewController(_ url: String, parameters: [String: Any], navigationController: UINavigationController?) -> UIViewController? { 488 | return show(url, parameters: parameters, navigation: navigationController) 489 | } 490 | 491 | /** 492 | Navigation controller show a new view controller 493 | 494 | - parameter url: URL 495 | - parameter parameters: additional parameters 496 | */ 497 | @available(iOS, deprecated: 8.0, message: "API renamed, use show instead") 498 | @discardableResult 499 | @objc public func showViewController(_ url: String, parameters: [String: Any]?, delegate: UINavigationControllerDelegate?) -> UIViewController? { 500 | return show(url, parameters: parameters, delegate: delegate) 501 | } 502 | 503 | @available(iOS, deprecated: 8.0, message: "API renamed, use show instead") 504 | @discardableResult 505 | @objc public func showViewController(_ url: String, parameters: [String: Any]?) -> UIViewController? { 506 | return show(url, parameters: parameters) 507 | } 508 | 509 | /** 510 | Navigation controller show detail a new view controller 511 | 512 | - parameter url: URL 513 | - parameter navigationController: navigation controller 514 | */ 515 | @available(iOS, deprecated: 8.0, message: "API renamed, use showDetail instead") 516 | @discardableResult 517 | @objc public func navigationControllerShowDetailViewController(_ url: String, navigationController: UINavigationController?, delegate: UINavigationControllerDelegate?) -> UIViewController? { 518 | return showDetail(url, navigation: navigationController, delegate: delegate) 519 | } 520 | 521 | @available(iOS, deprecated: 8.0, message: "API renamed, use showDetail instead") 522 | @discardableResult 523 | @objc public func navigationControllerShowDetailViewController(_ url: String, navigationController: UINavigationController?) -> UIViewController? { 524 | return showDetail(url, navigation: navigationController) 525 | } 526 | 527 | /** 528 | Navigation controller show detail a new view controller 529 | 530 | - parameter url: URL 531 | - parameter parameters: additional parameters 532 | - parameter navigationController: navigation controller 533 | */ 534 | @available(iOS, deprecated: 8.0, message: "API renamed, use showDetail instead") 535 | @discardableResult 536 | @objc public func navigationControllerShowDetailViewController(_ url: String, parameters: [String: Any], navigationController: UINavigationController?, delegate: UINavigationControllerDelegate?) -> UIViewController? { 537 | return showDetail(url, parameters: parameters, navigation: navigationController, delegate: delegate) 538 | } 539 | 540 | @available(iOS, deprecated: 8.0, message: "API renamed, use showDetail instead") 541 | @discardableResult 542 | @objc public func navigationControllerShowDetailViewController(_ url: String, parameters: [String: Any], navigationController: UINavigationController?) -> UIViewController? { 543 | return showDetail(url, parameters: parameters, navigation: navigationController) 544 | } 545 | 546 | /** 547 | Navigation controller show detail a new view controller 548 | 549 | - parameter url: URL 550 | - parameter parameters: additional parameters 551 | */ 552 | @available(iOS, deprecated: 8.0, message: "API renamed, use showDetail instead") 553 | @discardableResult 554 | @objc public func showDetailViewController(_ url: String, parameters: [String: Any]?, delegate: UINavigationControllerDelegate?) -> UIViewController? { 555 | return showDetail(url, parameters: parameters, delegate: delegate) 556 | } 557 | 558 | @available(iOS, deprecated: 8.0, message: "API renamed, use showDetail instead") 559 | @discardableResult 560 | @objc public func showDetailViewController(_ url: String, parameters: [String: Any]? = nil) -> UIViewController? { 561 | return showDetail(url, parameters: parameters) 562 | } 563 | 564 | /** 565 | Show a view controller modally 566 | 567 | - parameter url: URL 568 | - parameter viewController: view controller where new one show from 569 | */ 570 | @available(iOS, deprecated: 8.0, message: "API renamed, use present instead") 571 | @discardableResult 572 | @objc public func showModallyViewController(_ url: String, fromViewController viewController: UIViewController) -> UIViewController? { 573 | return present(url, from: viewController) 574 | } 575 | 576 | 577 | /// Show a view controller within navigation modally 578 | /// 579 | /// - Parameters: 580 | /// - url: URL 581 | /// - viewController: view controller where new one show from 582 | @available(iOS, deprecated: 8.0, message: "API renamed, use present instead") 583 | @discardableResult 584 | @objc public func showModallyViewControllerInNavigation(_ url: String, fromViewController viewController: UIViewController) -> UIViewController? { 585 | return present(url, from: viewController, inNavigation: true) 586 | } 587 | 588 | /** 589 | Show a view controller modally 590 | 591 | - parameter url: URL 592 | - parameter viewController: view controller where new one show from 593 | - parameter parameters: additional parameters 594 | */ 595 | @available(iOS, deprecated: 8.0, message: "API renamed, use present instead") 596 | @discardableResult 597 | @objc public func showModallyViewController(_ url: String, fromViewController viewController: UIViewController, parameters: [String: Any]) -> UIViewController? { 598 | return present(url, parameters: parameters, from: viewController) 599 | } 600 | 601 | 602 | /// Show a view controller within navigation modally 603 | /// 604 | /// - Parameters: 605 | /// - url: URL 606 | /// - viewController: view controller where new one show from 607 | /// - parameters: additional parameters 608 | @available(iOS, deprecated: 8.0, message: "API renamed, use present instead") 609 | @discardableResult 610 | @objc public func showModallyViewControllerInNavigation(_ url: String, fromViewController viewController: UIViewController, parameters: [String: Any]) -> UIViewController? { 611 | return present(url, parameters: parameters, from: viewController, inNavigation: true) 612 | } 613 | 614 | /** 615 | Show a view controller modally 616 | 617 | - parameter url: URL 618 | - parameter parameters: additional parameters 619 | */ 620 | @available(iOS, deprecated: 8.0, message: "API renamed, use present instead") 621 | @discardableResult 622 | @objc public func showModallyViewController(_ url: String, parameters: [String: Any]? = nil) -> UIViewController? { 623 | return present(url, parameters: parameters) 624 | } 625 | 626 | /// Show a view controller within navigation modally 627 | /// 628 | /// - Parameters: 629 | /// - url: URL 630 | /// - parameters: additional parameters 631 | @available(iOS, deprecated: 8.0, message: "API renamed, use present instead") 632 | @discardableResult 633 | @objc public func showModallyViewControllerInNavigation(_ url: String, parameters: [String: Any]? = nil) -> UIViewController? { 634 | return present(url, parameters: parameters, inNavigation: true) 635 | } 636 | 637 | /** 638 | Pop over a new view controller 639 | 640 | - parameter url: URL 641 | - parameter viewController: view controller where new one show from 642 | - parameter sourceRect: source area rect 643 | */ 644 | @available(iOS, deprecated: 8.0, message: "API renamed, use popover instead") 645 | @discardableResult 646 | @objc public func popoverViewController(_ url: String, fromViewController viewController: UIViewController, sourceRect: CGRect) -> UIViewController? { 647 | return popover(url, from: viewController, sourceRect: sourceRect) 648 | } 649 | 650 | /** 651 | Pop over a new view controller 652 | 653 | - parameter url: URL 654 | - parameter viewController: view controller where new one show from 655 | - parameter parameters: additional parameters 656 | - parameter sourceRect: source area rect 657 | */ 658 | @available(iOS, deprecated: 8.0, message: "API renamed, use popover instead") 659 | @discardableResult 660 | @objc public func popoverViewController(_ url: String, fromViewController viewController: UIViewController, parameters: [String: Any], sourceRect: CGRect) -> UIViewController? { 661 | return popover(url, parameters: parameters, from: viewController, sourceRect: sourceRect) 662 | } 663 | 664 | /** 665 | Pop over a new view controller 666 | 667 | - parameter url: URL 668 | - parameter sourceRect: source area rect 669 | - parameter parameters: additional parameters 670 | */ 671 | @available(iOS, deprecated: 8.0, message: "API renamed, use popover instead") 672 | @discardableResult 673 | @objc public func popoverViewController(_ url: String, sourceRect: CGRect, parameters: [String: Any]? = nil) -> UIViewController? { 674 | return popover(url, parameters: parameters, sourceRect: sourceRect) 675 | } 676 | } 677 | -------------------------------------------------------------------------------- /CRNativeRouterDemo/CRNativeRouter/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 30 | 37 | 44 | 51 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 103 | 109 | 115 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 169 | 175 | 181 | 187 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 247 | 253 | 259 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 326 | 332 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 382 | --------------------------------------------------------------------------------