Swift 不同Controller互動 與 Navigation Bar
Contents
Controller之間的呼叫
present 與 dismiss
想要跳轉頁面,我們可以靠storyBoard拉出present的線,也可以靠程式碼。
在StoryBoard中只要按住ctrl,將button拉到另一個controller後再選present即可。
在程式碼的話
// 因為此ViewController什麼都沒設定,所以會是一片黑
present(ViewController(), animated: true, completion: nil)
有跳過去,就有返回,想返回時使用dismiss function。
dismiss(animated: true, completion: nil)
dismiss的原理是,直接呼叫時,請求presentingViewController把你關掉。
間接呼叫時,把後面的presentedViewController們關掉。
至於這兩個是什麼意思請參看下節。
presentingViewController 與 presentedViewController
每個controller皆有兩個重要的屬性,可以分辨這個controller是被誰present,或是present了誰。
- presentingViewController:present Controller的Controller
- presentedViewController:被present的Controller
假設ABC皆為Controller,A present B,B present C,那對於B來說。
- A為B之presentingViewController
- C為B之presentedViewController
Navigation bar
push 與 pop
與present / dismiss相似,進到navigation controller後要使用 navigation屬性的push/pop方法,不然會跳出navigation controller的頁,失去Navigation Bar的效果。
程式碼的形式如下
// 推上下一個頁面
self.navigationController?.pushViewController(ViewController(), animated: true)
// 回到上一頁
self.navigationController?.popViewController(animated: true)
// 直接回到navigation最開頭
self.navigationController?.popToRootViewController(animated: true)
前後的controller
如同present / dismiss可以拿到前後關係一樣,navigation controller也是可以拿到前後關係的。
所有經過的controller都放在self.navigationController?.viewControllers這個array裡可以拿取。
self.navigationController?.viewControllers[0]
self.navigationController?.viewControllers[1]
...
Navigation Controller的建立
Navigation Contrller本身並不帶有頁面資訊,而是在Navigation Controller的下一個Controller才會開始顯示。
那兩者之間要建立連線,必須要指定Navigation Controller當中的Root View Controller才行。
- 方法1: Navigation綁定Root Controller,有兩種做法
- 按住Ctrl並將Navigation Controller拉到想要顯示的第一個Controller,鬆開後選擇Root view Controller
- 在Navigation Controller的Triggered Segue中,指定Root view Controller
- 方法2: 在StoryBoard中,點擊已經存在的Controller。並從上方選單Editor => Embed in => NavigatioController即可
上面的title和向右鈕
預設只有Navigation Controller後第一個Bar可以編輯Title,若要加入新title則要從StoryBoard中拉入Navigation item
預設上方並無向右鍵,只有返回鍵,若要加入則拉入Bar Button Item
Tab Bar Controller
與Navigation View許多做法類似,想加上新的tab可以
- 方法1: TabViewController綁定View Controllers,有兩種做法
- 按住Ctrl並將TabViewController拉到想要加上的Controller,鬆開後選擇View Controllers
- 在TabViewController的Triggered Segue中,指定新的View Controllers
- 方法2: 在StoryBoard中,點擊已經存在的Controller。並從上方選單Editor => Embed in => TabViewController即可
tab bar controller下的controller們
self.tabBarController?.viewControllers[0]
self.tabBarController?.viewControllers[1]
...
要注意的是,如果是tab bar controller下包著navigation controller,在存取時要先轉型成navigation controller,再取用navigation controller裡面的controller,才不會錯誤。
切換tab
self.tabBarController?.selectedIndex = 0
隱藏tab
self.tabBarController?.tabBar.isHidden = true
在不同的Controller之間跳轉
方法1 直接拖拉
在storyboard上
按住ctrl點下來源Controller之button拉到要呈現的Controller後鬆開,選擇show或present即可。
show會在啟用navigation bar的時候自動使用navigation.push,若無navigation bar則是使用present。
方法2 利用segue
在storyboard上
按住ctrl點下來源Controller,拉到要呈現的Controller,會多一條segue連線,幫此條連線取名字(identifer)
接下來在想切換的情況下使用performSegue即可(參數為剛剛取的identifer)
performSegue(withIdentifier: "goToView2", sender: nil)
方法3 指定StoryBoard以及Controller (通常用在更換Story Board的時候)
將要呈現的Controller取名字(StoryBoardID)
並在程式碼中指定
- 位於哪個StoryBoard
- Controller的StoryBoardID
最後present或push即可
let myStoryBoard = UIStoryboard(name: "Main", bundle: nil)
let whiteViewController = myStoryBoard.instantiateViewController(withIdentifier: "whiteView")
present(whiteViewController, animated:true, completion:nil)
如果是位於同一個storyboard,也可以拿到自己的storyboard
let myStoryBoard = self.storyboard!
let whiteViewController = myStoryBoard.instantiateInitialViewController(withIdentifier: "whiteView")
present(whiteViewController, animated:true, completion:nil)
方法4 StoryBoard Reference (通常用在更換Story Board的時候)
在StoryBoard中,將StoryBoard Reference
拖到畫面上,指定StoryBoard Reference上的StoryBoard和ControllerID
接下來利用方法1或2拖拉即可成功
方法5 逃生門 (通常用在要返回前一個Controller的時候)
情況
假設最初的controller取名叫parent controller
parent controller可以連到許多child controller
當child controller想返回parent controller的時候
作法
先寫在parent controller
@IBAction func backToMain(_ segue:UIStoryboardSegue){
print("back to main")
}
再用StoryBoard選擇child controller
按鈕按住ctrl往該controller的逃生門拉 (上方最右邊的圖示)
即會出現backToMain的方法
如果parent controller在child controller返回後想執行什麼動作也可在此function中填入
Controller之間傳遞訊息
方法1. 利用destination controller的property (呼叫下一個Controller時)
- 複寫Source Controller的prepare Function
- 參數segue之屬性destionation即為destination controller
- 強制轉型為destination controller
- 將其塞入數值
這裡要注意的是,不能把值塞進destination controller的UI元件內(Ex: Label),因為這個時候畫面元件還沒生成。
// Source Controller
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.destination is SecondViewController{
let des = segue.destination as! SecondViewController
des.textFromFirstView = "Hello World"
}
}
方法2. 利用前後階層關係(僅適用於返回controller時)
前面有說過present可以靠
- presentingViewController
- presentedViewController
navigation bar可以靠
- self.navigationController?.viewControllers[0]
- self.navigationController?.viewControllers[1]
- …
來獲得前面的controller元件 取得controller後再參考方法1塞入值
方法3. protocol (返回前一個controller時)
將前一個controller名為FirstViewController
現在這個controller名為SecondViewController
令一個protocol叫做SecondViewControllerDelegate
- 將SecondViewControllerDelegate當作SecondViewController的屬性
- 在FirstViewController跳轉到SecondViewController前,通過方法1塞進自己實作的SecondViewControllerDelegate
- SecondViewController消失前,執行該protocol的method (透過複寫viewWillDisapear來達成)
SecondController
var delegate:SecondViewControllerDelegate?
@IBAction func goBack(_ sender: UIButton) {
let _ = navigationController?.popViewController(animated: true)
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
if self.isMovingFromParentViewController{
delegate?.setColor(colorType: "red")
}
}
hint: 可以靠 self.isMovingFromParentViewController來測試是否為返回到前一個Controller
SecondViewControllerDelegate
protocol SecondViewControllerDelegate{
func setColor(colorType:String)
}
FirstViewController (實作SecondViewControllerDelegate)
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let des = segue.destination as? SecondViewController{
des.delegate = self
}
}
方法4. notification (返回前一個controller時)
- parent controller先監聽事件
- 撰寫接收到監聽後要執行的function
- 註冊監聽事件
- child controller發送notification
ParentController
// 接收到事件後要執行的任務
func getUpdateNoti(noti:Notification) {
let data = noti.userInfo!["data"] as! String
print(data)
}
override func viewDidLoad() {
super.viewDidLoad()
// 註冊事件
let notificationName = Notification.Name("GetUpdateNoti")
NotificationCenter.default.addObserver(self, selector: #selector(ViewController.getUpdateNoti(noti:)), name: notificationName, object: nil)
}
ChildController
// 發送事件
let notificationName = Notification.Name("GetUpdateNoti")
NotificationCenter.default().post(name: notificationName,object: nil, userInfo: ["data":"Hello World"])
方法5. 全域變數
在AppDelegate增加property
並在想要使用的class裡面,將UIApplication.shared.delegate轉型為AppDelegate
即可使用裡面的property
if let appDelegate = UIApplication.shared.delegate as? AppDelegate{
if let colorName = appDelegate.color{
setColor(colorType: colorName)
}
}
全域變數還有很多做法,像是singleton或是寫在class之外,有機會再開一篇講。