UISplitViewController簡單入門

在郵件這個App裏, 它在iPad裏劃分了兩個區域, 左邊是一個郵件列表, 右邊則是對應的郵件詳細內容. Apple爲咱們建立了一個很是方便的ViewController, 它的名字叫作UISplitViewController. 在這個教程中, 咱們將學習如何去使用它, 還有一個事情就是, 在iOS 8開始, UISplitViewController就能夠在iPadiPhone上運行.git

在本教程中, 咱們將會從頭開始建立一個通用的App, 它使用UISplitViewController來顯示水果的列表, 咱們將使用UISplitViewController來處理iPadiPhone 11Navigation和顯示的問題.程序員

在此以前, 你應該掌握iOS開發的一些基礎知識, 好比AutoLayoutStoryboard的使用等等.github

圖片

在開始以前, 咱們須要下載本教程的一些課件, 這裏的課件共有兩個, 一個是已經完成了的, 一個是準備讓你去完成的.swift

注意: 這裏使用的是Xcode 11, iOS 13Swift 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.

圖片

  • Table View Controller: 這是UINavigationController的根視圖, 它將會顯示水果列表.

注意: 這個時候因爲咱們沒有Cell的重用標識, 因此Xcode會有個警告, 這個前往別忘記了.

還有一點要注意的是, 因爲咱們把自帶的ViewController給刪除了, 因此咱們須要告訴Storyboard, 咱們但願將UISplitViewController設置爲初始化ViewController.

這個時候咱們選擇UISplitViewController, 而後在右側的"屬性"欄, 勾選Is Initial View Controller:

圖片

勾選了以後, 咱們就會在UISplitViewController的左側看到一個箭頭, 這就是這個Storyboard的初始化控制器.

這個時候, 咱們選擇iPad模擬器, 而後將模擬器橫向後, 你就會看到下面這個空白的UISplitViewController了:

圖片

以前也說了, 自從iOS 8以後, 咱們也能夠運行在iPhone上, 只要它的尺寸夠大就能夠了, 這裏咱們選擇iPhone 8 Plus模擬器, 而後就會看到效果:

圖片

在橫向的大尺寸iPhone會和iPad顯示的效果同樣以外, UISplitViewController將會和常規的操做同樣, 會有UINavigationControllerPushPop, 這都是系統幫咱們實現的, 不須要咱們而外再去操做,

建立自定義ViewController

如今咱們已經有了Storyboard主要的控制器結構, 如今咱們須要的是在代碼上添加數據源, 而後將數據顯示出來.

如今咱們建立一個名爲MasterViewControllerUITableViewController子類, 在建立的過程裏, 咱們須要把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中設置一些UITableViewControllerUITableViewCell相關的東西.

  • 首先設置RootViewControllerCustom Class設置爲MasterViewController:

圖片

  • 其次設置UITableViewCellIdentifyFruitCell, 就和我上們面的代碼一致, 而後再設置StyleBasic:

圖片

如今咱們能夠運行一下, 這個時候你會發現總共有十行, 每行的標題都是同樣的, 並且每當咱們點擊任何的一行都不會發生任何事情.

圖片

這是由於咱們尚未建立DetailViewController, 如今咱們來建立對應的DetailViewController, 和上面同樣, 這個過程就忽略掉, 只是名稱爲DetailViewController.

建立完成以後, 咱們按照一樣的方式, 在Main.storyboard中, 給DetailViewController設置Custom ClassDetailViewController.

爲了讓咱們的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的標題

爲了讓MasterViewController的標題看起來更加的貼合, 咱們能夠修改一下它的標題, 修改標題有兩種方式, 第一種是直接在Storyboard中修改:

圖片

第二種是在代碼上修改:

override func viewDidLoad() {
    super.viewDidLoad()

    title = "Fruit List"
}
複製代碼

不管哪一種均可以, 但若是你所在的公司有固定的代碼規範, 那就按照公司的規範來.

顯示水果的詳細內容

如今在TableView中咱們已經顯示了水果的名稱, 如今咱們是時候來完善每當點擊Cell的時候, DetailViewController則會顯示對應的內容了.

打開Main.storyboard, 將原來咱們添加到DetailViewController裏面的內容刪掉, 而後再添加咱們所須要展現的內容:

圖片

下面是咱們須要添加的內容:

  • 最左邊是用來顯示水果樣子的UIImageView, 它的尺寸是128x128
  • 右邊最上面的是用來顯示水果名稱的UILabel, 它用的是系統粗體, 字號爲28
  • 右邊最下面的是用來顯示水果詳情的UILabel, 它用的是系統常規, 字號爲17
  • 這裏使用了兩個UIStackView, 第一個是用在兩個UILabel裏, 排序方式是豎向的, 而且設置它們之間的間距爲10, 第二個則是用在UIImageView和第一個UIStackView, 它的排序方式是橫向的, 而且設置它們之間的間距爲15.

這裏使用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的屬性, 其中是包含了MasterViewControllerDetailViewController, 在咱們這個狀況下, MasterViewController實際上就是NavigationController, 因此咱們若是要獲取真正的MasterViewController實例, 就須要得到NavigationController中的一個ViewController.

要獲取DetailViewController也是使用一樣的方式, 只不過是獲取UISplitViewController中的ViewControllers最後一個ViewController.

如今咱們運行項目, 就能夠看到有關於水果的詳情信息:

圖片

但如今咱們又面臨了一個問題, 不管咱們點擊哪一個UITableViewCell, 都只會顯示蘋果的信息, 接下來咱們就須要解決這個問題.

使用delegate完善DetailViewController的顯示內容

關於兩個控制器之間的通訊方式有不少種, 在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.swiftscene(_:willConnectTo:options:)方法中, 將DetailViewController設置爲MasterViewController的代理:

masterViewController.delegate = detailViewController
複製代碼

如今咱們已經完成了MasterViewControllerDetailViewController之間的通訊了.

圖片

如今看上去一切都好像很是的完美, 但若是咱們須要在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上它就能夠正常運行了, 只是添加了幾行代碼, 咱們就能夠在iPadiPhone上使用完整的UISplitViewController了.

圖片

完善iPad的縱向顯示

在橫向顯示的時候, 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修改爲顯示DetailViewControllerNavigationController, 但不管怎麼改, 這個NavigationController的根視圖依然是DetailViewController, 和咱們以前看到的內容依然是同樣的.

接下來, 咱們須要在SceneDelegate.swiftscene(_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的使用, 雖然這個例子比較簡單, 可是能夠經過該例子慢慢的延展出更多的使用場景, 謝謝你們的閱讀.

相關文章
相關標籤/搜索