馬上著手開發 iOS 應用程序 (五) - 使用視圖控制器

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

翻譯自蘋果官網:

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

本課中,繼續構建食物界面并使用 image picker 往界面中添加照片。完成的 app 應該像這樣:

[圖片上傳失敗...(image-474146-1608214824102)]

學習目標:

在課程的最后,你將學會:

  • 理解視圖控制器生命周期回調,如 viewDidLoad、viewWillAppear 和 viewDidApper
  • 在控制器間傳遞數據
  • 關閉視圖控制器
  • 使用手勢識別器
  • 基于 UIViewController 類層次結構預期對象功能
  • 使用 asset catalog 向項目中添加圖片

理解視圖控制器生命周期

到目前為止, app 只有單個場景, 相應的 UI 由單個控制器管理著。隨著 app 越來越復雜,你會需要處理更多的場景,當在屏幕上加載這些場景需要管理視圖的加載和卸載。

UIViewController 和它的子類伴隨著一系列視圖層次的方法。當控制器狀態轉換時候 iOS 自動調用這些方法。當創建控制器子類(例如 ViewController),它繼承了 UIViewController 類的方法能讓你為每個方法實現自定義的行為。理解這些方法什么時候調用很重要,這樣可以在適當的時候設置或移除正在顯示的視圖。

[圖片上傳失敗...(image-a2390b-1608214824102)]

UIViewController 方法像如下被調用:

  • viewDidLoad() - 在控制器內容視圖(視圖層次最頂層)從 storyboardy 中創建和加載時被調用。此方法用于初始設置。然而,由于視圖可能因為資源有限而被清除,所以不能保證只被調用一次。
  • viewWillAppear() - 它總是在內容視圖出現在屏幕前立刻被調用。
  • viewDidAppear() - 它在內容視圖出現在屏幕后立即執行,在此方法中可以執行如獲取數據或顯示動畫的操作。

看上面的狀態轉換圖中,這三個方法都有對應的銷毀方法。

回憶一下。之前已經在 ViewController 的 viewDidLoad() 方法中寫了一些代碼了:

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

把控制器作為視圖和數據模型間的通信管道,這種方式的 app 設計稱為 MVC(模型 - 視圖 - 控制器)。在此模式中,模型跟蹤你的 app 數據,視圖顯示你的用戶界面和 app 內容,而控制器管理視圖并響應用戶的行為,從數據模型中取出數據填充視圖。MVC 是 iOS app 的核心設計模式。到目前為止, app 一直順著 MVC 原理開發的。

在這之后的 app 開發中多多考慮使用它。是時候讓你的基礎界面上一個臺階了,為食物場景創建最后的布局。

添加食物照片

為完成食物場景界面,下一步,添加食物的照片。這就用到了 image View(UIImageView)這個顯示圖片的的控件。

添加 image view 到場景中
  1. 打開 Main.storyboard。

  2. 在實用工具區打開對象庫(或者選擇 View > Utilities > Show Object Library )

    [圖片上傳失敗...(image-96895d-1608214824102)]

  3. 在對象庫中的搜索框中輸入 image view 迅速找到 Image View 對象。

  4. 從對象庫拖動 Image View 到堆棧視圖中按鈕的下面。

    [圖片上傳失敗...(image-c92db-1608214824102)]

  5. 選中 image view,在實用工具區中打開尺寸檢查器。

    [圖片上傳失敗...(image-9d5951-1608214824102)]

  6. 在 Intrinsic Size 區域,選擇 Placeholder。(它在尺寸檢查器的底部,需要滾動才能看到。)

  7. 寬度和高度區域都輸入 320。回車確認。
    空的的 image view 沒有固有內容尺寸。給它一個占位符尺寸這樣就可以在界面中指定合適的約束了。

    [圖片上傳失敗...(image-a19abf-1608214824102)]

  8. 在畫板的底部,打開 Pin 菜單。

    [圖片上傳失敗...(image-f6ea79-1608214824102)]

  9. 選擇 Aspect Ratio 旁邊的多選框。
    Pin 菜單應該像這樣:

    [圖片上傳失敗...(image-b5a925-1608214824102)]

  10. 在 Pin 菜單,點擊 Add 1 Constraints 按鈕;

    [圖片上傳失敗...(image-9476df-1608214824102)]

  11. 選中 image view,打開屬性檢查器。

  12. 在屬性檢查器中,找到名叫 Interaction 的區域并選中 User Interaction Enabled 多選框。
    之后需要這個特性讓用戶與 image view 交互。

最后的 UI 應該像這樣:

[圖片上傳失敗...(image-6c30e4-1608214824102)]

顯示默認的照片

用戶需要指示來引導他們單擊 image view 選擇照片。通過添加默認的占位圖片告訴用戶可以選擇照片。

[圖片上傳失敗...(image-8c4bdd-1608214824102)]

在本課最終項目的 Images/ 文件夾中找到他們,或使用你自己的圖片。

添加圖片到你的項目中
  1. 在項目導航中,選擇 Assets.xcassets 查看 asset catalog。
    asset catalog 是 app 中存放和組織圖片資源的地方。

  2. 在左下角,點擊 + 按鈕并從彈出的菜單中選擇 New Image Set。

    [圖片上傳失敗...(image-bc56d4-1608214824102)]

  3. 雙擊 image set 修改名字為 defaultPhot。

  4. 從電腦中選擇你想添加的圖片。

  5. 拖動圖片到 image set 的 2X 槽中。

    [圖片上傳失敗...(image-8eb0ec-1608214824102)]

    2x 是 iPhone 6 模擬器顯示的分辨率,在這個分辨率下圖片看起來最好。

在 image view 中顯示默認圖片
  1. 打開 storyboard。
  2. 選中 image view。
  3. 選中后,在實用工具區打開屬性檢查器。
  4. 在屬性檢查器中,選擇名為 Image 區域的 defaultPhoto。

檢驗:運行 app。默認的圖片顯示在 image view 中。

[圖片上傳失敗...(image-ede098-1608214824102)]

連接 Image View 和代碼

現在,需要實現必要的功能在運行時修改 image view 的圖片。為了能在代碼中修改圖片,首先需要連接 image view 和 ViewController.swift 中的代碼。

連接 image view 和 ViewController.swift 代碼

  1. 點擊 Xcode 右上角工具欄中 Assistant 按鈕打開輔助編輯器。

    [圖片上傳失敗...(image-222076-1608214824102)]

  2. 如果想要更多空間來工作,通過點擊 Xcode 工具欄中的 Navigator 和 Utilities 按鈕收縮項目導航和實用工具區。

    [圖片上傳失敗...(image-e4ff4e-1608214824102)]

    同樣可以收縮大綱視圖。

  3. 選擇 storyboard 中的 image view。

  4. 按住 Control 從畫板的 image view 拖動到右邊編輯器中的代碼,在已經存在的 outlets 下面停止拖動。

    [圖片上傳失敗...(image-e24e22-1608214824102)]

  5. 在彈出的對話框中,輸入名字 photoImageView。
    忽略其他選項。對話框應該像這樣:

    [圖片上傳失敗...(image-7cd111-1608214824102)]

  6. 點擊 Connect。
    Xcode 向 ViewController.swift 中添加必要的代碼來存儲 image view 的指針并配置 storyboard 設置這個連接。

     @IBOutlet weak var photoImageView: UIImageView!
    

雖然可以在代碼中訪問 image view 來修改它的圖片,但你知道什么時候修改圖片嗎?你需要提供用戶一種方式如單擊 image view 來表達他們想要修改圖片,定義一個動作當單擊事件發生后修改圖片。

views 和 controls(UIControl) 間有微妙的區別,controls 是能夠響應用戶動作的視圖。control (UIControl) 是 UIView 的子類。實際上,在界面中已經使用了 views(labels, image views) 以及 controls(text fields, buttons)。

創建手勢識別器

image view 不是 control 對象,所以無法像 button 這樣的 control 對象一樣響應用戶輸入。例如,你無法簡單創建一個當用戶點擊 image view 時觸發的動作方法。(如果嘗試按住 Control 從 image view 往代碼中拖動,你會發現 Connection 區域無法選擇 Action。)

幸運的是,通過添加手勢識別器讓它具備和 control 一樣的能力。手勢識別器是關聯到視圖的對象允許視圖像 control 一樣響應動作。手勢識別器中斷交互來決定是否響應一個特殊的手勢,如清掃,捏合或旋轉。當手勢識別器識別手勢了相應動作方法執行,那正是你需要為 image view 做的。

給 image view 添加單擊手勢識別器(UITapGestureRecognizer),當用戶點擊 image view 手勢會被識別。在 storyboard 中很簡單就能實現這個功能。

給 image view 添加單擊手勢識別器
  1. 打開對象庫。(選擇 View > Utilities > Show Object Library 可以快速打開它)

  2. 在對象庫的搜索框中輸入 tap gesture 快速找到手勢識別器對象。

  3. 從對象庫中拖動手勢識別器到場景中并把它放在 image view 的頂部。

    [圖片上傳失敗...(image-4dce9c-1608214824102)]

    單擊手勢識別器會出現在場景 dock 中。

連接手勢識別器和代碼

連接手勢識別器和 ViewController.swift 代碼
  1. 按住 Control 從場景 dock 中的手勢識別器拖動到右邊編輯器的代碼中,在 // MARK: Actions 注釋行下面停止拖動。

    [圖片上傳失敗...(image-605b56-1608214824102)]

  2. 在彈出的對話框的 Connection 區域選擇 Action。

  3. Name 輸入 selectImageFromPhotoLibrary。

  4. Type 選擇 UITapGestureRecognizer。
    忽略其他選項,最后對話框應該像這樣:

    [圖片上傳失敗...(image-71864-1608214824102)]

  5. 點擊 Connect。
    Xcode 向 ViewController.swift 添加必要的代碼來設置這個 action(動作)。

     @IBAction func selectImageFromPhotoLibrary(sender: UITapGestureRecognizer) {
     }
    

創建 Image Picker 響應用戶單擊

當用戶點擊 image view,應該發生什么?據推測,用戶應該能從照片集中選擇一張照片或自己拍一張。幸運的是,UIImagePickerController 類內置支持這些操作。image picker 控制器為拍照和從 app 中選取照片都提供了基礎界面。類似你需要使用 text field delegate 來處理文本框,你同樣需要 delegate 來處理 image picker controller。這個代理協議就是 UIImagePickerControllerDelegate。

首先,ViewController 需要遵循 UIImagePickerControllerDelegate 協議。因為 ViewController 負責顯示 image picker 控制器,所以它同樣需要遵循 UINavigationControllerDelegate 協議,它只是簡單的讓 ViewController 承擔一些基本的導航責任。

遵循 UIImagePickerControllerDelegate 和 UINavigationControllerDelegate 協議
  1. 點擊 Standard 按鈕返回標準編輯器。

    [圖片上傳失敗...(image-5a60b4-1608214824102)]

    點擊 Xcode 工具欄中的 Navigator 和 Utilities 按鈕展開項目導航和實用工具區。

  2. 在項目導航中選擇 ViewController.swift。

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

     class ViewController: UIViewController, UITextFieldDelegate {
    
  4. 在 UIImagePickerControllerDelegate 后面添加 , 和 UIImagePickerControllerDelegate 來遵循這個協議。

     class ViewController: UIViewController, UITextFieldDelegate, UIImagePickerControllerDelegate {
    
  5. 在 UIImagePickerControllerDelegate 后面,添加 , 和 UINavigationControllerDelegate。

     class ViewController: UIViewController, UITextFieldDelegate, UIImagePickerControllerDelegate, UINavigationControllerDelegate {
    
實現 selectImageFromPhotoLibrary(_:) 動作方法
  1. 在 ViewController.swift 中找到之前添加的 selectImageFromPhotoLibrary(_:) 動作方法。

     @IBAction func selectImageFromPhotoLibrary(sender: UITapGestureRecognizer) {
     }
    
  2. 在 {} 之間添加下行代碼:

     // Hide the keyboard.
     nameTextField.resignFirstResponder()
    

    這行代碼確保當用戶在文本框輸入的同時點擊 image view,鍵盤會消失。

  3. 添加如下代碼來創建 image picker 控制器:

     // UIImagePickerController is a view controller that lets a user pick media from their photo library.
     let imagePickerController = UIImagePickerController()
    
  4. 繼續添加:

     // Only allow photos to be picked, not taken.
     imagePickerController.sourceType = .PhotoLibrary
    

    這一行,設置 image picker 控制器的數據源,就是圖片來源。.PhotoLibrary 選項使用模擬器的照片冊。
    sourceType 是 UIImagePickerControllerSourceType 枚舉的類型。意味著可以使用縮寫形式 .PhotoLibrary 取代 UIImagePickerControllerSourceType.PhotoLibrary。記住當枚舉類型已知情況下可以使用縮寫形式。

  5. 向 ViewController 中添加如下代碼設置 image picker 控制器的代理:

     // Make sure ViewController is notified when the user picks an image.
     imagePickerController.delegate = self
    
  6. 繼續添加代碼:

     presentViewController(imagePickerController, animated: true, completion: nil)
    

    在 ViewController 中調用presentViewController(_:animated:completion:) 方法。盡管寫的不是很明確,它有個隱式的前綴 self 對象。這個方法要求 ViewController 顯示 imagePickerController 定義的視圖控制器。給 animated 參數傳遞 true 讓 image picker 控制器彈出的時候有動畫。 compleation 參數是個 compleation handler,表示當方法執行完后執行的代碼塊。因為完成后不需要做什么事情,所以傳遞 nil。

最后的 selectImageFromPhotoLibrary(_:) 方法應該是這樣:

@IBAction func selectImageFromPhotoLibrary(sender: UITapGestureRecognizer) {
    // Hide the keyboard.
    nameTextField.resignFirstResponder()
    
    // UIImagePickerController is a view controller that lets a user pick media from their photo library.
    let imagePickerController = UIImagePickerController()
    
    // Only allow photos to be picked, not taken.
    imagePickerController.sourceType = .PhotoLibrary
    
    // Make sure ViewController is notified when the user picks an image.
    imagePickerController.delegate = self
    
    presentViewController(imagePickerController, animated: true, completion: nil)
}

當 image picker 控制器打開了,它的行為就被轉交給代理了。為了讓用戶能選擇照片,需要你實現 UIImagePickerControllerDelegate 的兩個代理方法:

func imagePickerControllerDidCancel(picker: UIImagePickerController)
func imagePickerController(picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : AnyObject])

第一個 imagePickerControllerDidCancel(_:) 會在用戶單擊 image picker 的取消按鈕時調用。此時你可以關掉 UIImagePickerController(或其他任何必要的清理)。

實現 imagePickerControllerDidCancel(_:) 方法
  1. // MARK: Actions 區域右上角,添加如下行:

     // MARK: UIImagePickerControllerDelegate
    

    此注釋幫助你(或任何讀你代碼的人)瀏覽你的代碼并識別哪個是 image picker 實現的代碼區域。

  2. 在注釋下方添加這個方法:

     func imagePickerControllerDidCancel(picker: UIImagePickerController) {
     }
    
  3. 在方法中添加如下代碼行:

     // Dismiss the picker if the user canceled.
     dismissViewControllerAnimated(true, completion: nil)
    

    這行代碼隨著動畫關掉 image picker 控制器。

最后的 imagePickerControllerDidCancel(_:) 方法如下:

func imagePickerControllerDidCancel(picker: UIImagePickerController) {
    // Dismiss the picker if the user canceled.
    dismissViewControllerAnimated(true, completion: nil)
}

第二個需要實現的 UIImagePickerControllerDelegate 的方法是 imagePickerController(_:didFinishPickingMediaWithInfo:),它會在用戶選擇照片的時候調用。這時獲取選中的圖片并顯示在界面上。

實現 imagePickerController(_:didFinishPickingMediaWithInfo:) 方法
  1. 在 imagePickerControllerDidCancel(_:) 下面添加這個方法:

     func imagePickerController(picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : AnyObject]) {
    

    }

  2. 在方法中添加如下代碼:

     // The info dictionary contains multiple representations of the image, and this uses the original.
     let selectedImage = info[UIImagePickerControllerOriginalImage] as! UIImage
    

    info 字典包含 picker 選中的原始圖片,如果編輯后的圖片存在的情況下也包含這張編輯的圖片。簡單起見,使用那張原始未編輯的圖片來作為食物照片。將它存儲到 selectedImage 常量中。

  3. 添加如下代碼設置之前創建的 image view 圖片。

     // Set photoImageView to display the selected image.
     photoImageView.image = selectedImage
    
  4. 添加如下代碼關閉 image picker:

     // Dismiss the picker.
     dismissViewControllerAnimated(true, completion: nil)
    

最后的 imagePickerController(_:didFinishPickingMediaWithInfo) 方法應該像這樣:

func imagePickerController(picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : AnyObject]) {
    // The info dictionary contains multiple representations of the image, and this uses the original.
    let selectedImage = info[UIImagePickerControllerOriginalImage] as! UIImage
    
    // Set photoImageView to display the selected image.
    photoImageView.image = selectedImage
    
    // Dismiss the picker.
    dismissViewControllerAnimated(true, completion: nil)
}

檢驗:運行 app。此時應該能夠點擊 image view 打開 image picker。在彈出的授權對話框中點擊 OK 授予 app 訪問照片的權限。之后,點擊 Cancel 按鈕關閉 picker,或點擊 Camera Roll(相機膠卷)選擇一張照片作為 image view 的圖片。

[圖片上傳失敗...(image-d34285-1608214824102)]

你會注意到模擬器并不包含任何食物的照片。在項目文件的 Images/ 文件夾中找到示例圖片并添加到模擬器。

添加圖片到模擬器

  1. 如果需要,在模擬器中運行 app。

  2. 在電腦中選擇想要添加的圖片。

  3. 拖動圖片到模擬器中。

    [圖片上傳失敗...(image-b56cf3-1608214824102)]

    模擬器打開照片應用程序顯示剛才添加的圖片。

    [圖片上傳失敗...(image-1d4f4-1608214824102)]

檢驗:運行 app。應該能夠點擊 image view 來打開 image picker。然后打開它的 Camera Roll(相機膠卷),選擇剛才添加的圖片來設置 image view 的圖片。

[圖片上傳失敗...(image-ca7556-1608214824102)]

注意:

下載完整示例項目并在 Xcode 中打開它。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,825評論 6 546
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,814評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,980評論 0 384
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 64,064評論 1 319
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,779評論 6 414
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 56,109評論 1 330
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,099評論 3 450
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,287評論 0 291
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,799評論 1 338
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,515評論 3 361
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,750評論 1 375
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,221評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,933評論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,327評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,667評論 1 296
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,492評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,703評論 2 380

推薦閱讀更多精彩內容