在本課中,你將連接FoodTracker應用的基本UI到代碼,并定義一些用戶可以在這UI上進行的操作。完成后,應用將如下所示:
學習目標
結束本課時,你將能夠:
- 解釋storyboard場景和底層控制器之間的關系。
- 在storyboard中的UI元素和源代碼之間創建outlet(插座)和action(行動)。
- 處理用戶在text filed上的輸入并將結果顯示在UI上。
- 使一個類符合協議
- 理解代理模式
- 在設計應用架構的時候,遵從目標-動作模式。
連接UI到源代碼上
storyboard中的元素被鏈接到源代碼。了解故事板與您編寫代碼的關系很重要。
在storyboard中,一個場景顯示一屏幕的內容,通常也表示一個視圖控制器。視圖控制器實現應用的行為。一個視圖控制器管理一個單一的內容視圖,這個內容視圖又有著它自己的子視圖層次結構。視圖控制器協調應用數據模型(data model,它封裝應用的數據)和顯示這些數據的視圖之間的信息流,管理它們的內容視圖的生命周期,處理當設備旋轉時的方向改變,定義應用的導航,以及實現對用戶輸入的響應行為。所有iOS的視圖控制器對象都是UIViewController及其子類的對象。
你可以通過創建和實現自定義的視圖控制器子類,進而使用代碼來定義視圖控制器的行為。稍后你將創建一個在這些類和場景之間的連接,用來得到定義在代碼中的行為和定義在storyboard中的用戶界面。
Xcode已經創建了一個你之前看過的類,ViewController.swift,并且已將其連接到storyboard中的那個場景。將來,你會添加更多場景,你要自己在Identity inspector(身份檢查器)中建立連接。Identity inspector讓你編輯storyboard中的一個對象的屬性,這個屬性和這個對象的身份有關聯,例如這個對象屬于哪個類。
在運行時,storyboard創建一個ViewController的實例,你的自定義視圖控制器子類。storyboard中的場景出現在設備的屏幕上,而用戶界面的行為定義在了ViewController.swift中。
雖然場景連接到了ViewController.swift, 但那不是唯一需要連接的。為了定義應用的交互,你的視圖控制器源代碼需要能夠和storyboard中的視圖進行通信。你可以在storyboard的視圖和視圖控制器的源代碼之間定義額外的的被稱為outlet和action的連接來做到。
為UI元素創建Outlet
Outlet提供了一個從源代碼文件引用界面對象(添加在storyboard中的對象)的方式。要創建一個outlet,按住Control鍵,從storyboard的特定對象拖拽到視圖控制器文件。這個操作會在視圖控制文件中創建一個該對象的property,這個property讓你能夠在運行時從代碼訪問和操作這個對象。
你將創建用戶界面中的text filed和label的outlet,從而可以引用它們。
連接text field到ViewController.swift代碼
- 打開storyboard,Main.storyboard.
-
點擊Xcode工具條靠近Xcode右上角的Assistant按鈕來打開助理編輯器(assistant editor)。
image: ../Art/assistant_editor_toggle_2x.png -
如果更多的空間,點擊Xcode工具條上的Navigator和Utilities按鈕把project navigator和utility area折疊起來。
image: ../Art/navigator_utilities_toggle_on_2x.png
你也可以折疊outline view。
-
在編輯器選擇器欄中,它出現在助理視圖的頂部,把助理視圖從Previw to Automatic轉換到ViewController.swift。
image: ../Art/CUIC_switchtoviewcontroller_2x.png
ViewController.swift顯示在右側的編輯器中
- 在ViewController.swift中,找到class那一行。它看上去像這樣
class ViewController: UIViewController
- 在class行的下面,輸入:
//MARK: Properties
你剛剛添加了一條注釋到你的代碼。回想一下,注釋時源代碼文件的一段文本,不會被編譯為程序的一部分,但是它提供了關于特定部分代碼的上下文或者有用信息。
一條從//MARK:開始的注釋是一種特殊類型的注釋,它通常用來組織你的代碼并幫助你(或其他讀你代碼的人)瀏覽它。你可以稍后看到這種行為。具體來說,這段你添加的注釋表明,這是一部分羅列屬性的代碼。
- 在storyboard中,選擇text field。
-
按住Control從畫布拖拽text filed到右側編輯器的代碼中,拖到剛添加的注釋的下面然后停止。
image: ../Art/CUIC_textfield_dragoutlet_2x.png -
在彈出的對話框總,Name選擇nameTextField。其他選項保持原樣。對話框應該如下所示:
image: ../Art/CUIC_textfield_addoutlet_2x.png - 點擊Connect。
Xcode添加必要的代碼到ViewController.swift來存儲一個指向text filed的引用,并且配置storyboard來設置這個連接。
@IBOutlet weak var nameTextField: UITextField!
花點時間理解一下這行代碼做了什么。
IBOutlet屬性告訴Xcode你能從Interface Builder(這就是為什么屬性有IB前綴的原因)連接到nameTextField屬性。weak關鍵字表明這個引用不會阻止系統釋放被引用的對象。Weak引用幫助防止引用環的產生;然而,為了保持對象在內存中存在,你需要確保應用的其他部分對這個對象有強引用。在這種情況下,它是text field的超視圖。一個超視圖有著其所有子視圖的強引用。只要超視圖還存在于內存中,那么所有它的子視圖也都存在。類似的,視圖控制器對它的內容視圖有一個強引用——保持整個視圖層次結構存在于內存中。
聲明的剩余部分定義了一個名為nameTextField的UITextField類型變量,這個變量是一個隱式解包可選變量(implicitly unwrapped optional).特別注意聲明結尾處的感嘆號。這個感嘆號表明這個類型是隱式解包可選類型,它是一個在第一次設置后就會一直有一個值的可選類型。當你訪問一個隱式解包可選類型對象時,系統會認為它有一個有效的值并自動為你解包。注意,如果這個變量的值還沒有被設置,那么應用會終止。
當一個視圖控制器從storyboard被加載時,系統實例化視圖層次結構,并分配合適的值給所有視圖控制器的outlets。這時,視圖控制器的viewDidLoad()方法被調用,因為系統已經為所有控制器的outlets分配了有效的值,所以你可以安全的訪問它們的內容。
現在,用連接text field相同的方式連接label到你的代碼。
連接label到ViewController.swift代碼
- 在storyboard中,選中label。
-
按住Control從畫布拖拽label到右側編輯器的代碼中,拖到nameTextField屬性的下面然后停止。
image: ../Art/CUIC_label_dragoutlet_2x.png -
在彈出的對話框總,Name選擇mealNameLabel。其他選項保持原樣。對話框應該如下所示:
image: ../Art/CUIC_label_addoutlet_2x.png - 點擊Connect。
再次,Xcode添加必要的代碼到ViewController.swift來存儲一個指向label的引用,并且配置storyboard來設置這個連接。這個outlet類似于text field的那個,除了名字和類型有所區別(這次的類型是UILabel,與storyboard中的對象類型相匹配)。
@IBOutlet weak var mealNameLabel: UILabel!
如果你打算訪問來自接口對象的值或修改代碼中的接口對象,那你只需要一個接口對象的outlet。這種情況,你需要設置text field的delegate屬性和label的text屬性。你不需要修改button,所以沒有必要為它創建一個outlet。
Outlets讓你在代碼中引用界面元素,但當用戶和元素交互時,你仍需要一種方式來響應。現在來說說行動。
定義一個Action來執行
iOS應用是基于事件驅動編程(event-driven programming)的。也就是說,應用的工作流取決于事件:系統事件和用戶行為。用戶在界面中實施一個行為會觸發應用的事件。這些事件會引起應用邏輯的執行和應用數據的操作。應用對于用戶行為的響應之后會反映到用戶界面。當應用的某些代碼執行的時候,因為是用戶(而不是開發人員)在控制,所以你需要確定哪些動作(action)用戶能執行以及這些動作的響應是什么。
一個action(動作,或稱為動作方法)是一段代碼,它連接到一個可以在應用中發生的事件。當這個事件發生時,系統執行這個action的代碼。你能定義一個action方法來完成從操作一段數據到更新用戶界面的所有事。你使用action方法來驅動應用的工作流,來響應用戶或系統的事件。
你可以用創建outlet的相同方式創建一個action:按住Control鍵從storyboard中拖拽一個特定的對象到視圖控制器文件。這個操作會在視圖控制器文件中創建一個方法,它會在用戶與這個附加有action方法的對象交互時被觸發。
創建一個簡單action方法,這個方法可以讓用戶在任何時候點擊Set Default Text按鈕時把leable設置為默認文本。(把lebel的文本設置為text filed中的文本有點復雜,我們將在處理用戶輸入Process User Input部分寫它們。)
在ViewController.swift中創建名為setDefaultLabelText的action方法
- 在ViewController.swift中,在花括號(})的上面,添加下面這個注釋
//MARK: Actions
這行注釋表明,這個部分的代碼是羅列actions 方法的。
- 在storyboard中,選擇Set Default Label Text 按鈕。
-
按住Control鍵,從畫布中拖拽Set Default Label Text按鈕到右側編輯器中的顯示代碼處,到剛添加的注釋的下面時停止拖拽。
image: ../Art/CUIC_button_dragaction_2x.png - 在出現的對話框中,Connection選擇Action。
- Name,輸入setDefaultLabelText。
-
Type,選擇UIButton。
你或許注意到,Type字段的默認值為AnyObject。在Swift中,AnyObject是用來描述一個對象可以屬于認可類的類型。這個action方法指定了UIButton類型,表明這個方法只能和button對象連接。雖然這個對于你現在創建的方法不是很重要,但是記住它對以后很重要。
不更改其余的選項。現在你的對話框應該是這樣的:
image: ../Art/CUIC_button_addaction_2x.png - 點擊連接。
Xcode添加必要的代碼到ViewController.swift來設置這個action方法。
@IBAction func setDefaultLabelText(_ sender: UIButton) {
}
Sender參數引用的對象是觸發這個action的對象,在本例中是一個按鈕。IBAction屬性表明這是一個action方法,你可以從Interface Builder中的storyboard連接到它。剩下的聲明聲明了這個方法的名字:setDefaultLabelText(_:)。
現在,方法聲明是空的。重設label值的代碼非常簡單。
在ViewController中實現label重設action方法
- 在ViewController.swift中找到你剛添加的setDefaultLabelText action方法。
- 在兩個花括號之間,添加如下代碼
mealNameLabel.text = "Default Text"
就像你猜的,這個代碼設置label的text屬性為Default Text。注意,你無需指定Default Text的類型,因為Swift的類型推斷能夠看到你正在分配String類型,并且能夠正確的推斷類型。
iOS為你處理所有重繪代碼,所以,這就是現在你需要寫的所有代碼。你的setDefaultLabelText(_:) action方法看上去就像這樣:
@IBAction func setDefaultLabelText(_ sender: UIButton) {
mealNameLabel.text = "Default Text"
}
檢查點:運行模擬器來檢查改變。當點擊Set Default Label Text按鈕時,你的setDefaultLabelText(_:)方法被調用,mealNameLabel對象的text值從Meal Name變為Default Text。你應該可以在你的用戶界面上看到這種改變。
讓菜品名變為“Default Text”沒有什么大用,但是它說明了一個重要的點。你剛剛實現的行為是iOS的目標動作(target-action)設計模式的一個例子。目標動作模式是一種當指定行為發生的時候一個對象給另一個對象發送消息的設計模式。
在本例中:
- 事件是用戶點擊Set Default Label Text按鈕。
- 動作是setDefaultLabelText(_)。
- 目標是ViewController(action方法被定義的地方)。
- 發送者是Set Default Label Text按鈕
系統通過調用目標中的action方法和傳遞發送者(sender)對象來發送消息。Sender通常是控件,例如按鈕、滑塊(slider)、或開關(switch),這些控件能夠觸發事件來響應用戶的點擊、拖拽和改變值等交互操作。這種模式在iOS 應用編程時非常常見,你在本課中還將看到大量這種模式。
(未完待續......)