馬上著手開發(fā) iOS 應(yīng)用程序 (四) - 連接 UI 和代碼

重要:這是針對(duì)于正在開發(fā)中的API或技術(shù)的預(yù)備文檔(預(yù)發(fā)布版本)。蘋果提供這份文檔的目的是幫助你按照文中描述的方式對(duì)技術(shù)的選擇及界面的設(shè)計(jì)開發(fā)進(jìn)行規(guī)劃。這些信息有可能發(fā)生變化,因此根據(jù)本文檔的軟件開發(fā)應(yīng)當(dāng)基于最終版本的操作系統(tǒng)和文檔進(jìn)行測(cè)試。該文檔的新版本或許會(huì)隨著API或相關(guān)技術(shù)未來(lái)的發(fā)展而進(jìn)行更新。

翻譯自蘋果官網(wǎng):

https://developer.apple.com/library/prerelease/ios/referencelibrary/GettingStarted/DevelopiOSAppsSwift/Lesson3.html#//apple_ref/doc/uid/TP40015214-CH22-SW1

在本課中,連接 FoodTracker app 的 UI 和源代碼并且定義一些用戶可以在 UI 中執(zhí)行的動(dòng)作。當(dāng)完成這些,你的 app 看起來(lái)是這樣:

[圖片上傳失敗...(image-aa239a-1608214806753)]

學(xué)習(xí)目標(biāo)

在課程的最后,你將學(xué)會(huì):

  • 說(shuō)明 storyboard 中的場(chǎng)景和底層的視圖控制器的關(guān)系。
  • 在 storyboard 中的 UI 控件和源代碼之間創(chuàng)建 outlet 和 action 連接
  • 處理文本框中的用戶輸入及在 UI 中顯示結(jié)果
  • 創(chuàng)建一個(gè)類遵循協(xié)議
  • 理解代理設(shè)計(jì)模式
  • 當(dāng)設(shè)計(jì) app 結(jié)構(gòu)時(shí)遵循 target-action 模式

連接 UI 和源代碼

storyboard 中控件關(guān)聯(lián)著源代碼。理解 storyboard 和你寫的代碼間的關(guān)系非常重要。

在 storyboard 中,一個(gè)場(chǎng)景代表一個(gè)屏幕的內(nèi)容通常是個(gè)視圖控制器。視圖控制器實(shí)現(xiàn) app 的功能并管理內(nèi)容視圖和它的各級(jí)子類,同時(shí)協(xié)調(diào)封裝數(shù)據(jù)的模型層和顯示數(shù)據(jù)的視圖層之間的信息流,管理它們的內(nèi)容視圖的生命周期,當(dāng)設(shè)備旋轉(zhuǎn)時(shí)處理方向變化,在 app 中定義導(dǎo)航,響應(yīng)用戶輸入。所有的 iOS 視圖控制器對(duì)象都是 UIViewController 和它的子類型。

通過(guò)在代碼中創(chuàng)建和實(shí)現(xiàn)自定義控制器類來(lái)定義功能。在這個(gè)類和 storyboard 場(chǎng)景間創(chuàng)建連接,這樣你就同時(shí)得到了代碼中的功能和 storyboard 中的 UI。

Xcode 已經(jīng)在之前創(chuàng)建了這樣的類了,ViewController.swift,把它連接到之前定義的場(chǎng)景。未來(lái),你會(huì)遇到更多的場(chǎng)景,那時(shí)你得自己在識(shí)別檢查器(Identity inspector) 中連接它們。識(shí)別檢查器讓你能夠編輯 storyboard 中對(duì)象的與身份相關(guān)屬性,例如對(duì)象屬于哪個(gè)類。

[圖片上傳失敗...(image-79ebd7-1608214806753)]

運(yùn)行時(shí),storyboard 會(huì)創(chuàng)建自定義 ViewController 的實(shí)例,app 顯示 storyboard 中定義的 UI 并執(zhí)行 ViewController.swift 中定義的一些功能。

盡管場(chǎng)景成功連接到了 ViewController.swift,但這不是唯一需要連接的。為了在 app 中定義交互,控制器源代碼需要與 storyboard 中視圖關(guān)聯(lián)。通過(guò)在視圖和控制器源代碼間定義 outlets 和 acitons 來(lái)實(shí)現(xiàn)關(guān)聯(lián)。

為 UI 控件創(chuàng)建 outlets

outlets 是在代碼中引用界面對(duì)象的一種方式。為了創(chuàng)建 outlet,按住 control 從 storyboard 中對(duì)象往控制器文件拖拽。這會(huì)在控制器中創(chuàng)建對(duì)象屬性,讓你能在運(yùn)行時(shí)訪問(wèn)這個(gè)屬性。

你需要為 UI 中文本框和標(biāo)簽創(chuàng)建 outlets 來(lái)引用它們。

連接文本框和 ViewController.swift 代碼
  1. 打開 Main.storyboard。

  2. 點(diǎn)擊 Xcode 右上角工具欄中的 Assistant 按鈕來(lái)打開輔助編輯器。

    [圖片上傳失敗...(image-4d4962-1608214806753)]

  3. 如果你想要更多空間來(lái)工作,請(qǐng)點(diǎn)擊 Xcode 工具欄中的 Navigator 和 Utilities 按鈕來(lái)收縮項(xiàng)目導(dǎo)航和實(shí)用工具區(qū)。

    [圖片上傳失敗...(image-7d873c-1608214806753)]

    同樣可以收縮大綱視圖

  4. 在輔助編輯器頂部的編輯器選擇欄,從 Preview 修改輔助編輯器為 Automatic > ViewController.swift。

    [圖片上傳失敗...(image-2016db-1608214806753)]

    ViewController.swift 會(huì)在右邊的編輯器中顯示。

  5. 在 ViewController.swift 中,找到如下的 class 行:

     class ViewController: UIViewController {
    
  6. 在 class 行下面,添加如下行:

     // MARK: Properties
    

    這僅僅向源代碼添加了一行注釋。回憶一下,注釋是代碼中不會(huì)被編譯的文本,但提供了有關(guān)代碼的上下文說(shuō)明信息。

    以 // MARK: 開頭的的注釋是種很特別的注釋,讓你(或任何其他讀你代碼的人)組織代碼并進(jìn)行導(dǎo)航的。你稍后會(huì)在 action 一節(jié)中再次看到它的。具體來(lái)說(shuō),用它來(lái)分割邏輯代碼區(qū)域。

  7. 在 storyboard 中,選擇文本框。

  8. 按住 Control 從畫板中的文本框拖動(dòng)到右邊編輯器中的代碼。在剛剛添加的注釋下面停止拖動(dòng)。

    [圖片上傳失敗...(image-d4683e-1608214806753)]

  9. 在打開的對(duì)象框中,輸入名字 nameTextField。
    忽略其他的選項(xiàng)。最后對(duì)話框應(yīng)該像這樣:

    [圖片上傳失敗...(image-42f1bf-1608214806753)]

  10. 點(diǎn)擊 Connect
    Xcode 向 ViewController.swift 中添加必要的代碼來(lái)存儲(chǔ)文本框的指針并且配置 storyboard 來(lái)設(shè)置剛才的連接。

    @IBOutlet weak var nameTextField: UITextField!
    

花點(diǎn)時(shí)間理解這行代碼發(fā)生了什么。

IBOutlet 屬性告訴 Xcode 讓你可以從 IB 中連接 nameTextField 屬性(這就是為什么屬性有 IB 前綴)。 weak 關(guān)鍵字意味著屬性在生命周期某些時(shí)候可能沒有值(是 nil)。剩下的部分定義了一個(gè)叫 nameTextField 的文本框。

注意最后的 !,它表明類型是隱式解析可選,這是一種在第一次賦值后就一直存在值的可選類型。

現(xiàn)在,以同樣的方式連接標(biāo)簽和你的代碼。

連接標(biāo)簽和 ViewController.swift代碼
  1. 在 storyboard 中,選擇標(biāo)簽。

  2. 按住 Control 從畫板中的標(biāo)簽拖動(dòng)到右邊編輯器的代碼中,在 ViewController.swift 中 nameTextField 屬性的下面的行停止拖動(dòng)。

    [圖片上傳失敗...(image-3c546e-1608214806753)]

  3. 在出現(xiàn)的對(duì)話框中,輸入名字 mealNameLabel。忽略其他選項(xiàng)。你的對(duì)話框應(yīng)該是這樣:

    [圖片上傳失敗...(image-a4559a-1608214806753)]

  4. 點(diǎn)擊 Connect。

再一次,Xcode 往 ViewController.swift 中添加了必要的代碼來(lái)存儲(chǔ)標(biāo)簽的指針并配置 storyboard 來(lái)設(shè)置連接。這個(gè) outlet 除了名字和類型其他都類似之前文本框的。

@IBOutlet weak var mealNameLabel: UILabel!

當(dāng)使用這種方式成功在代碼中引用界面控件,你需要定義事件來(lái)與這些控件觸發(fā)交互。這就是 actions 的由來(lái)。

定義一個(gè)動(dòng)作(action)來(lái)執(zhí)行

iOS app 是基于事件驅(qū)動(dòng)的程序。那就是,app 的流程是由系統(tǒng)事件和用戶動(dòng)作決定的。用戶在界面中執(zhí)行動(dòng)作并觸發(fā)事件。這些事件導(dǎo)致 app 邏輯的執(zhí)行和相應(yīng)的數(shù)據(jù)處理。app 響應(yīng)用戶動(dòng)作并反饋到 UI。當(dāng)確定代碼塊執(zhí)行時(shí),因?yàn)?app 是用戶而不是開發(fā)者在控制的,所以在代碼中標(biāo)示用戶可以執(zhí)行哪個(gè)動(dòng)作以及怎么響應(yīng)這些動(dòng)作。

動(dòng)作(或動(dòng)作方法)是 app 中關(guān)聯(lián)事件的一部分代碼塊。當(dāng)事件發(fā)生了,對(duì)應(yīng)代碼得到執(zhí)行。你可以定義一個(gè)動(dòng)作方法來(lái)完成從操作一塊數(shù)據(jù)到更新 UI 的任何事情。使用動(dòng)作驅(qū)動(dòng) app 的流程來(lái)響應(yīng)用戶或系統(tǒng)的事件。

像創(chuàng)建 outlet 一樣創(chuàng)建 action:按住 Control 從一個(gè) storyboard 中特定對(duì)象向視圖控制器文件拖動(dòng)。這步操作會(huì)在視圖控制器中創(chuàng)建一個(gè)方法,當(dāng)用戶與這個(gè)方法關(guān)聯(lián)的對(duì)象交互時(shí)就會(huì)觸發(fā)這個(gè)方法。

通過(guò)創(chuàng)建一個(gè)簡(jiǎn)單的動(dòng)作:當(dāng)用戶點(diǎn)擊 Set Default Label Text 按鈕,設(shè)置標(biāo)簽為默認(rèn)值,Default Text(設(shè)置標(biāo)簽為文本框中文本的代碼有點(diǎn)復(fù)雜,所以將在下一節(jié)講解。)

在 ViewController.swift 代碼中創(chuàng)建標(biāo)簽重置動(dòng)作
  1. 在 ViewController.swift 的最后一個(gè)大括號(hào)(}) 前面,添加如下注釋:

     // MARK: Actions
    

    這行注釋表明以下是寫 actions 的區(qū)域。

  2. 在 storyboard 中, 選擇 Set Default Label Text 按鈕。

  3. 按住 Control 從畫板中的 Set Default Label Text 按鈕拖動(dòng)到右邊編輯器代碼中,在剛才的注釋行下面停止拖動(dòng)。

    [圖片上傳失敗...(image-7fabbf-1608214806753)]

  4. 在彈出的對(duì)話框中 Connection 那一行選擇 Action。

  5. Name 那一行輸入 setDefaultLabelText 。

  6. Type 那一行,選擇 UIButton。

    你或許注意到文本框的默認(rèn)值 AnyObject。在 Swift 中, AnyObject 類型用于描述屬于任何類的對(duì)象。指定 action 方法的類型為 UIButton 意味著只有按鈕對(duì)象可以連接這個(gè) action。盡管對(duì)現(xiàn)在創(chuàng)建的這個(gè)動(dòng)作沒有什么意義,但是記住這點(diǎn)很重要。

    忽略剩下的選項(xiàng)。你的對(duì)話框應(yīng)該是這樣:

    [圖片上傳失敗...(image-8411e2-1608214806753)]

  7. 點(diǎn)擊 Connect。

Xcode 向 ViewController.swift 中添加必要的代碼來(lái)設(shè)置 action 方法。

@IBAction func setDefaultLabelText(sender: UIButton) {
}   

sender 參數(shù)指向負(fù)責(zé)觸發(fā)動(dòng)作的對(duì)象-當(dāng)前是這個(gè)按鈕。 IBAction 屬性表明這個(gè)方法是個(gè) action 可以在 storyboard 中連接這個(gè)方法。剩下的定義就是方法名字 setDefaultLabelText(_:) 。

方法實(shí)現(xiàn)是空的。設(shè)置標(biāo)簽值為空非常簡(jiǎn)單。

在 ViewController 代碼中實(shí)現(xiàn)重置標(biāo)簽文本動(dòng)作
  1. 在 VieController.swift 中,找到剛才添加的setDefaultLabelText 動(dòng)作方法。

  2. 在大括號(hào)({})之間的代碼實(shí)現(xiàn)中,添加這行代碼:

     mealNameLabel.text = "Default Text"
    

    或許你會(huì)猜到,這句代碼就是設(shè)置標(biāo)簽的文本為默認(rèn)值。
    注意到你沒有指定 Default Text 的類型,因?yàn)?Swift 類型推斷發(fā)現(xiàn)你給標(biāo)簽賦值 String 類型的值所以可以正確推斷類型。

iOS 系統(tǒng)為你默認(rèn)執(zhí)行了所有的重繪操作,所以你只需寫這句代碼就行了。你的 setDefaultLabelText(_:) 方法最后應(yīng)該是這樣:

@IBAction func setDefaultLabelText(sender: UIButton) {
    mealNameLabel.text = "Default Text"
}

檢驗(yàn): 運(yùn)行模擬器測(cè)試你的修改。當(dāng)點(diǎn)擊 Set Default Label Text 按鈕,標(biāo)簽的文本應(yīng)該從 Meal Name(storyboard 中設(shè)置的值) 變?yōu)?Default Text (動(dòng)作設(shè)置的值)。

[圖片上傳失敗...(image-8d8d5e-1608214806753)]

你剛才的行為是 iOS app target-action 設(shè)計(jì)模式的一個(gè)例子。當(dāng)特定的事件發(fā)生相應(yīng)對(duì)象就會(huì)發(fā)送消息給另外一個(gè)對(duì)象。在這個(gè)例子中,事件就是用戶點(diǎn)擊 Set Default Label Text 按鈕,動(dòng)作就是 setDefaultLabelText,target 就是 ViewController,sender 就是 Set Default Label Text 按鈕。消息就是在源代碼中定義的動(dòng)作方法,target 就是接受消息并有能力執(zhí)行動(dòng)作的對(duì)象。發(fā)送動(dòng)作消息通常是 control 對(duì)象,例如按鈕,slider 或 switch - 它們可以觸發(fā)事件來(lái)響應(yīng)用戶的交互如點(diǎn)擊,拖動(dòng)或值改變。這種設(shè)計(jì)模式在 iOS app 開發(fā)中很常見,后面的課程你會(huì)看到更多的例子。

處理用戶輸入

使用這種方式重置標(biāo)簽的默認(rèn)值,繼續(xù)添加一個(gè)功能來(lái)設(shè)置標(biāo)簽為文本框中的值。我們簡(jiǎn)單處理,當(dāng)用戶點(diǎn)擊文本框鍵盤上的 Return 按鈕,更新標(biāo)簽的值。

當(dāng)使用一個(gè)文本框來(lái)接受用戶輸入,你需要一個(gè)文本框代理的幫助。一個(gè) delegate 代表一個(gè)對(duì)象去執(zhí)行一些任務(wù)。代理對(duì)象 - 在本例中是這個(gè)文本框會(huì)保持代理的引用。在合適的時(shí)間它會(huì)發(fā)送消息給代理。告訴代理它將要處理或剛剛已經(jīng)處理過(guò)的事件。代理可能做一些響應(yīng)如更新它自己或 app 中其他對(duì)象的外觀和狀態(tài),或者返回值來(lái)影響即將到來(lái)的事件的處理。

只要遵循合適的協(xié)議任何對(duì)象都可以作為代理為另一個(gè)對(duì)象服務(wù)。文本框的代理協(xié)議叫做 UITextFieldDelegate。在這個(gè)例子中,因?yàn)?ViewController 保持了文本框的引用,設(shè)置 ViewController 為文本框的代理。

通過(guò)把它列在類定義的后面讓 ViewController 遵循 UITextFieldDelegate 協(xié)議。

遵循 UITextFieldDelegate 協(xié)議
  1. 在打開的輔助編輯器中,點(diǎn)擊 Standard 按鈕返回標(biāo)準(zhǔn)的編輯器。

    [圖片上傳失敗...(image-df0973-1608214806753)]
    點(diǎn)擊 Xcode 工具欄的 Navigator 和 Utilities 按鈕來(lái)展開項(xiàng)目導(dǎo)航和實(shí)用工具區(qū)。

  2. 在項(xiàng)目導(dǎo)航中,選擇 ViewController.swift。

  3. 在 ViewController.swift 中,找到如下的 class 行:

     class ViewController: UIViewController {
    
  4. 在 UIViewController 后面添加一個(gè) , 和 UITextFieldDelegate 來(lái)遵循這個(gè)協(xié)議。

     class ViewController: UIViewController, UITextFieldDelegate {
    

通過(guò)遵循這個(gè)協(xié)議,ViewController 就有能力確認(rèn)自己是 UITextFieldDelegate。意味著你可以設(shè)置它作為文本框的代理并實(shí)現(xiàn)處理用戶文本框輸入的功能。

設(shè)置 ViewController 作為 nameTextField 的代理
  1. 在 ViewController.swift 中,找到如下的 ViewDidLoad() 方法:

     override func viewDidLoad() {
         super.viewDidLoad()
         // Do any additional setup after loading the view, typically from a nib.
     }
    

    模板方法的實(shí)現(xiàn)包含一行注釋。我們不需要它,所以刪掉。

  2. 在 super.viewDidLoad() 行下面,添加一個(gè)如下代碼:

     // Handle the text field’s user input through delegate callbacks.
     nameTextField.delegate = self
    

    self 指的是 ViewController 類,因?yàn)樗窃?ViewController 類的定義范圍內(nèi)引用的。

    可以添加自己的注釋來(lái)幫助理解代碼的邏輯。

viewDidLoad() 方法應(yīng)該像這樣:

override func viewDidLoad() {
    super.viewDidLoad()
    
    // Handle the text field’s user input through delegate callbacks.
    nameTextField.delegate = self
}

ViewController 現(xiàn)在是 nameTextField 的一個(gè)代理了。

UITextFieldDelegate 協(xié)議包含可選方法,意味著它們不是必須要實(shí)現(xiàn)的。但是為了得到你想的特定的行為,需要實(shí)現(xiàn)它們中的兩個(gè)方法:

func textFieldShouldReturn(textField: UITextField) -> Bool
func textFieldDidEndEditing(textField: UITextField)

為了理解什么時(shí)候調(diào)用這些方法,首先需要知道文本框怎么響應(yīng)用戶事件。當(dāng)用戶點(diǎn)擊一個(gè)文本框,文本框自動(dòng)變成第一響應(yīng)者。而第一響應(yīng)者是對(duì)象集合中第一個(gè)接收 app 事件的對(duì)象,事件包括鍵盤事件,移動(dòng)事件和動(dòng)作消息,當(dāng)然還有其他的。換言之,用戶的事件優(yōu)先傳給第一響應(yīng)者。

當(dāng)文本框變成第一響應(yīng)者時(shí),iOS 會(huì)顯示鍵盤以及一個(gè)編輯區(qū)域。讓用戶使用鍵盤而不是文本框輸入文字。

當(dāng)用戶完成編輯,文本框應(yīng)該放棄第一響應(yīng)者狀態(tài)。點(diǎn)擊 Return(本例是 Done) 按鈕來(lái)結(jié)束在文本框中的編輯,相應(yīng)的 textFieldShouldReturn(_:) 方法觸發(fā)了

實(shí)現(xiàn) UITextFieldDelegate 協(xié)議方法 textFieldShouldReturn(_:)
  1. 在 ViewController.swift 文件中 // MARK: Actions 的右上角,添加如下注釋:

     // MARK: UITextFieldDelegate
    

    這行注釋是用來(lái)組織代碼并幫助你(或任何其他讀你代碼的人)導(dǎo)航。

    到目前為止添加了一些注釋。Xcode 在 Functions menu 區(qū)域列舉了每個(gè)注釋,點(diǎn)擊標(biāo)題來(lái)跳到對(duì)應(yīng)的區(qū)域。

    [圖片上傳失敗...(image-90fcdd-1608214806753)]

  2. 在注釋的下面,添加如下方法:

     func textFieldShouldReturn(textField: UITextField) -> Bool {
     }
    
  3. 在這個(gè)方法中,添加如下代碼來(lái)放棄文本框的第一響應(yīng)者狀態(tài),并添加注釋描述代碼做了什么:

     // Hide the keyboard.
     textField.resignFirstResponder()
    

    不要復(fù)制粘貼而是自己輸入第二行的代碼。當(dāng)輸入時(shí) Xcode 會(huì)彈出代碼提示列表,滾動(dòng)這個(gè)列表找到你想要的那項(xiàng)然后回車確認(rèn)選擇。Xcode 代碼提示真是非常牛逼和省時(shí)的功能。

    [圖片上傳失敗...(image-ada5fc-1608214806753)]

  4. 在方法中,添加如下代碼行。

     return true
    

    方法返回一個(gè)布爾值 true 表示文本框能夠響應(yīng)用戶點(diǎn)擊 Return 鍵來(lái)關(guān)閉鍵盤的操作。

textFieldShouldReturn(_:) 方法最后應(yīng)該像這樣:

func textFieldShouldReturn(textField: UITextField) -> Bool {
    // Hide the keyboard.
    textField.resignFirstResponder()
    return true
}

第二個(gè)需要實(shí)現(xiàn)的方法是 textFieldDidEndEditing(_:),在文本框放棄第一響應(yīng)者狀態(tài)時(shí)被調(diào)用。也就是在你剛實(shí)現(xiàn)的 textFieldShouldReturn 后面。

你可以在 textFieldDidEndEditing(_:) 方法中讀取剛才輸入到文本框中的文本信息。在這個(gè)例子中,你會(huì)得到文本框的文本并使用它來(lái)修改標(biāo)簽的值。

實(shí)現(xiàn) UITextFieldDelegate 協(xié)議的方法 textFieldDidEndEditing(_:)
  1. 在 ViewController.swift 的 textFieldShouldReturn(_:) 方法后面,添加如下方法:

     func textFieldDidEndEditing(textField: UITextField) {
     }
    
  2. 在方法中,添加如下代碼:

     mealNameLabel.text = textField.text
    

最后 textFieldDidEndEditing(_:) 方法應(yīng)該像這樣:

func textFieldDidEndEditing(textField: UITextField) {
    mealNameLabel.text = textField.text
}   

檢驗(yàn): 運(yùn)行模擬器測(cè)試你的修改。選擇文本框并輸入文本。當(dāng)你點(diǎn)擊鍵盤上的完成按鈕,鍵盤消失然后會(huì)在標(biāo)簽中顯示文本框中輸入的文本。當(dāng)點(diǎn)擊 Set Default Label Text 按鈕,標(biāo)簽從當(dāng)前顯示的文本變?yōu)?Default Text。

[圖片上傳失敗...(image-e7b4e-1608214806753)]

注意

為了查看本課的完整實(shí)例項(xiàng)目,下載文件并在 Xcode 中查看它。

最后編輯于
?著作權(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)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

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