在郵件這個App裏, 它在iPad裏劃分了兩個區域, 左邊是一個郵件列表, 右邊則是對應的郵件詳細內容. Apple爲咱們建立了一個很是方便的ViewController, 它的名字叫作UISplitViewController. 在這個教程中, 咱們將學習如何去使用它, 還有一個事情就是, 在iOS 8開始, UISplitViewController就能夠在iPad和iPhone上運行.git
在本教程中, 咱們將會從頭開始建立一個通用的App, 它使用UISplitViewController來顯示水果的列表, 咱們將使用UISplitViewController來處理iPad和iPhone 11的Navigation和顯示的問題.程序員
在此以前, 你應該掌握iOS開發的一些基礎知識, 好比AutoLayout和Storyboard的使用等等.github
在開始以前, 咱們須要下載本教程的一些課件, 這裏的課件共有兩個, 一個是已經完成了的, 一個是準備讓你去完成的.swift
注意: 這裏使用的是Xcode 11, iOS 13和Swift 5, 如需轉載, 請聯繫做者, 侵權必究.數組
點擊File ▸ New ▸ Project, 在Xcode中建立一個新的項目, 選擇iOS ▸ Application ▸ Single View App模板ide
將項目命名爲Fruit, 將開發語言設置爲Swift, 而後將用戶界面設置爲Storyboard, 若是底下的勾選框勾選了的話, 則要取消掉.佈局
雖然咱們能夠選擇使用Master-Detail App這個模板, 可是爲了更好的瞭解UISplitViewController的工做原理, 因此咱們將使用Single View App模板. 這對咱們在未來的項目使用UISplitViewController時會更有幫助.學習
如今咱們來建立App的主體UI, 打開Main.storyboard, 這裏咱們須要把系統自帶的ViewController刪除, 同時也要將項目中的ViewController.swift文件也刪掉.優化
而後在Main.storyboard中找到Split View Controller, 而後拖出來:ui
這裏會讓Storyboard添加幾個元素:
Split View Controller: 第一個確定是UISplitViewController, 它將是這個App的根控制器.
Navigation Controller: 其次是UINavigationController, 它將是主控制器的根視圖, 在iPad或者是比較大尺寸的iPhone橫屏時, 它將會顯示在左側.
仔細點查看, 在UISplitViewController中, 這個具備UINavigationController的控制器, 是它的Master View Controller, 這將容許咱們在主視圖控制器中建立整個Navigation的層次結果, 而後又不影響到Detail View Controller.
View Controller: 這裏將會顯示全部水果的詳細信息, 若是你仔細點查看UISplitViewController, 你會發現ViewController是它的Detail View Controller.
注意: 這個時候因爲咱們沒有Cell的重用標識, 因此Xcode會有個警告, 這個前往別忘記了.
還有一點要注意的是, 因爲咱們把自帶的ViewController給刪除了, 因此咱們須要告訴Storyboard, 咱們但願將UISplitViewController設置爲初始化ViewController.
這個時候咱們選擇UISplitViewController, 而後在右側的"屬性"欄, 勾選Is Initial View Controller:
勾選了以後, 咱們就會在UISplitViewController的左側看到一個箭頭, 這就是這個Storyboard的初始化控制器.
這個時候, 咱們選擇iPad模擬器, 而後將模擬器橫向後, 你就會看到下面這個空白的UISplitViewController了:
以前也說了, 自從iOS 8以後, 咱們也能夠運行在iPhone上, 只要它的尺寸夠大就能夠了, 這裏咱們選擇iPhone 8 Plus模擬器, 而後就會看到效果:
在橫向的大尺寸iPhone會和iPad顯示的效果同樣以外, UISplitViewController將會和常規的操做同樣, 會有UINavigationController的Push和Pop, 這都是系統幫咱們實現的, 不須要咱們而外再去操做,
如今咱們已經有了Storyboard主要的控制器結構, 如今咱們須要的是在代碼上添加數據源, 而後將數據顯示出來.
如今咱們建立一個名爲MasterViewController的UITableViewController子類, 在建立的過程裏, 咱們須要把Also create XIB file給去掉, 由於在Storyboard裏已經有了, 而後開發語言爲Swift, 而後就一直下一步, 到最後完成建立便可.
建立完成了以後, 咱們打開MasterViewController.swift, 刪除一些不須要的代碼, 而後添加一些咱們須要的代碼:
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 10
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "FruitCell", for: indexPath)
return cell
}
複製代碼
在運行以前, 咱們須要在Storyboard中設置一些UITableViewController與UITableViewCell相關的東西.
如今咱們能夠運行一下, 這個時候你會發現總共有十行, 每行的標題都是同樣的, 並且每當咱們點擊任何的一行都不會發生任何事情.
這是由於咱們尚未建立DetailViewController, 如今咱們來建立對應的DetailViewController, 和上面同樣, 這個過程就忽略掉, 只是名稱爲DetailViewController.
建立完成以後, 咱們按照一樣的方式, 在Main.storyboard中, 給DetailViewController設置Custom Class爲DetailViewController.
爲了讓咱們的DetailViewController可以有東西顯示, 這裏加一個UILabel而後, 寫上全世界程序員第一句學的代碼Hello Wirkd!, 而後在iPad模擬器上運行:
如今每當咱們點擊一下UITableViewCell, 在DetailViewController中就會顯示一個Hello World!.
基礎知識講完了, 接下來是來製做咱們須要顯示的數據模型, 因爲如今咱們是在演示, 因此這裏的數據模型不會很複雜, 更加不會使用到數據持久化.
首先咱們須要建立一個名爲Fruit類, 這裏使用的模板是:
咱們建立的這個類, 其中包含了水果的圖片, 名稱, 介紹等等:
import UIKit
struct Fruit {
let name: String
let description: String
let iconName: String
init(name: String, description: String, iconName: String) {
self.name = name
self.description = description
self.iconName = iconName
}
var icon: UIImage? {
return UIImage(named: iconName)
}
}
複製代碼
如今回到咱們的MasterViewController.
打開MasterViewController.swift以後, 咱們須要新增一個let的屬性:
let fruits = [
Fruit(name: "Apple", description: "這是蘋果", iconName: "Apple"),
Fruit(name: "Banana", description: "這是香蕉", iconName: "Banana"),
Fruit(name: "Blackberry", description: "這是黑莓", iconName: "Blackberry"),
Fruit(name: "Cherries", description: "這是櫻桃", iconName: "Cherries"),
Fruit(name: "Coconut", description: "這是椰子", iconName: "Coconut"),
Fruit(name: "Grapes", description: "這是葡萄", iconName: "Grapes")
]
複製代碼
這是咱們用來顯示的具體數據數組.
而後咱們找到tableView(_:numberOfRowsInSection:)
並將return語句替換爲如下內容:
return fruits.count
複製代碼
接下來, 咱們須要將名稱顯示到UITableViewCell中, 找到tableView(_:cellForRowAtIndexPath:)
, 而且在return的語句以前添加下面的代碼:
let fruti = fruits[indexPath.row]
cell.textLabel?.text = fruti.name
複製代碼
這將會把水果的名稱顯示到UITableViewCell中, 如今咱們運行一下項目:
如今咱們成功的將水果名稱顯示在UITableViewCell中了.
爲了讓MasterViewController的標題看起來更加的貼合, 咱們能夠修改一下它的標題, 修改標題有兩種方式, 第一種是直接在Storyboard中修改:
第二種是在代碼上修改:
override func viewDidLoad() {
super.viewDidLoad()
title = "Fruit List"
}
複製代碼
不管哪一種均可以, 但若是你所在的公司有固定的代碼規範, 那就按照公司的規範來.
如今在TableView中咱們已經顯示了水果的名稱, 如今咱們是時候來完善每當點擊Cell的時候, DetailViewController則會顯示對應的內容了.
打開Main.storyboard, 將原來咱們添加到DetailViewController裏面的內容刪掉, 而後再添加咱們所須要展現的內容:
下面是咱們須要添加的內容:
這裏使用UIStackView能夠幫助咱們省下不少使用AutoLayout的佈局問題, 如今佈局完成了, 咱們來將UIKit控件和DetailViewController關聯.
打開DetailViewController.swift, 而後將下面的代碼添加進去:
@IBOutlet weak var imageView: UIImageView!
@IBOutlet weak var nameLabel: UILabel!
@IBOutlet weak var descriptionLabel: UILabel!
var furti: Fruit? {
didSet {
refreshUI()
}
}
private func refreshUI() {
loadViewIfNeeded()
nameLabel.text = furti?.name
descriptionLabel.text = furti?.description
imageView.image = furti?.icon
}
複製代碼
接下來, 咱們則須要在Storyboard中, 去關聯這裏的屬性:
如今, 咱們準備就緒了, 只差將數據顯示就能夠了, 打開SceneDelegate.swift, 而後將下面的代碼替換scene(_:willConnectTo:options :)
的內部實現:
guard
let splitViewController = window?.rootViewController as? UISplitViewController,
let leftNavController = splitViewController.viewControllers.first as? UINavigationController,
let masterViewController = leftNavController.viewControllers.first as? MasterViewController,
let detailViewController = (splitViewController.viewControllers.last as? UINavigationController)?.topViewController as? DetailViewController
else { fatalError() }
let firstMonster = masterViewController.fruits.first
detailViewController.furti = firstMonster
複製代碼
在UISplitViewController中, 它有一個名爲ViewControllers的屬性, 其中是包含了MasterViewController和DetailViewController, 在咱們這個狀況下, MasterViewController實際上就是NavigationController, 因此咱們若是要獲取真正的MasterViewController實例, 就須要得到NavigationController中的一個ViewController.
要獲取DetailViewController也是使用一樣的方式, 只不過是獲取UISplitViewController中的ViewControllers最後一個ViewController.
如今咱們運行項目, 就能夠看到有關於水果的詳情信息:
但如今咱們又面臨了一個問題, 不管咱們點擊哪一個UITableViewCell, 都只會顯示蘋果的信息, 接下來咱們就須要解決這個問題.
關於兩個控制器之間的通訊方式有不少種, 在Master-Detail App的模板中, MasterViewController有着對DetailViewController的引用, 這就意味着MasterViewController能夠在DetailViewController上設置屬性了.
這若是隻是在一個簡單的ViewController中則能夠直接使用, 但在咱們這種狀況, 仍是遵循UISplitViewController類引用中的建議方法來處理, 那就是添加delegate.
打開MasterViewController.swift, 而且在這個類的上面添加下面的代碼:
protocol FruitSelectionDelegate: class {
func furitSelected(_ newFurit: Fruit)
}
複製代碼
這定義了一個帶有名爲furitSelected方法的協議, 咱們將會在DetailViewController中實現這個方法, 而且在MasterViewController中將用戶選擇的水果發送過去.
接下來, 咱們須要定義一個delegate屬性:
weak var delegate: FruitSelectionDelegate?
複製代碼
這就意味着delegate屬性須要一個實現了furitSelected(_:)
方法的對象, 因爲咱們但願DetailViewController在用戶點了不一樣的水果時就更新內容, 因此咱們須要在DetailViewController中實現這個代理方法:
extension DetailViewController: FruitSelectionDelegate {
func furitSelected(_ newFurit: Fruit) {
self.furti = newFurit
}
}
複製代碼
這裏咱們使用extension可讓代碼看起來更加的明確, 如今咱們在DetailViewController中實現了這個代理方法, 那麼接下來, 咱們還須要在MasterViewController裏, 實現這個傳遞的細節:
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let selectedFruits = fruits[indexPath.row]
delegate?.furitSelected(selectedFruits)
}
複製代碼
最後, 咱們還須要在SceneDelegate.swift的scene(_:willConnectTo:options:)
方法中, 將DetailViewController設置爲MasterViewController的代理:
masterViewController.delegate = detailViewController
複製代碼
如今咱們已經完成了MasterViewController和DetailViewController之間的通訊了.
如今看上去一切都好像很是的完美, 但若是咱們須要在iPhone上運行它, 那麼在MasterViewController選擇水果時就不會顯示DetailViewController了, 這裏咱們須要進行一丟丟的優化, 確保在iPhone上也能夠正常運行.
打開MasterViewController.swift, 找到tableView(_:didSelectRowAt:)
方法, 而後將下面的內容添加到內部代碼的最後面:
if let detailViewController = delegate as? DetailViewController {
splitViewController?.showDetailViewController(detailNavigationController, sender: nil)
}
複製代碼
首先, 咱們確保咱們的Delegate對象是DetailViewController實例, 而後在UISplitViewController上調用showDetailViewController(_: sender:)
時, 將DetailViewController傳遞進去, 這裏有一點要說明, UIViewController自己是有一個叫作splitViewController的屬性, 它將會引用已經存在的ViewController.
通過這個簡單的改動, 在iPhone上它就能夠正常運行了, 只是添加了幾行代碼, 咱們就能夠在iPad和iPhone上使用完整的UISplitViewController了.
在橫向顯示的時候, iPad會自動在左邊顯示菜單欄, 可是在縱向時, 只能經過手勢從左往右的滑動纔會顯示, 在點擊菜單欄之外的位置, 它就會自動隱藏掉.
雖然這種滑動顯示的方式很高大上, 但若是咱們要像iPhone那樣, 在左上方有一個顯示菜單的按鈕該怎麼作呢? 這個時候咱們只須要對App進行一丟丟的優化就能夠了.
首先咱們打開Main.storyboard, 給DetailViewController添加一個UINavigationController, 這裏有兩種添加的方式.
不管哪一種其實都是能夠的, 沒有任何的區別, 下面是完成了添加的storyboard:
如今咱們打開MasterViewController, 找到tableView(_:didSelectRowAt:)
, 修改一下咱們以前調用showDetailViewController(_:sender:)
的小細節:
if let detailViewController = delegate as? DetailViewController,
let detailNavigationController = detailViewController.navigationController {
splitViewController?.showDetailViewController(detailNavigationController, sender: nil)
}
複製代碼
這裏咱們將顯示DetailViewController修改爲顯示DetailViewController的NavigationController, 但不管怎麼改, 這個NavigationController的根視圖依然是DetailViewController, 和咱們以前看到的內容依然是同樣的.
接下來, 咱們須要在SceneDelegate.swift的scene(_willConnectTo:options:)
中, 修改初始化detailViewController的代碼:
let detailViewController = (splitViewController.viewControllers.last as? UINavigationController)?.topViewController as? DetailViewController
複製代碼
由於DetailViewController是存在於UINavigationController中, 因此咱們這裏須要經過訪問兩層來獲取到它的實例.
最後, 咱們在方法結束以前, 添加下面的兩行代碼:
detailViewController.navigationItem.leftItemsSupplementBackButton = true
detailViewController.navigationItem.leftBarButtonItem = splitViewController.displayModeButtonItem
複製代碼
這兩行代碼是用來告訴DetailViewController的左上角按鈕是用來顯示UISplitViewController的, 在iPhone不會有任何的改變, 可是在iPad上會有一個按鈕用來顯示菜單的UITableView.
下面就是咱們運行的效果:
經過簡單的事例, 咱們學習了UISplitViewController的使用, 雖然這個例子比較簡單, 可是能夠經過該例子慢慢的延展出更多的使用場景, 謝謝你們的閱讀.