今天搞通了swift页面跳转。如果对比前端或者说web应用,其实就是“路由”,像react应用,可以通过react-router来管理路由,vue可以通过vue-router来管理路由类似,swift中可以用UINavigationController
来管理“路由”,这里应该叫“导航”吧。iOS中有两种不同形式的跳转,一种是有逻辑层级关系的跳转,一种是临时页面的跳转。
1.临时的页面跳转
我觉得可以看作是一个弹出页,比如点击一个表单控件,可以弹出一个相应的编辑页。这个是属于该页面的行为,所以不需要全局使用,视图自带。
跳转到下一页,可以用self.present(anotherView, animated: true, completion: nil)
,这样页面会从底部弹出。收回页面,调用self.dismiss(animated: true, completion: nil)
。这里灰常简单,就认识俩方法而已。
ViewController.swift
import UIKit class ViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() initBtn() } //初始化按钮,点击按钮跳转页面 func initBtn() { let screenSize = UIScreen.main.bounds.size let jumpBtn = UIButton(type: .system) jumpBtn.setTitle("跳转", for: .normal) jumpBtn.frame = CGRect(x: screenSize.width / 2 - 50, y: screenSize.height - 50, width: 100, height: 30) jumpBtn.backgroundColor = .blue jumpBtn.setTitleColor(UIColor.white, for: .normal) //按钮绑定事件,点击时执行 jumpBtn.addTarget(self, action: #selector(pageJump), for: .touchDown) self.view.addSubview(jumpBtn) } @objc func pageJump() { print("main to dest") //创建一个页面 let destination = DestinationViewController() //取目标页面的一个变量进行赋值,以属性的方式进行传值。 destination.message = "传递的信息" //跳转 self.present(destination, animated: true, completion: nil) } }
DestinationViewContrller.swift
import UIKit class DestinationViewController: UIViewController { var message: String? override func viewDidLoad() { super.viewDidLoad() self.view.backgroundColor = UIColor.white initBtn() print(message!) } //初始化返回按钮,点击按钮返回主页面。 func initBtn() { let screenSize = UIScreen.main.bounds.size let jumpBtn = UIButton(type: .system) jumpBtn.setTitle("返回", for: .normal) jumpBtn.frame = CGRect(x: screenSize.width / 2 - 50, y: screenSize.height - 100, width: 100, height: 30) jumpBtn.backgroundColor = .red jumpBtn.setTitleColor(UIColor.white, for: .normal) //按钮绑定事件 jumpBtn.addTarget(self, action: #selector(pageReturn), for: .touchDown) self.view.addSubview(jumpBtn) } @objc func pageReturn() { print("dest to main") //返回主页面 self.dismiss(animated: true, completion: nil) } }
2.具有逻辑层次的页面跳转
比如网上购物,下单之后要跳到订单页面,再跳到支付页面等等,这种跳转是有一定逻辑,或者说先后顺序的。这种跳转需要用NavigationViewController
进行跳转。
还是上面那个例子:
import UIKit class ViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() initBtn() } //初始化按钮,点击按钮跳转页面 func initBtn() { let screenSize = UIScreen.main.bounds.size let jumpBtn = UIButton(type: .system) jumpBtn.setTitle("跳转", for: .normal) jumpBtn.frame = CGRect(x: screenSize.width / 2 - 50, y: screenSize.height - 50, width: 100, height: 30) jumpBtn.backgroundColor = .blue jumpBtn.setTitleColor(UIColor.white, for: .normal) //按钮绑定事件,点击时执行 jumpBtn.addTarget(self, action: #selector(pageJump), for: .touchDown) self.view.addSubview(jumpBtn) } @objc func pageJump() { print("main to dest") //创建一个页面 let destination = DestinationViewController() //取目标页面的一个变量进行赋值,以属性的方式进行传值。 destination.message = "传递的信息" //跳转 self.navigationController?.pushViewController(destination, animated: true) } }
DestinationViewController.swift
import UIKit class DestinationViewController: UIViewController { var message: String? override func viewDidLoad() { super.viewDidLoad() self.view.backgroundColor = UIColor.white initBtn() print(message!) } //初始化返回按钮,点击按钮返回主页面。 func initBtn() { let screenSize = UIScreen.main.bounds.size let jumpBtn = UIButton(type: .system) jumpBtn.setTitle("返回", for: .normal) jumpBtn.frame = CGRect(x: screenSize.width / 2 - 50, y: screenSize.height - 50, width: 100, height: 30) jumpBtn.backgroundColor = .red jumpBtn.setTitleColor(UIColor.white, for: .normal) //按钮绑定事件 jumpBtn.addTarget(self, action: #selector(pageReturn), for: .touchDown) self.view.addSubview(jumpBtn) } @objc func pageReturn() { print("dest to main") //返回主页面 self.navigationController?.popViewController(animated: true) } }
但是如果这时编译页面,在模拟器中点击Next按钮,发现并没有任何事情发生😅这块找了半天才找到解决办法,最后发现,是没有实例化UINavigationController
,其实看到navigationController
是一个可选类型就可以猜到一二。
接下来,该在什么地方实例化这个UINavigationController
呢?如果你直接在页面实例化,发现一样不管用。类比一下react里的路由(react-router 4.0之前的版本),就会发现,导航其实是一个全局的东西,来管理所有的界面堆栈,而不是存在于某一个页面内。如果放入某一个页面内,每次加载都是实例化的一个新UINavigationController
,它在其他页面是没法被拿到的。如这个例子中,如果在第一页实例化一个导航控制器,在第二页中是拿不到的。所以,要解决这个问题,就要实例化一个全局的UINavigationController
。
我们可以在AppDelegate.swift
中实例化这个全局导航器。如果有SceneDelegate.swift
中实例化。
class SceneDelegate: UIResponder, UIWindowSceneDelegate { var window: UIWindow? func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`. // If using a storyboard, the `window` property will automatically be initialized and attached to the scene. // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead). guard let _ = (scene as? UIWindowScene) else { return } let vc = ViewController() let navRoot = UINavigationController(rootViewController: vc) self.window?.rootViewController = navRoot self.window?.backgroundColor = UIColor.white } ... ... }
即通过给window的rootViewController赋值一个UINavigationController
实例,就可以在所有页面使用navigationController
了。这里其实是重写了应用启动后的默认行为,所以需要在实例化UINavigationController
时指定一下根试图。然后给个背景色,否则是黑的😓。这样就可以了,其他页面直接使用pushViewController
和popViewController
就可以了。
还有两个有用的跳转方法
// 第三个按钮绑定的方法,根据全局序号,查找堆栈中指定序号的视图控制器 @objc func gotoIndexPage(){ let viewController = self.navigationController?.viewControllers[1] self.navigationController?.popToViewController(viewController!, animated: true) } // 创建第四个按钮绑定的方法,所有子视图出栈 @objc func gotoRootPage(){ self.navigationController?.popToRootViewController(animated: true) }
3. segue
使用segue进行跳转第一步必须在storyboard中创建segue,暂不讨论。