第十四章——UINavigationController【譯】

在第五章中,您了解了 UITabBarController 以及它如何允許用戶(hù)訪問(wèn)不同的屏幕。 標(biāo)簽欄控制器非常適合彼此獨(dú)立的屏幕,但如果您有多個(gè)提供相關(guān)信息的屏幕怎么辦?

例如,Settings 應(yīng)用程序具有多個(gè)相關(guān)信息屏幕:一個(gè)設(shè)置列表(如Sounds),每個(gè)設(shè)置的詳細(xì)頁(yè)面以及每個(gè)詳細(xì)信息的選擇頁(yè)面(圖14.1)。 這種類(lèi)型的界面稱(chēng)為 向下鉆取界面(drill-down interface)。

圖14.1“Settings” 中的向下鉆取界面

在本章中,您將使用 UINavigationControllerHomepwner 添加一個(gè) 向下鉆取界面,該界面允許用戶(hù)查看和編輯 Item 的詳細(xì)信息。 這些詳細(xì)信息將由您在第十三章(圖14.2)中創(chuàng)建的 DetailViewController 顯示。

圖14.2 使用 UINavigationController 的 Homepwner

UINavigationController

UINavigationController 包含一個(gè)視圖控制器數(shù)組,用于在棧中呈現(xiàn)相關(guān)信息。 當(dāng) UIViewController 位于棧頂部時(shí),其視圖是可見(jiàn)的。

你將使用 UIViewController 初始化一個(gè) UINavigationController 的實(shí)例。 該 UIViewController 被添加到導(dǎo)航控制器的 viewControllers 數(shù)組,并成為導(dǎo)航控制器的根視圖控制器。 根視圖控制器始終位于棧底。 (請(qǐng)注意,盡管該視圖控制器被稱(chēng)為導(dǎo)航控制器的 “根視圖控制器”,然而 UINavigationController 沒(méi)有 rootViewController 這個(gè)屬性。)

更多的視圖控制器可以在應(yīng)用程序運(yùn)行時(shí)被 push 到 UINavigationController 的棧頂。 這些視圖控制器被添加到對(duì)應(yīng)于棧頂?shù)?viewControllers 數(shù)組的末尾。

UINavigationControllertopViewController 屬性保留對(duì)堆棧頂部的視圖控制器的引用。

當(dāng)一個(gè)視圖控制器 壓棧(push) 時(shí),它的視圖就會(huì)從右邊移到屏幕上。當(dāng) 彈棧(pop) 時(shí)(例:最后一項(xiàng)被移除時(shí)),頂部視圖控制器從棧中移開(kāi),它的視圖滑到右邊,將下一個(gè)視圖控制器的視圖暴露在棧上并將成為頂部視圖控制器。圖14.3顯示了一個(gè)帶有兩個(gè)視圖控制器的導(dǎo)航控制器。topViewController 的視圖是用戶(hù)所看到的。

圖14.3 UINavigationController 的棧

UINavigationControllerUIViewController 的子類(lèi),因此它具有自己的視圖。它的視圖總是有兩個(gè)子視圖:一個(gè) UINavigationBartopViewController 的視圖(圖14.4)。

圖14.4 UINavigationController 的視圖

在本章中,您將向 Homepwner 應(yīng)用程序添加一個(gè) UINavigationController,并使 ItemsViewController 成為 UINavigationController 的根視圖控制器。 當(dāng)選擇 Item 時(shí), DetailViewController 將被 push 到 UINavigationController 的棧上。該視圖控制器將允許用戶(hù)查看和編輯在 ItemsViewController 的表視圖中選中的 Item 的屬性。 更新的 Homepwner 應(yīng)用程序的對(duì)象圖如圖14.5所示。

圖14.5 Homepwner 對(duì)象圖

這個(gè)應(yīng)用程序變得相當(dāng)大,你可以看到。 幸運(yùn)的是,視圖控制器和 UINavigationController 知道如何處理這種復(fù)雜的對(duì)象圖。 在編寫(xiě)iOS應(yīng)用程序時(shí),將每個(gè) UIViewController 視為自己的小世界很重要。 在 Cocoa Touch 中已經(jīng)實(shí)施的內(nèi)容將會(huì)起到舉足輕重的作用。

首先重新打開(kāi) Homepwner 項(xiàng)目,給 Homepwner 添加一個(gè)導(dǎo)航控制器。 使用 UINavigationController 的唯一要求是您給它一個(gè)根視圖控制器,并將其視圖添加到窗口。

打開(kāi) Main.storyboard 并選擇 Items View Controller。 然后,從 Editor 菜單中選擇
Embed InNavigation Controller。 這將將 ItemsViewController 設(shè)置為 UINavigationController 的根視圖控制器。 它還將更新故事板,將 Navigation Controller 設(shè)置為初始視圖控制器。

您的 Detail View Controller 界面現(xiàn)在可能會(huì)放錯(cuò)位置,因?yàn)樗话趯?dǎo)航控制器中。 如果是,請(qǐng)選擇棧視圖,然后單擊自動(dòng)布局約束菜單中的 Update Frames 按鈕。

構(gòu)建并運(yùn)行應(yīng)用程序,然后應(yīng)用程序崩潰了。 發(fā)生了什么? 您之前與 AppDelegate
創(chuàng)建 關(guān)聯(lián)( contract )的是 ItemsViewController 的實(shí)例,這將會(huì)是窗口的
rootViewController:

let itemsController = window!.rootViewController as! ItemsViewController

您現(xiàn)在通過(guò)將 ItemsViewController 嵌入到 UINavigationController 中來(lái)破壞此關(guān)聯(lián)。 您需要更新此關(guān)聯(lián)。

打開(kāi) AppDelegate.swift 并更新 application(_:didFinishLaunchingWithOptions :) 以反映新的視圖控制器層級(jí)。

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey : Any]?) -> Bool {
??// Override point for customization after application launch.

??// Create an ItemStore
??let itemStore = ItemStore()

??// Access the ItemsViewController and set its item store

??let itemsController = window!.rootViewController as! ItemsViewController
??let navController = window!.rootViewController as! UINavigationController
??let itemsController = navController.topViewController as! ItemsViewController
??itemsController.itemStore = itemStore

??return true
}

再次構(gòu)建并運(yùn)行應(yīng)用程序。 Homepwner 再次正常運(yùn)行,在屏幕頂部有一個(gè)非常好看的(雖然是空的) UINavigationBar (圖14.6)。

圖14.6具有空導(dǎo)航欄的Homepwner

請(qǐng)注意屏幕是如何調(diào)整來(lái)適應(yīng) ItemsViewController 的視圖以及新的導(dǎo)航欄的。UINavigationController 為您做了這樣的事情:雖然 ItemsViewController 的視圖實(shí)際上是在導(dǎo)航欄上進(jìn)行的,但 UINavigationController 在頂部添加了 padding,使一切都很適合。這是因?yàn)橐晥D控制器的頂部布局指南被調(diào)整了,以及所有視圖都被約束在頂部的布局指南中——就像棧視圖一樣。

UINavigationController 導(dǎo)航

在應(yīng)用程序仍在運(yùn)行的情況下,創(chuàng)建一個(gè)新 item 并從 UITableView 中選擇該行。你不僅被帶到 DetailViewController 的視圖中,而且你還可以在 UINavigationBar 中獲得一個(gè)自帶的動(dòng)畫(huà)和一個(gè) Back 按鈕。點(diǎn)擊這個(gè)按鈕,你將回到 ItemsViewController。

注意,你不需要改變你在第13章創(chuàng)建的 show segue 來(lái)得到這個(gè)行為。正如在那一章中所提到的,show segue 以一種有意義的方式呈現(xiàn)了目標(biāo)視圖控制器。當(dāng)一個(gè) show segue 從嵌入在導(dǎo)航控制器中的視圖控制器觸發(fā)時(shí),目標(biāo)視圖控制器被 push 到導(dǎo)航控制器的視圖控制器棧中。

因?yàn)?UINavigationController 的棧是一個(gè)數(shù)組,它將擁有對(duì)添加到它的任何視圖控制器的所有權(quán)。因此,DetailViewController 只在 segue 完成之后由 UINavigationController 擁有。當(dāng)棧被彈出時(shí), DetailViewController 會(huì)被銷(xiāo)毀。下一次當(dāng)一行被點(diǎn)擊時(shí),會(huì)創(chuàng)建一個(gè) DetailViewController 的新實(shí)例。

使用一個(gè)視圖控制器來(lái) push 下一個(gè)視圖控制器是一個(gè)常見(jiàn)的模式。根視圖控制器通常創(chuàng)建下一個(gè)視圖控制器,下一個(gè)視圖控制器創(chuàng)建后一個(gè)視圖控制器,等等。有些應(yīng)用程序可能可以根據(jù)用戶(hù)輸入來(lái) push 不同的視圖控制器。例如,根據(jù)選擇的媒體類(lèi)型,Photos 應(yīng)用程序?qū)⒁曨l視圖控制器或圖像視圖控制器 push 到導(dǎo)航棧上。

請(qǐng)注意,item 的詳細(xì)信息視圖包含所選 Item 的信息。然而,當(dāng)您可以編輯這些數(shù)據(jù)時(shí), UITableView 在返回時(shí)不會(huì)反映這些更改。為了解決這個(gè)問(wèn)題,您需要實(shí)現(xiàn)代碼來(lái)更新正在編輯的 Item 的屬性。在下一節(jié)中,您將看到何時(shí)執(zhí)行此操作。

顯示和消失的視圖

每當(dāng)一個(gè) UINavigationController 即將交換視圖時(shí),它會(huì)調(diào)用兩個(gè)方法: viewWillDisappear(_ :)viewWillAppear(_ :)。 當(dāng) viewWillDisappear(_ :) 被調(diào)用,UIViewController 將從棧彈出。 然后,當(dāng) viewWillAppear(_ :) 被調(diào)用,UIViewController 將處在該棧頂。

為了保持?jǐn)?shù)據(jù)更改,當(dāng) DetailViewController 從棧中彈出時(shí),您將將其 item 的屬性設(shè)置為文本字段的內(nèi)容。 在實(shí)現(xiàn)這些視圖顯示和消失的方法時(shí),重要的是調(diào)用超類(lèi)的實(shí)現(xiàn)——它可能要做一些事,或者需要機(jī)會(huì)去做一些事。 在 DetailViewController.swift 中,實(shí)現(xiàn) viewWillDisappear(_ :)

override func viewWillDisappear(_ animated: Bool) {
??super.viewWillDisappear(animated)

??// "Save" changes to item
??item.name = nameField.text ?? ""
??item.serialNumber = serialNumberField.text

??if let valueText = valueField.text, let value = numberFormatter.number(from: valueText) {
????item.valueInDollars = value.intValue
??} else {
????item.valueInDollars = 0
??}
}

現(xiàn)在當(dāng)用戶(hù)點(diǎn)擊 UINavigationBar 上的 back 按鈕時(shí),Item 的值將被更新。 當(dāng) ItemsViewController 出現(xiàn)在屏幕上時(shí),方法 viewWillAppear(_ :) 被調(diào)用。 借此機(jī)會(huì)重新加載 UITableView ,以便用戶(hù)立即看到更改。

ItemsViewController.swift 中,重寫(xiě) viewWillAppear(_ :) 來(lái)重新加載表視圖。

override func viewWillAppear(_ animated: Bool) {
??super.viewWillAppear(animated)

??tableView.reloadData()
}

再次構(gòu)建并運(yùn)行應(yīng)用程序。 現(xiàn)在,您可以在創(chuàng)建的視圖控制器之間來(lái)回移動(dòng),并輕松更改數(shù)據(jù)。

取消鍵盤(pán)

運(yùn)行應(yīng)用程序,添加并選擇一個(gè) item,然后觸摸 item 的 Name 文本字段。 當(dāng)您觸摸文本字段時(shí),屏幕上會(huì)顯示一個(gè)鍵盤(pán)(圖14.7),如您在第4章中的 WorldTrotter 應(yīng)用程序中所看到的。(如果您使用的是模擬器,鍵盤(pán)沒(méi)有出現(xiàn),請(qǐng)記住可以按 Command-K 切換設(shè)備鍵盤(pán)。)

圖14.7 觸摸文本字段時(shí)出現(xiàn)鍵盤(pán)

UITextField 類(lèi)以及 UITextView 內(nèi)置了鍵盤(pán)響應(yīng)觸摸的外觀,因此您無(wú)需為鍵盤(pán)出現(xiàn)做任何額外的操作。但是,有時(shí)您會(huì)希望確保鍵盤(pán)的行為符合您的要求。

舉個(gè)例子,注意到鍵盤(pán)覆蓋了屏幕的三分之一以上。 現(xiàn)在,它并沒(méi)有遮住任何東西,但是很快你會(huì)添加更多的詳細(xì)信息,擴(kuò)展到屏幕的底部,當(dāng)用戶(hù)不需要鍵盤(pán)時(shí)需要一種方法來(lái)隱藏它。 在本節(jié)中,您將給用戶(hù)兩種方法來(lái)關(guān)閉鍵盤(pán):按下鍵盤(pán)的 Return 鍵,或者點(diǎn)擊詳情視圖控制器視圖上的其他任何位置。 但首先,我們來(lái)看看使文本可編輯的事件組合。

事件處理基礎(chǔ)知識(shí)

觸擊視圖時(shí),會(huì)創(chuàng)建一個(gè)事件。 此事件(稱(chēng)為“觸摸事件”)與視圖控制器視圖中的特定位置相關(guān)聯(lián)。 該位置確定觸摸事件將傳遞到層級(jí)中的哪個(gè)視圖。

例如,當(dāng)您在其邊界內(nèi)點(diǎn)擊一個(gè) UIButton 時(shí),它會(huì)收到觸摸事件并以按鈕的形式進(jìn)行響應(yīng)——通過(guò)在其目標(biāo)上調(diào)用動(dòng)作方法。 當(dāng)您的應(yīng)用程序中的視圖被觸摸時(shí),該視圖會(huì)接收到觸摸事件,并且可以選擇對(duì)該事件做出反應(yīng)或忽略它。 但是,您的應(yīng)用程序中的視圖也可以響應(yīng)非觸摸事件。 一個(gè)很好的例子是搖一搖。 如果您在運(yùn)行應(yīng)用程序時(shí)晃動(dòng)設(shè)備,您的其中一個(gè)視圖就可以響應(yīng)。 但是是哪一個(gè)呢? 另一個(gè)有趣的情況是響應(yīng)鍵盤(pán)。 DetailViewController 的視圖包含三個(gè) UITextFields。 用戶(hù)輸入時(shí)哪個(gè)會(huì)接收到文本?

對(duì)于震動(dòng)和鍵盤(pán)事件,在視圖層級(jí)中沒(méi)有事件位置來(lái)確定哪個(gè)視圖將接收該事件,因此必須使用另一個(gè)機(jī)制。這個(gè)機(jī)制是 第一響應(yīng)者(first responder) 狀態(tài)。許多視圖和控件可以是視圖層級(jí)中的第一響應(yīng)者,但一次只能有一個(gè)響應(yīng)者。可以把它看作可以在視圖中傳遞的標(biāo)志。無(wú)論哪個(gè)視圖持有該標(biāo)志,都將接收震動(dòng)或鍵盤(pán)事件。

UITextFieldUITextView 的實(shí)例對(duì)觸摸事件有一個(gè)不尋常的響應(yīng)。 觸摸時(shí),文本字段或文本視圖將成為第一響應(yīng)者,反而會(huì)觸發(fā)系統(tǒng)將鍵盤(pán)顯示在屏幕上,并將鍵盤(pán)事件發(fā)送到文本字段或視圖。 鍵盤(pán)和文本字段或視圖沒(méi)有直接的連接,但它們通過(guò)第一響應(yīng)者狀態(tài)協(xié)同工作。

這是確保將鍵盤(pán)輸入傳遞到正確文本字段的整潔方法。 第一響應(yīng)者的概念只是 Cocoa Touch 編程中包含 UIResponder 類(lèi)和 響應(yīng)者鏈(responder chain) 的更廣泛的事件處理主題的一部分。 當(dāng)您處理第18章中的觸摸事件時(shí),您將了解更多信息,您還可以訪問(wèn)Apple的 `Event Handling Guide for iOS* 了解更多信息。

按Return鍵退出

現(xiàn)在讓我們回到允許用戶(hù)關(guān)閉鍵盤(pán)。 如果您觸摸應(yīng)用程序中的另一個(gè)文本字段,則該文本字段將成為第一個(gè)響應(yīng)者,鍵盤(pán)將保留在屏幕上。 當(dāng)沒(méi)有文本字段(或文本視圖)是第一個(gè)響應(yīng)者時(shí),鍵盤(pán)將被放棄并離開(kāi)。 要關(guān)閉鍵盤(pán),那么您需要在第一個(gè)響應(yīng)者的文本字段上調(diào)用 resignFirstResponder()。

要使文本字段響應(yīng)于按下Return鍵,您將要實(shí)現(xiàn) UITextFieldDelegate 方法 textFieldShouldReturn(_ :)。 只要按下Return鍵,就會(huì)調(diào)用此方法。

首先,在 DetailViewController.swift 中,使 DetailViewController 符合 UITextFieldDelegate 協(xié)議。

class DetailViewController: UIViewController, UITextFieldDelegate {

接下來(lái),在傳入的文本字段上實(shí)現(xiàn) textFieldShouldReturn(_ :) 來(lái)調(diào)用 resignFirstResponder()

func textFieldShouldReturn(_ textField: UITextField) -> Bool {
??textField.resignFirstResponder()
??return true
}

最后,打開(kāi) Main.storyboard 并將每個(gè)文本字段的 delegate 屬性連接到 Detail View Controller (圖14.8)。(右鍵從每個(gè) UITextField 拖動(dòng)到 Detail View Controller 并選擇 delegate。)

圖14.8 連接文本字段的委托屬性

構(gòu)建并運(yùn)行應(yīng)用程序。 點(diǎn)擊文本字段,然后按鍵盤(pán)上的 Return 鍵。 鍵盤(pán)將消失。 要使鍵盤(pán)返回,請(qǐng)觸摸任何文本字段。

點(diǎn)擊其他地方返回

如果用戶(hù)在 DetailViewController 的視圖上輕觸其他任何地方,也應(yīng)該會(huì)關(guān)閉鍵盤(pán)。 為了做到這一點(diǎn),當(dāng)視圖被觸摸時(shí),您將使用手勢(shì)識(shí)別器,就像在 WorldTrotter 應(yīng)用程序中一樣。 在動(dòng)作方法中,您將在文本字段上調(diào)用 resignFirstResponder()

打開(kāi) Main.storyboard 并在對(duì)象庫(kù)中找到 Tap Gesture Recognizer。 將此對(duì)象拖動(dòng)到 Detail View Controller 的背景視圖上。 您將在 scee 底部中看到此手勢(shì)識(shí)別器的引用。

在項(xiàng)目導(dǎo)航器中,右擊 DetailViewController.swiftassistant editor 中打開(kāi)它。 右鍵從故事板中的 tap gesture recognizer 拖動(dòng)到 DetailViewController 的實(shí)現(xiàn)類(lèi)。

在出現(xiàn)的彈出窗口中,從 Connection 菜單中選擇 Action。 命名動(dòng)作 backgroundTapped 。 對(duì)于 Type,選擇 UITapGestureRecognizer(圖14.9)。

圖14.9 配置UITapGestureRecognizer操作

單擊 Connect,動(dòng)作方法的存根將顯示在 DetailViewController.swift 中。 在 DetailViewController 的視圖上更新調(diào)用 endEditing(_ :) 的方法。

@IBAction func backgroundTapped(_ sender: UITapGestureRecognizer) {
??view.endEditing(true)
}

調(diào)用 endEditing(_ :) 是一種方便的方式來(lái)解除鍵盤(pán),你不必知道(或關(guān)心)哪個(gè)文本字段是第一個(gè)響應(yīng)者。 當(dāng)視圖獲得此調(diào)用時(shí),它將檢查其層級(jí)中的任何文本字段是否是第一個(gè)響應(yīng)者。 然后會(huì)在該特定的視圖上調(diào)用 resignFirstResponder()。

構(gòu)建并運(yùn)行您的應(yīng)用程序。 點(diǎn)擊文本字段以顯示鍵盤(pán)。 點(diǎn)擊文本字段外的視圖,鍵盤(pán)也將消失。

最后一個(gè)需要關(guān)掉鍵盤(pán)的情況。 當(dāng)用戶(hù)點(diǎn)擊后退按鈕時(shí),在將其從彈出的堆棧之前的 DetailViewController 上調(diào)用 viewWillDisappear(_ :),并且鍵盤(pán)立即消失,沒(méi)有動(dòng)畫(huà)。 要更順利地關(guān)閉鍵盤(pán),請(qǐng)?jiān)?DetailViewController.swift 中更新 viewWillDisappear(_ :) 的實(shí)現(xiàn),以調(diào)用 endEditing(_ :) 。

override func viewWillDisappear(_ animated: Bool) {
??super.viewWillDisappear(animated)

??// Clear first responder

??view.endEditing(true)

??// "Save" changes to item
??item.name = nameField.text ?? ""
??item.serialNumber = serialNumberField.text

??if let valueText = valueField.text, let value = numberFormatter.number(from: valueText) {
????item.valueInDollars = value.integerValue
??} else {
????item.valueInDollars = 0
??}
}

UINavigationBar

在本節(jié)中,您將聲明 UINavigationBar, 一個(gè) UIViewController 的描述性標(biāo)題,并正好位于 UINavigationController 的棧頂。

每個(gè) UIViewController 都有一個(gè)類(lèi)型為 UINavigationItemnavigationItem 屬性。 但是,與 UINavigationBar 不同的是,UINavigationItem 不是 UIView 的子類(lèi),所以它不能出現(xiàn)在屏幕上。 相反,導(dǎo)航項(xiàng)目為導(dǎo)航欄提供了需要繪制的內(nèi)容。 當(dāng)一個(gè) UIViewController 到達(dá) UINavigationController 的棧頂時(shí),UINavigationBar 使用 UIViewControllernavigationItem 進(jìn)行配置,如圖14.10所示。

圖14.10 UINavigationItem

默認(rèn)情況下,UINavigationItem 為空。 在最基本的層次上, UINavigationItem 有一個(gè)簡(jiǎn)單的 title 字符串。 當(dāng) UIViewController 移動(dòng)到導(dǎo)航棧頂,并且其 navigationItem 具有有效字符串的 title 屬性時(shí),導(dǎo)航欄將顯示該字符串(圖14.11)。

圖14.11 帶有標(biāo)題的UINavigationItem

ItemsViewController 的標(biāo)題將始終保持不變,因此您可以在故事板本身中設(shè)置其導(dǎo)航項(xiàng)的標(biāo)題。

打開(kāi) Main.storyboard。 雙擊 Items View Controller 上方導(dǎo)航欄的中央以編輯其標(biāo)題。 給它一個(gè)標(biāo)題 “Homepwner”(圖14.12)。

圖14.12 在故事板中設(shè)置標(biāo)題

構(gòu)建并運(yùn)行應(yīng)用程序。 注意導(dǎo)航欄上的字符串 Homepwner。 創(chuàng)建并點(diǎn)按一行,并注意導(dǎo)航欄不再具有標(biāo)題。 將 DetailViewController 的導(dǎo)航項(xiàng)標(biāo)題作為它正在顯示的 Item 的名稱(chēng)是很好的。 因?yàn)闃?biāo)題將取決于正在顯示的 Item,您需要在代碼中動(dòng)態(tài)設(shè)置 navigationItem 的標(biāo)題。

DetailViewController.swift 中,將屬性觀察器添加到更新 navigationItem 標(biāo)題的 item 屬性中。

var item: Item! {
??didSet {
????navigationItem.title = item.name
??}
}
  
構(gòu)建并運(yùn)行應(yīng)用程序。 創(chuàng)建并點(diǎn)按一行,您將看到導(dǎo)航欄的標(biāo)題是您選擇的 Item 的名稱(chēng)。

導(dǎo)航項(xiàng)可以保存多個(gè)標(biāo)題字符串,如圖14.13所示。 每個(gè) UINavigationItem 有三個(gè)可自定義的區(qū)域:一個(gè) leftBarButtonItem,一個(gè) rightBarButtonItem 和一個(gè) titleView。 左和右欄的按鈕項(xiàng)是對(duì) UIBarButtonItem 的實(shí)例的引用,它包含僅可以在 UINavigationBarUIToolbar 上顯示的按鈕的信息。

圖14.13 UINavigationItem

回想一下,UINavigationItem 不是 UIView 的子類(lèi)。 相反, UINavigationItem 封裝了 UINavigationBar 用于配置自身的信息。 類(lèi)似地,UIBarButtonItem 不是視圖,而是保存有關(guān)如何顯示 UINavigationBar 上單個(gè)按鈕的信息。( UIToolbar 還使用 UIBarButtonItem 的實(shí)例配置自身。)

UINavigationItem 的第三個(gè)可定制區(qū)域是它的 titleView。 您可以使用基本字符串作為標(biāo)題,也可以將 UIView 的子類(lèi)置于導(dǎo)航項(xiàng)目的中心。 你不能同時(shí)擁有這兩個(gè)。 如果它適合特定視圖控制器的上下文以具有自定義視圖(例如分段控件或文本字段),則可以將導(dǎo)航項(xiàng)的 titleView 設(shè)置為該自定義視圖。 圖14.13顯示了具有自定義視圖作為其 titleViewUINavigationItem 的內(nèi)置 Maps 應(yīng)用程序的示例。 然而,通常,標(biāo)題字符串就足夠了。

將按鈕添加到導(dǎo)航欄

在本節(jié)中,當(dāng) ItemsViewController 位于棧頂時(shí),您將使用兩個(gè)按鈕項(xiàng)替換表頭視圖中的兩個(gè)按鈕,這些按鈕項(xiàng)將出現(xiàn)在 UINavigationBar 中。 導(dǎo)航條按鈕項(xiàng)(Bar Button Item)具有像 UIControl 的目標(biāo)動(dòng)作機(jī)制一樣的目標(biāo)動(dòng)作對(duì):當(dāng)點(diǎn)擊時(shí),它將該動(dòng)作消息發(fā)送到目標(biāo)。

首先,我們來(lái)處理一個(gè)添加新 item 的按鈕項(xiàng)。 當(dāng) ItemsViewController 位于棧頂時(shí),此按鈕將位于導(dǎo)航欄的右側(cè)。 點(diǎn)擊后,它將添加一個(gè)新的 Item。

在更新故事板之前,需要更改 addNewItem(_ :) 的方法聲明。 目前這種方法是由 UIButton 觸發(fā)的。 現(xiàn)在您正在將發(fā)件人更改為 UIBarButtonItem,您需要更新聲明。

ItemsViewController.swift 中,更新 addNewItem(_ :) 的方法聲明。

@IBAction func addNewItem(_ sender: UIButton) {
@IBAction func addNewItem(_ sender: UIBarButtonItem) {
??...
}

現(xiàn)在打開(kāi) Main.storyboard 然后打開(kāi)對(duì)象庫(kù)。 將 導(dǎo)航條按鈕項(xiàng) 拖動(dòng)到 Items View Controller 導(dǎo)航欄的右側(cè)。 選擇此 導(dǎo)航條按鈕項(xiàng) 并打開(kāi)其屬性檢查器。 將 System Item 更改為 Add (圖14.14)。

圖14.14系統(tǒng)欄按鈕項(xiàng)

右鍵從此 導(dǎo)航條按鈕項(xiàng) 拖動(dòng)到 Items View Controller 并選擇 addNewItem:(圖14.15)。

圖14.15連接 addNewItem:action

構(gòu)建并運(yùn)行應(yīng)用程序。 點(diǎn)擊 + 按鈕,一個(gè)新行將出現(xiàn)在表格中。 現(xiàn)在我們來(lái)更換Edit 按鈕。 視圖控制器包含的導(dǎo)航條按鈕項(xiàng)會(huì)自動(dòng)切換其編輯模式。 沒(méi)有辦法通過(guò) Interface Builder 訪問(wèn)它,因此您需要以編程方式添加該按鈕項(xiàng)。

ItemsViewController.swift 中,重寫(xiě) init(coder :) 方法來(lái)設(shè)置左邊導(dǎo)航條按鈕項(xiàng)。

required init?(coder aDecoder: NSCoder) {

??super.init(coder: aDecoder)

??navigationItem.leftBarButtonItem = editButtonItem
}

構(gòu)建并運(yùn)行應(yīng)用程序,添加一些 item,然后點(diǎn)擊 Edit 按鈕。 UITableView 進(jìn)入編輯模式!editButtonItem 屬性創(chuàng)建了一個(gè)標(biāo)題為 EditUIBarButtonItem。 更好的是,這個(gè)按鈕帶有一個(gè)目標(biāo)動(dòng)作對(duì):在點(diǎn)擊時(shí)調(diào)用它的 UIViewController 上的setEditing(_:animated :)方法。

打開(kāi) Main.storyboard。 現(xiàn)在,Homepwner 具有全功能的導(dǎo)航欄,您可以擺脫標(biāo)題視圖和相關(guān)代碼。 在表視圖上選擇標(biāo)題視圖,然后按 Delete。

此外,UINavigationController 將處理更新表視圖的插值。 在 ItemsViewController.swift 中,修改為以下。

override func viewDidLoad() {
??super.viewDidLoad()

??// Get the height of the status bar
??let statusBarHeight = UIApplication.shared.statusBarFrame.height

??let insets = UIEdgeInsets(top: statusBarHeight, left: 0, bottom: 0, right: 0)
??tableView.contentInset = insets
??tableView.scrollIndicatorInsets = insets

??tableView.rowHeight = UITableViewAutomaticDimension
??tableView.estimatedRowHeight = 65
}

最后,刪除 toggleEditingMode(_ :) 方法。

@IBAction func toggleEditingMode(_ sender: UIButton) {
??// If you are currently in editing mode...
??if isEditing {
????// Change text of button to inform user of state
????sender.setTitle("Edit", for: .normal)
????// Turn off editing mode
????setEditing(false, animated: true)
??} else {
????// Change text of button to inform user of state
????sender.setTitle("Done", for: .normal)
????// Enter editing mode
????setEditing(true, animated: true)
??}
}

建立并再次運(yùn)行。 舊的 EditAdd 按鈕已經(jīng)消失,留下了一個(gè)好看的 UINavigationBar(圖14.16)。

圖14.16 帶導(dǎo)航欄的Homepwner

青銅挑戰(zhàn):顯示數(shù)字鍵盤(pán)

顯示 ItemvalueInDollarsUITextField 的鍵盤(pán)是一個(gè)QWERTY鍵盤(pán)。 如果它是一個(gè)數(shù)字鍵盤(pán)會(huì)更好。 將 UITextFieldKeyboard Type 更改為 Number Pad。 (提示:您可以使用屬性檢查器在 storyboard 文件中執(zhí)行此操作。)

白銀挑戰(zhàn):自定義 UITextField

創(chuàng)建 UITextField 的子類(lèi),并覆蓋 getsFirstResponder()resignFirstResponder() 方法(繼承自 UIResponder),以使其邊框樣式在成為第一個(gè)響應(yīng)者時(shí)更改。 您可以使用 UITextFieldborderStyle 屬性來(lái)完成此操作。 在 DetailViewController 中使用您自定義的文本字段。

黃金挑戰(zhàn):推動(dòng)更多視圖控制器

目前,Item 的實(shí)例不能更改其 dateCreated 屬性。 更改 Item ,使他們可以,然后在 DetailViewController 中的 dateLabel 下面添加一個(gè)帶有 "Change Date" 標(biāo)題的按鈕。當(dāng)點(diǎn)擊此按鈕時(shí),將另一個(gè)視圖控制器實(shí)例 push 到導(dǎo)航堆棧。 此視圖控制器應(yīng)包含一個(gè)要修改的所選 ItemdateCreated 屬性的 UIDatePicker 實(shí)例。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 在本章中,您將要添加照片到 Homepwner 應(yīng)用程序。 您將呈現(xiàn)一個(gè) UIImagePickerControl...
    titvax閱讀 678評(píng)論 0 1
  • 在這本書(shū)中,您一直在使用自動(dòng)布局來(lái)創(chuàng)建靈活的界面,可以跨設(shè)備類(lèi)型和大小進(jìn)行擴(kuò)展。 自動(dòng)布局是一種非常強(qiáng)大的技術(shù),但...
    titvax閱讀 903評(píng)論 0 1
  • 內(nèi)容抽屜菜單ListViewWebViewSwitchButton按鈕點(diǎn)贊按鈕進(jìn)度條TabLayout圖標(biāo)下拉刷新...
    皇小弟閱讀 47,163評(píng)論 22 665
  • 概述 摘要:從制作一個(gè)看圖app和了解關(guān)鍵概念開(kāi)始swift編程。 概念:Constants and variab...
    lbhw閱讀 651評(píng)論 0 1
  • 發(fā)現(xiàn) 關(guān)注 消息 iOS 第三方庫(kù)、插件、知名博客總結(jié) 作者大灰狼的小綿羊哥哥關(guān)注 2017.06.26 09:4...
    肇東周閱讀 15,406評(píng)論 4 61

友情鏈接更多精彩內(nèi)容