1. 程式人生 > IOS開發 >UISplitViewController簡單入門

UISplitViewController簡單入門

在郵件這個App裡,它在iPad裡劃分了兩個區域,左邊是一個郵件列表,右邊則是對應的郵件詳細內容. Apple為我們建立了一個非常方便的ViewController,它的名字叫做UISplitViewController. 在這個教程中,我們將學習如何去使用它,還有一個事情就是,在iOS 8開始,UISplitViewController就可以在iPadiPhone上執行.

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

和顯示的問題.

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

圖片

在開始之前,我們需要下載本教程的一些課件,這裡的課件共有兩個,一個是已經完成了的,一個是準備讓你去完成的.

注意: 這裡使用的是Xcode 11,iOS 13Swift 5,如需轉載,請聯絡作者,侵權必究.

開始

點選File ▸ New ▸ Project,在Xcode中建立一個新的專案,選擇iOS ▸ Application ▸ Single View App模板

圖片

將專案命名為Fruit,將開發語言設定為Swift,然後將使用者介面設定為Storyboard

,如果底下的勾選框勾選了的話,則要取消掉.

圖片

雖然我們可以選擇使用Master-Detail App這個模板,但是為了更好的瞭解UISplitViewController的工作原理,所以我們將使用Single View App模板. 這對我們在將來的專案使用UISplitViewController時會更有幫助.

現在我們來建立App的主體UI,開啟Main.storyboard,這裡我們需要把系統自帶的ViewController刪除,同時也要將專案中的ViewController.swift檔案也刪掉.

然後在Main.storyboard中找到Split View Controller

,然後拖出來:

圖片

圖片

這裡會讓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的使用,雖然這個例子比較簡單,但是可以通過該例子慢慢的延展出更多的使用場景,謝謝大家的閱讀.