第十五章——相機【譯】

在本章中扔仓,您將要添加照片到 Homepwner 應(yīng)用程序搓幌。 您將呈現(xiàn)一個 UIImagePickerController作儿,以便用戶可以拍攝并保存每個 item 的圖片强胰。 然后舱沧,圖像將與 Item 實例相關(guān)聯(lián),并在 item 的詳情視圖中查看(圖15.1)哪廓。

圖15.1帶攝像頭的 Homepwner

圖像往往非常大狗唉,所以將圖像與其他數(shù)據(jù)分開存儲是個好主意。 因此涡真,您將要創(chuàng)建第二個 store 來存儲圖像分俯。 ImageStore 將根據(jù)需要獲取并緩存圖像。

顯示圖像和 UIImageView

您的第一步是讓 DetailViewController 獲取并顯示圖像哆料。 顯示圖像的簡單方法是將 UIImageView 的實例放在屏幕上缸剪。

打開 Homepwner.xcodeprojMain.storyboard。 然后將 UIImageView 的實例拖動到棧視圖底部的視圖上东亦。 選擇 圖像視圖 并打開其尺寸檢查器杏节。 您希望圖像視圖的 Vertical Content Hugging PriorityVertical Content Compression Resistance Priority 低于其他視圖。 將 Vertical Content Hugging Priority 更改為 248典阵,Vertical Content Compression Resistance Priority 為 749奋渔。

您的布局將如圖15.2所示。

圖15.2 DetailViewController 的視圖中的 UIImageView

UIImageView 根據(jù)圖像視圖的 contentMode 屬性顯示圖像壮啊。 此屬性決定圖像視圖框架內(nèi)的位置和如何調(diào)整大小嫉鲸。 UIImageViewcontentMode 的默認值是 UIViewContentMode.scaleToFill,它可以調(diào)整圖像歹啼,使之與圖像視圖的邊界完全匹配玄渗。 如果保持默認值,相機拍攝的圖像將被縮放以適應(yīng) UIImageView狸眼。 要保持圖像的寬高比藤树,您必須更新 contentMode

選擇 UIImageView 后拓萌,打開屬性檢查器岁钓。 查找 Content Mode 屬性,并將其更改為 Aspect Fit(圖15.3)司志。 您不會在故事板上看到更改甜紫,但是現(xiàn)在,圖像的大小將被調(diào)整以適應(yīng) UIImageView 的尺寸骂远。

圖15.3將 UIImageView 的模式更改為 Aspect Fit

接下來囚霸,單擊選中項目導(dǎo)航器中的 DetailViewController.swift,在助手編輯器中打開它激才。 右鍵從 UIImageView 拖動到 DetailViewController.swift 的頂部拓型。 命名為 imageView 并確保 存儲(storage) 類型為 Strong额嘿。 單擊 Connect(圖15.4)。

圖15.4創(chuàng)建 imageView outlet

DetailViewController.swift 的頂部應(yīng)該如下所示:

class DetailViewController: UIViewController, UITextFieldDelegate {

??@IBOutlet var nameField: UITextField!@IBOutlet var serialNumberField: UITextField!
??@IBOutlet var valueField: UITextField!
??@IBOutlet var dateLabel: UILabel!
??@IBOutlet var imageView: UIImageView!

添加相機按鈕

現(xiàn)在您需要一個按鈕來啟動拍照過程劣挫。 您將創(chuàng)建一個 UIToolbar 的實例册养,并將其放在 DetailViewController 視圖的底部。

Main.storyboard 中压固,按 Command-Return 關(guān)閉助理編輯器球拦,給自己更多的空間在故事板上工作。 您將需要暫時中斷您的界面以將工具欄添加到界面帐我。

選擇棧視圖的底部約束坎炼,然后按 Delete 將其刪除。 您需要為底部的工具欄騰出空間拦键。 從Xcode 8.1開始谣光,很難調(diào)整棧視圖的大小。 因此我們將棧視圖拖曳一點(圖15.5)芬为。 現(xiàn)在的視圖會出錯萄金,但是你很快就會解決這個問題。

圖15.5 將棧視圖移開

現(xiàn)在將 工具欄(Toolbar) 從對象庫拖動到視圖的底部媚朦。 選擇 Toolbar 并打開自動布局 Add New Constraints 菜單氧敢。 精確地配置約束,如圖15.6所示询张,然后單擊 Add 5 Constraints福稳。 因為您選擇了 update frame 的選項,棧視圖將重新定位到其正確的位置瑞侮。

圖15.6 Toolbar 約束

UIToolbar 的工作原理很像 UINavigationBar——您可以向其添加 UIBarButtonItem 的實例。 但是鼓拧,導(dǎo)航欄只有兩個 Bar Button Item半火,而工具欄可以有多個 Bar Button Item。 您可以在工具欄中放置盡可能多的 Bar Button Item季俩,以適應(yīng)屏幕钮糖。

默認情況下,在界面文件中創(chuàng)建的一個新的 UIToolbar 實例帶有一個 UIBarButtonItem酌住。 選擇此 Bar Button Item 并打開屬性檢查器店归。 將 System Item 更改為 Camera,item 將顯示相機圖標(biāo)(圖15.7)酪我。

圖15.7帶相機按鈕項的 UIToolbar

構(gòu)建并運行應(yīng)用程序并導(dǎo)航到 item 的詳細信息消痛,以使用工具欄上的 Camera Bar Button Item 。 您尚未將相機按鈕連接到操作都哭,因此點擊它將不會執(zhí)行任何操作秩伞。

相機按鈕需要一個目標(biāo)和一個動作逞带。 在 Main.storyboard 仍然打開的情況下,在項目導(dǎo)航器中選中 DetailViewController.swift纱新,在助理編輯器中重新打開它展氓。

Main.storyboard 中,首先單擊工具欄上的按鈕本身脸爱,選擇相機按鈕遇汞。 右鍵從所選按鈕拖動到 DetailViewController.swift

Connection 彈出菜單中簿废,選擇 Action 作為連接類型空入,命名為 takePicture,選擇 UIBarButtonItem 作為類型捏鱼,然后單擊 Connect(圖15.8)执庐。

圖15.8 創(chuàng)建動作

如果在進行此連接時發(fā)生任何錯誤,則需要打開 Main.storyboard 并斷開任何不良連接导梆。 (在連接檢查器中查找黃色警告標(biāo)志轨淌。)

拍照和 UIImagePickerController

takePicture(_ :) 方法中,您將實例化一個 UIImagePickerController 并將其顯示在屏幕上看尼。 創(chuàng)建 UIImagePickerController 的實例時递鹉,必須設(shè)置其 源類型(sourceType) 屬性并為其分配委托。 因為這是圖像選擇器控制器必需的藏斩,所以您需要以編程方式創(chuàng)建并顯示它躏结,而不是通過故事板。

設(shè)置圖像選擇器的 sourceType

sourceType 常量告訴圖像選擇器在哪里獲取圖像狰域。 它有三個可能的值:

UIImagePickerControllerSourceType.camera 允許用戶拍攝新照片媳拴。

UIImagePickerControllerSourceType.photoLibrary 提示用戶選擇相冊,然后選擇該相冊中的照片兆览。

UIImagePickerControllerSourceType.savedPhotosAlbum 提示用戶從最近拍攝的照片中進行選擇屈溉。

圖15.9三種 sourceType 的例子

第一種源類型 .camera,將不會在沒有相機的設(shè)備上工作抬探。 所以在使用這種類型之前子巾,您必須通過調(diào)用 UIImagePickerController 類上的方法 isSourceTypeAvailable(_ :) 來檢查攝像頭:

class func isSourceTypeAvailable(_ type: UIImagePickerControllerSourceType) -> Bool

調(diào)用此方法將返回一個布爾值,以查看設(shè)備是否支持傳入源類型小压。

DetailViewController.swift 中线梗,找到 takePicture(_ :) 的位置。 添加以下代碼以創(chuàng)建圖像選擇器并設(shè)置其 sourceType怠益。

@IBAction func takePicture(_ sender: UIBarButtonItem) {

??let imagePicker = UIImagePickerController()

??// If the device has a camera, take a picture; otherwise,
??// just pick from photo library
??if UIImagePickerController.isSourceTypeAvailable(.camera) {
????imagePicker.sourceType = .camera
??} else {
????imagePicker.sourceType = .photoLibrary
??}
}

設(shè)置圖像選擇器的委托

除了源類型仪搔,UIImagePickerController 實例需要一個委托。 當(dāng)用戶從 UIImagePickerController 的界面中選擇一個圖像時溉痢,委托將發(fā)送消息 imagePickerController(_:didFinishPickingMediaWithInfo :)僻造。 (如果用戶點擊取消按鈕憋他,則委托會收到消息 imagePickerControllerDidCancel(_ :)

圖像選擇器的委托將是 DetailViewController 的實例。 在 DetailViewController.swift 的頂部髓削,聲明 DetailViewController 符合 UINavigationControllerDelegateUIImagePickerControllerDelegate 協(xié)議竹挡。

class DetailViewController: UIViewController, UITextFieldDelegate, UINavigationControllerDelegate, UIImagePickerControllerDelegate {

為什么是 UINavigationControllerDelegateUIImagePickerControllerdelegate 屬性實際上是從其父類 UINavigationController 繼承的立膛,雖然 UIImagePickerController 具有自己的 委托協(xié)議揪罕,但其繼承的 delegate 屬性被聲明為引用符合 UINavigationControllerDelegate 的對象。

DetailViewController.swift 中宝泵,將 DetailViewController 的實例設(shè)置為 takePicture(_ :) 中的圖像選擇器代理好啰。

@IBAction func takePicture(_ sender: UIBarButtonItem) {

??let imagePicker = UIImagePickerController()

??// If the device has a camera, take a picture; otherwise,
??// just pick from photo library
??if UIImagePickerController.isSourceTypeAvailable(.camera) {
????imagePicker.sourceType = .camera
??} else {
????imagePicker.sourceType = .photoLibrary
??}

??imagePicker.delegate = self
}

以模態(tài)方式呈現(xiàn)圖片選擇器

一旦 UIImagePickerController 具有源類型和代理,您可以通過模式顯示視圖控制器來顯示它儿奶。

DetailViewController.swift 中框往,將代碼添加到 takePicture(_ :) 的末尾以呈現(xiàn) UIImagePickerController

imagePicker.delegate = self

??// Place image picker on the screen
??present(imagePicker, animated: true, completion: nil)
}

構(gòu)建并運行應(yīng)用程序闯捎。 選擇 item 以查看其詳細信息椰弊,然后點擊 UIToolbar 上的相機按鈕,... 應(yīng)用程序崩潰瓤鼻。 看看在控制臺中的崩潰的描述秉版。

Homepwner[3575:64615] [access] This app has crashed because it attempted to access privacy-sensitive data without a usage description. The app's Info.plist must contain an NSPhotoLibraryUsageDescription key with a string value explaining to the user how the app uses this data.

Homepwner [3575:64615] [訪問]此應(yīng)用程序已崩潰,因為它嘗試訪問隱私敏感數(shù)據(jù)而沒有使用說明茬祷。 應(yīng)用程序的 Info.plist 必須包含一個 NSPhotoLibraryUsageDescription 鍵清焕,其中的字符串值向用戶解釋應(yīng)用程序如何使用此數(shù)據(jù)。

當(dāng)嘗試訪問私人信息(例如用戶的照片)時祭犯,iOS會向用戶詢問是否要允許訪問應(yīng)用程序秸妥。 在此提示中包含一個描述為什么應(yīng)用程序想要訪問此信息。 Homepwner 缺少此描述沃粗,因此應(yīng)用程序會崩潰筛峭。

權(quán)限

iOS上有許多功能需要用戶批準才能使用。 以下是這些功能的一個子集:

  • 相機和照片
  • 位置
  • 麥克風(fēng)
  • HealthKit 數(shù)據(jù)
  • 日歷
  • 提醒

對于每一個權(quán)限陪每,您的應(yīng)用程序必須提供一個 使用說明(usage description),指定您的應(yīng)用程序要訪問此信息的原因镰吵。 每當(dāng)應(yīng)用程序訪問該功能時檩禾,將向用戶呈現(xiàn)此描述。

在項目導(dǎo)航器中疤祭,選中最頂層的 Homepwner盼产,并打開頂部的 Info 選項卡(圖15.10)。

圖15.10打開項目信息

將鼠標(biāo)懸停在此 Custom iOS Target Properties 列表中的最后一項上勺馆,然后單擊 + 按鈕戏售。 將此新條目的 key 設(shè)置為 NSCameraUsageDescription侨核,將 Type 設(shè)置為 String

雙擊此行的值灌灾,然后輸入字符串 “This app uses the camera to associate photos with items.”搓译。這是將呈現(xiàn)給用戶的字符串。

現(xiàn)在重復(fù)上述相同的步驟添加照片庫的使用說明锋喜。Key 將是 String 類型的 NSPhotoLibraryUsageDescription些己,ValueThis app uses the Photos library to associate photos with items.

Custom iOS Target Properties 部分現(xiàn)在將如圖15.11所示嘿般。 (列表中的項可能有不同的順序段标。)

圖15.11 添加新 key

構(gòu)建并運行應(yīng)用程序并選擇一個 item。 點擊相機按鈕炉奴,您將看到您提供的使用說明一起顯示的權(quán)限對話框(圖15.12顯示了庫的描述)逼庞。 接受后,UIImagePickerController 的界面將出現(xiàn)在屏幕上(圖15.13顯示了攝像頭界面)瞻赶,您可以拍照赛糟,如果您的設(shè)備沒有相機則可以選擇現(xiàn)有的圖片。

圖15.12 照片庫使用說明

(如果您正在使用模擬器共耍,照片庫中已經(jīng)有一些默認圖片虑灰,如果要添加自己的圖片,可以將圖片從計算機拖動到模擬器上痹兜,并將它添加到模擬器的照片庫穆咐,或者,您可以在模擬器中打開 Safari字旭,并導(dǎo)航到帶有圖片的頁面对湃。右擊圖片,然后選擇 圖片存儲為(Save Image) 將其保存在模擬器的照片庫中遗淳。)

圖15.13 UIImagePickerController 的預(yù)覽界面

保存圖片

選擇一個圖片會關(guān)閉 UIImagePickerController 并返回到詳情視圖拍柒。 但是,一旦圖片選擇器被關(guān)閉屈暗,您就沒有了圖片的引用拆讯。 要解決這個問題,你將要實現(xiàn)委托方法 imagePickerController(_:didFinishPickingMediaWithInfo :)养叛。 當(dāng)選擇照片時种呐,圖片選擇器的委托將調(diào)用此方法。

DetailViewController.swift 中弃甥,實現(xiàn)此方法以將圖片放入 UIImageView爽室,然后調(diào)用方法關(guān)閉圖片選擇器。

func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String: Any]) {
??// Get picked image from info dictionary
??let image = info[UIImagePickerControllerOriginalImage] as! UIImage

??// Put that image on the screen in the image view
??imageView.image = image

??// Take image picker off the screen -
??// you must call this dismiss method
??dismiss(animated: true, completion: nil)
}

再次構(gòu)建并運行應(yīng)用程序淆攻。 拍攝(或選擇)照片阔墩。 圖片選擇器被關(guān)閉嘿架,您將返回到 DetailViewController 的視圖,您將在其中看到所選照片啸箫。

Homepwner 的用戶可以有數(shù)百個 item 耸彪,每個用戶可以擁有與之相關(guān)聯(lián)的大圖像。 保存數(shù)百個 Item 的內(nèi)容不難筐高,但要在內(nèi)存中保存數(shù)百張圖像則是不好的:首先搜囱,您將獲得低內(nèi)存警告。 然后柑土,如果您的應(yīng)用程序的內(nèi)存占用量繼續(xù)增長蜀肘,則操作系統(tǒng)將會終止它。 您將在下一節(jié)中實現(xiàn)的解決方案是將 圖片 存儲到磁盤稽屏,并在需要時將其存入RAM扮宠。 這個做法將由一個新類 ImageStore 完成。 當(dāng)應(yīng)用程序接收到低內(nèi)存通知時狐榔,ImageStore 的緩存將被刷新以釋放所獲取的圖片占用的內(nèi)存坛增。

創(chuàng)建 ImageStore

在第16章中,您將創(chuàng)建一個 Item 的實例來將它們的屬性寫入一個文件薄腻,然后在應(yīng)用程序啟動時被讀入收捣。 然而,由于圖片往往非常大庵楷,將它們與其他數(shù)據(jù)分開是一個好主意罢艾。 您將存儲用戶在 ImageStore 類的實例中使用的圖片。 圖片存儲將根據(jù)需要提取和緩存圖片尽纽。 如果設(shè)備內(nèi)存不足咐蚯,則可以刷新緩存。

創(chuàng)建一個名為 ImageStore 的新的 Swift 文件弄贿。 在 ImageStore.swift 中春锋,定義 ImageStore 類并添加一個屬性,該屬性是 NSCache 的一個實例差凹。

import Foundation
import UIKit

class ImageStore {

??let cache = NSCache<NSString,UIImage>()

}

緩存的運作非常像一本字典(在第2章中看到)期奔。 您可以添加,刪除和更新給定的 key 相關(guān)聯(lián)的值危尿。 與字典不同能庆,如果系統(tǒng)內(nèi)存不足,緩存將自動刪除對象脚线。 雖然這可能是本章中的一個問題(因為圖片只會存在于緩存中),當(dāng)您還想將圖片寫入文件系統(tǒng)時弥搞,您將在第16章中解決這個問題邮绿。

請注意渠旁,緩存將 NSString 的實例與 UIImage 相關(guān)聯(lián)。 NSString 是 Objective-C 的 String 版本船逮。 由于 NSCache 的實現(xiàn)方式所限(它是一個 Objective-C 類顾腊,大多數(shù)Apple的類一直在使用),它要求您使用 NSString 而不是 String挖胃。

現(xiàn)在實現(xiàn)從字典添加杂靶,檢索和刪除圖像的三種方法。

class ImageStore {

??let cache = NSCache<NSString,UIImage>()

??func setImage(_ image: UIImage, forKey key: String) {
????cache.setObject(image, forKey: key as NSString)
??}

??func image(forKey key: String) -> UIImage? {
????return cache.object(forKey: key as NSString)
??}

??func deleteImage(forKey key: String) {
????cache.removeObject(forKey: key as NSString)
??}
}

這三種方法都采用 String 類型的關(guān)鍵字酱鸭,以便其余的代碼庫不必考慮 NSCache 的底層實現(xiàn)吗垮。 然后,當(dāng)將每個 String 傳遞到緩存時凹髓,將每個 String 轉(zhuǎn)換為 NSString烁登。

讓視圖控制器訪問 ImageStore

DetailViewController 需要一個 ImageStore 實例來獲取和存儲圖片。 您將把這個依賴項注入到 DetailViewController 的指定的構(gòu)造器中蔚舀,就像在第10章中為 ItemsViewControllerItemStore 所做的那樣饵沧。

DetailViewController.swift 中,為 ImageStore 添加一個屬性赌躺。

var item: Item! {
??didSet {
????navigationItem.title = item.name
??}
}
var imageStore: ImageStore!

現(xiàn)在在 ItemsViewController.swift 中執(zhí)行相同操作狼牺。

var itemStore: ItemStore!
var imageStore: ImageStore!

接下來,仍然在 ItemsViewController.swift 中礼患,更新 prepare(for:sender :)DetailViewController 上設(shè)置 imageStore 屬性是钥。

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
??// If the triggered segue is the "showItem" segue"
??switch segue.identifier {
??case "showItem"?:
????// Figure out which row was just tapped
????if let row = tableView.indexPathForSelectedRow?.row {

??????// Get the item associated with this row and pass it along
??????let item = itemStore.allItems[row]
??????let detailViewController = segue.destination as! DetailViewController
??????detailViewController.item = item
??????detailViewController.imageStore = imageStore
????}
??default:
????preconditionFailure("Unexpected segue identifier.")
??}
}

最后,更新 AppDelegate.swift 創(chuàng)建并注入 ImageStore讶泰。

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

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

??// Create an ImageStore
??let imageStore = ImageStore()

??// Access the ItemsViewController and set its item store and image store
??let navController = window!.rootViewController as! UINavigationController
??let itemsController = navController.topViewController as! ItemsViewController
??itemsController.itemStore = itemStore
??itemsController.imageStore = imageStore

創(chuàng)建和使用 key

當(dāng)一個圖片被添加到 store 時咏瑟,它要有唯一的 key 并被放入到緩存中,關(guān)聯(lián)的 Item 對象也應(yīng)該有這個 key痪署。 當(dāng) DetailViewController 想要從 store 獲得圖片時码泞,它會請求其 item 獲得 key 并搜索緩存中的圖像。

添加一個屬性到 Item.swift 存儲 key狼犯。

let dateCreated: Date
let itemKey: String

緩存中的 key 必須保證是唯一的余寥。 雖然有許多方法可以將一個唯一的字符串組合在一起,您將使用 Cocoa Touch 機制來創(chuàng)建通用唯一標(biāo)識符 universally unique identifier(UUID)悯森,也稱為全局唯一標(biāo)識符 globally unique identifier(GUID)宋舷。

類型 NSUUID 的對象表示 UUID,并使用時間瓢姻,計數(shù)器和硬件標(biāo)識符生成祝蝠,通常是 Wi-Fi 卡的MAC地址。 當(dāng)以字符串的形式表示時,UUID看起來像這樣:

4A73B5D2-A6F4-4B40-9F82-EA1E34C1DC04

Item.swift 中绎狭,生成一個 UUID 并將其設(shè)置為 itemKey细溅。

init(name: String, serialNumber: String?, valueInDollars: Int) {
??self.name = name
??self.valueInDollars = valueInDollars
??self.serialNumber = serialNumber
??self.dateCreated = Date()
??self.itemKey = UUID().uuidString

??super.init()
}

DetailViewController.swift 中,更新 imagePickerController(_:didFinishPickingMediaWithInfo :) 將圖片存儲在 ImageStore 中儡嘶。

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

??// Get picked image from info dictionary
??let image = info[UIImagePickerControllerOriginalImage] as! UIImage

??// Store the image in the ImageStore for the item's key
??imageStore.setImage(image, forKey: item.itemKey)

??// Put that image on the screen in the image view
??imageView.image = image

??// Take image picker off the screen -
??// you must call this dismiss method
??dismiss(animated: true, completion: nil)
}

每當(dāng)捕捉到一張圖片寸认,它都將被添加到 store瘾带。 ImageStoreItem 都將知道圖像的 key棒旗,所以兩者都可以根據(jù)需要進行訪問(圖15.14)嗦明。

圖15.14 從緩存訪問圖像

類似地,當(dāng) item 被刪除時凯楔,您需要從圖像存儲庫中刪除其圖像窜骄。 在 ItemsViewController.swift 中,更新 tableView(_:commit:forRowAt :) 以從 ImageStore 中刪除 item 的圖片啼辣。

override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
??// If the table view is asking to commit a delete command...
??if editingStyle == .delete {
????let item = itemStore.allItems[indexPath.row]

????let title = "Delete \(item.name)?"
????let message = "Are you sure you want to delete this item?"

????let ac = UIAlertController(title: title, message: message, preferredStyle: .actionSheet)

????let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil)

????ac.addAction(cancelAction)

????let deleteAction = UIAlertAction(title: "Delete", style: .destructive, handler: { (action) -> Void in
??????// Remove the item from the store
??????self.itemStore.removeItem(item)

??????// Remove the item's image from the image store
??????self.imageStore.deleteImage(forKey: item.itemKey)

??????// Also remove that row from the table view with an animation
??????self.tableView.deleteRows(at: [indexPath], with: .automatic)
????})
????ac.addAction(deleteAction)

????// Present the alert controller
????present(ac, animated: true, completion: nil)
??}
}

封裝 ImageStore

現(xiàn)在 ImageStore 可以存儲圖片和 Item 的實例并且由一個 key 來獲取圖片(圖15.14)啊研,您需要使 DetailViewController 獲取所選 Item 的圖片并將其放在其 imageView 中。

當(dāng)用戶在 ItemsViewController 中點擊一行和 UIImagePickerController 被關(guān)閉時鸥拧,DetailViewController 的視圖將會出現(xiàn)党远。 在這兩種情況下,imageView 都應(yīng)該顯示正在顯示的 item 的圖片富弦。 而目前僅在 UIImagePickerController 被關(guān)閉時才會發(fā)生沟娱。

DetailViewController.swift 中,在 viewWillAppear(_ :) 中進行此操作腕柜。

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

??nameField.text = item.name
??serialNumberField.text = item.serialNumber
??valueField.text = numberFormatter.string(from: NSNumber(value: item.valueInDollars))
??dateLabel.text = dateFormatter.string(from: item.dateCreated)

??// Get the item key
??let key = item.itemKey

??// If there is an associated image with the item
??// display it on the image view
??let imageToDisplay = imageStore.image(forKey: key)
??imageView.image = imageToDisplay
}

運行應(yīng)用程序济似。 創(chuàng)建一個 item 并從表格視圖中選擇它。 然后點擊相機按鈕拍照盏缤。 圖片將按原樣出現(xiàn)砰蠢。 從 item 的 詳情 返回到 item 列表。 與以前不同唉铜,如果您點擊并向下鉆取以查看添加了圖片的 item 的詳情時台舱,您將看到圖片。

青銅挑戰(zhàn):編輯圖片

UIImagePickerController 具有內(nèi)置界面潭流,用于在選擇圖像后對其進行編輯竞惋。 允許用戶編輯圖片,并使用編輯的圖片灰嫉,而不是 DetailViewController 中的原始圖像拆宛。

白銀挑戰(zhàn):刪除圖片

添加一個清除 item 圖片的按鈕。

黃金挑戰(zhàn):相機覆蓋線

UIImagePickerController 具有 cameraOverlayView 屬性讼撒。 用它來使 UIImagePickerController 在圖像捕獲區(qū)域的中間顯示十字準線浑厚。

更多:瀏覽實現(xiàn)類文件

您的兩個視圖控制器在其實現(xiàn)類文件中都有很多方法股耽。 要成為一名高效的 iOS 開發(fā)人員,您必須能夠快速便捷地瀏覽您正在尋找的代碼钳幅。 Xcode中的源代碼編輯器跳轉(zhuǎn)欄是您可以使用的一個工具(圖15.15)豺谈。

圖15.15 源代碼編輯器跳轉(zhuǎn)欄

跳轉(zhuǎn)條顯示您在項目中的完整位置(以及光標(biāo)在給定文件中的位置)。 跳轉(zhuǎn)詳細信息如圖15.16贡这。

圖15.16 跳轉(zhuǎn)欄詳情

跳欄的導(dǎo)航痕跡導(dǎo)航反映了項目導(dǎo)航層級。 如果您單擊任何部分厂榛,將在項目層次結(jié)構(gòu)中顯示該部分的 彈出窗口盖矫。 從那里,您可以輕松導(dǎo)航到項目的其他部分击奶。

圖15.17顯示了 Homepwner 文件夾的文件彈出窗口辈双。

圖15.17 文件彈出窗口

也許最有用的是在實現(xiàn)類文件中輕松瀏覽的能力。 如果您點擊導(dǎo)航痕跡中的最后一個元素柜砾,您將獲得一個包含文件內(nèi)容的彈出窗口湃望,包括該文件中實現(xiàn)的所有方法。

當(dāng)彈出窗口可見時痰驱,您可以輸入文本以過濾列表項证芭。 在任何時候,您可以使用上下箭頭鍵担映,然后按 Return(回車) 鍵在代碼中跳轉(zhuǎn)到該方法废士。 圖15.18顯示了在 ItemsViewController.swift 中搜索 tableview 時獲得的內(nèi)容。

圖15.18文件 彈出窗口 與搜索 “tableview”

// MARK:

隨著你的類越來越長蝇完,找到一個埋在一大堆方法中的方法將變得越來越困難官硝。 組織您的方法的一個好方法是使用 // MARK: 注釋。

兩個有用的 // MARK: 注釋是分隔符和標(biāo)簽:

// This is a divider
// MARK: -

// This is a label
// MARK: My Awesome Methods

分隔線和標(biāo)簽可以組合使用 :

// MARK: - View life cycle
override func viewDidLoad() { ... }
override func viewWillAppear(_ animated: Bool) { ... }

// MARK: - Actions
func addNewItem(_ sender: UIBarButtonItem) {...}

添加 // MARK: 注釋到你的代碼不會改變代碼本身; 它只是告訴 Xcode 如何可視化組織你的方法短蜕。 您可以通過在跳轉(zhuǎn)欄中打開當(dāng)前文件項來查看結(jié)果氢架。 圖15.19 是一個組織良好的組織的 ItemsViewController.swift

圖15.19 帶有 //MARK: 的文件彈出窗口

如果您習(xí)慣使用 // MARK:注釋,您將強制自己組織您的代碼朋魔。 如果你組織的好岖研,這將使您的代碼更易于閱讀,更易于使用铺厨。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末缎玫,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子解滓,更是在濱河造成了極大的恐慌赃磨,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,576評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件洼裤,死亡現(xiàn)場離奇詭異邻辉,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,515評論 3 399
  • 文/潘曉璐 我一進店門值骇,熙熙樓的掌柜王于貴愁眉苦臉地迎上來莹菱,“玉大人,你說我怎么就攤上這事吱瘩〉牢埃” “怎么了?”我有些...
    開封第一講書人閱讀 168,017評論 0 360
  • 文/不壞的土叔 我叫張陵使碾,是天一觀的道長蜜徽。 經(jīng)常有香客問我,道長票摇,這世上最難降的妖魔是什么拘鞋? 我笑而不...
    開封第一講書人閱讀 59,626評論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮矢门,結(jié)果婚禮上盆色,老公的妹妹穿的比我還像新娘。我一直安慰自己祟剔,他們只是感情好隔躲,可當(dāng)我...
    茶點故事閱讀 68,625評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著峡扩,像睡著了一般蹭越。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上教届,一...
    開封第一講書人閱讀 52,255評論 1 308
  • 那天响鹃,我揣著相機與錄音,去河邊找鬼案训。 笑死买置,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的强霎。 我是一名探鬼主播忿项,決...
    沈念sama閱讀 40,825評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼城舞!你這毒婦竟也來了轩触?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,729評論 0 276
  • 序言:老撾萬榮一對情侶失蹤家夺,失蹤者是張志新(化名)和其女友劉穎脱柱,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體拉馋,經(jīng)...
    沈念sama閱讀 46,271評論 1 320
  • 正文 獨居荒郊野嶺守林人離奇死亡榨为,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,363評論 3 340
  • 正文 我和宋清朗相戀三年惨好,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片随闺。...
    茶點故事閱讀 40,498評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡日川,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出矩乐,到底是詐尸還是另有隱情龄句,我是刑警寧澤,帶...
    沈念sama閱讀 36,183評論 5 350
  • 正文 年R本政府宣布散罕,位于F島的核電站撒璧,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏笨使。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,867評論 3 333
  • 文/蒙蒙 一僚害、第九天 我趴在偏房一處隱蔽的房頂上張望硫椰。 院中可真熱鬧,春花似錦萨蚕、人聲如沸靶草。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,338評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽奕翔。三九已至,卻和暖如春浩蓉,著一層夾襖步出監(jiān)牢的瞬間派继,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,458評論 1 272
  • 我被黑心中介騙來泰國打工捻艳, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留驾窟,地道東北人。 一個月前我還...
    沈念sama閱讀 48,906評論 3 376
  • 正文 我出身青樓认轨,卻偏偏與公主長得像绅络,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子嘁字,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,507評論 2 359

推薦閱讀更多精彩內(nèi)容