在本章中,您將要添加照片到 Homepwner
應用程序。 您將呈現一個 UIImagePickerController,以便用戶可以拍攝并保存每個 item 的圖片。 然后,圖像將與 Item 實例相關聯,并在 item 的詳情視圖中查看(圖15.1)。
圖15.1帶攝像頭的 Homepwner
圖像往往非常大,所以將圖像與其他數據分開存儲是個好主意。 因此,您將要創建第二個 store 來存儲圖像。 ImageStore 將根據需要獲取并緩存圖像。
顯示圖像和 UIImageView
您的第一步是讓 DetailViewController 獲取并顯示圖像。 顯示圖像的簡單方法是將 UIImageView 的實例放在屏幕上。
打開 Homepwner.xcodeproj
和 Main.storyboard
。 然后將 UIImageView 的實例拖動到棧視圖底部的視圖上。 選擇 圖像視圖 并打開其尺寸檢查器。 您希望圖像視圖的 Vertical Content Hugging Priority
和 Vertical Content Compression Resistance Priority
低于其他視圖。 將 Vertical Content Hugging Priority
更改為 248,Vertical Content Compression Resistance Priority
為 749。
您的布局將如圖15.2所示。
圖15.2 DetailViewController 的視圖中的 UIImageView
UIImageView 根據圖像視圖的 contentMode
屬性顯示圖像。 此屬性決定圖像視圖框架內的位置和如何調整大小。 UIImageView 的 contentMode
的默認值是 UIViewContentMode.scaleToFill
,它可以調整圖像,使之與圖像視圖的邊界完全匹配。 如果保持默認值,相機拍攝的圖像將被縮放以適應 UIImageView。 要保持圖像的寬高比,您必須更新 contentMode
。
選擇 UIImageView 后,打開屬性檢查器。 查找 Content Mode
屬性,并將其更改為 Aspect Fit
(圖15.3)。 您不會在故事板上看到更改,但是現在,圖像的大小將被調整以適應 UIImageView 的尺寸。
圖15.3將 UIImageView 的模式更改為 Aspect Fit
接下來,單擊選中項目導航器中的 DetailViewController.swift
,在助手編輯器中打開它。 右鍵從 UIImageView 拖動到 DetailViewController.swift
的頂部。 命名為 imageView
并確保 存儲(storage) 類型為 Strong
。 單擊 Connect
(圖15.4)。
圖15.4創建 imageView outlet
DetailViewController.swift
的頂部應該如下所示:
class DetailViewController: UIViewController, UITextFieldDelegate {
??@IBOutlet var nameField: UITextField!@IBOutlet var serialNumberField: UITextField!
??@IBOutlet var valueField: UITextField!
??@IBOutlet var dateLabel: UILabel!
??@IBOutlet var imageView: UIImageView!
添加相機按鈕
現在您需要一個按鈕來啟動拍照過程。 您將創建一個 UIToolbar 的實例,并將其放在 DetailViewController 視圖的底部。
在 Main.storyboard
中,按 Command-Return
關閉助理編輯器,給自己更多的空間在故事板上工作。 您將需要暫時中斷您的界面以將工具欄添加到界面。
選擇棧視圖的底部約束,然后按 Delete
將其刪除。 您需要為底部的工具欄騰出空間。 從Xcode 8.1開始,很難調整棧視圖的大小。 因此我們將棧視圖拖曳一點(圖15.5)。 現在的視圖會出錯,但是你很快就會解決這個問題。
圖15.5 將棧視圖移開
現在將 工具欄(Toolbar) 從對象庫拖動到視圖的底部。 選擇 Toolbar 并打開自動布局 Add New Constraints
菜單。 精確地配置約束,如圖15.6所示,然后單擊 Add 5 Constraints
。 因為您選擇了 update frame
的選項,棧視圖將重新定位到其正確的位置。
圖15.6 Toolbar 約束
UIToolbar 的工作原理很像 UINavigationBar——您可以向其添加 UIBarButtonItem 的實例。 但是,導航欄只有兩個 Bar Button Item,而工具欄可以有多個 Bar Button Item。 您可以在工具欄中放置盡可能多的 Bar Button Item,以適應屏幕。
默認情況下,在界面文件中創建的一個新的 UIToolbar 實例帶有一個 UIBarButtonItem。 選擇此 Bar Button Item 并打開屬性檢查器。 將 System Item
更改為 Camera
,item 將顯示相機圖標(圖15.7)。
圖15.7帶相機按鈕項的 UIToolbar
構建并運行應用程序并導航到 item 的詳細信息,以使用工具欄上的 Camera Bar Button Item 。 您尚未將相機按鈕連接到操作,因此點擊它將不會執行任何操作。
相機按鈕需要一個目標和一個動作。 在 Main.storyboard
仍然打開的情況下,在項目導航器中選中 DetailViewController.swift
,在助理編輯器中重新打開它。
在 Main.storyboard
中,首先單擊工具欄上的按鈕本身,選擇相機按鈕。 右鍵從所選按鈕拖動到 DetailViewController.swift
。
在 Connection
彈出菜單中,選擇 Action
作為連接類型,命名為 takePicture,選擇 UIBarButtonItem
作為類型,然后單擊 Connect
(圖15.8)。
圖15.8 創建動作
如果在進行此連接時發生任何錯誤,則需要打開 Main.storyboard
并斷開任何不良連接。 (在連接檢查器中查找黃色警告標志。)
拍照和 UIImagePickerController
在 takePicture(_ :) 方法中,您將實例化一個 UIImagePickerController 并將其顯示在屏幕上。 創建 UIImagePickerController 的實例時,必須設置其 源類型(sourceType
) 屬性并為其分配委托。 因為這是圖像選擇器控制器必需的,所以您需要以編程方式創建并顯示它,而不是通過故事板。
設置圖像選擇器的 sourceType
sourceType
常量告訴圖像選擇器在哪里獲取圖像。 它有三個可能的值:
UIImagePickerControllerSourceType.camera
允許用戶拍攝新照片。
UIImagePickerControllerSourceType.photoLibrary
提示用戶選擇相冊,然后選擇該相冊中的照片。
UIImagePickerControllerSourceType.savedPhotosAlbum
提示用戶從最近拍攝的照片中進行選擇。
圖15.9三種 sourceType 的例子
第一種源類型 .camera
,將不會在沒有相機的設備上工作。 所以在使用這種類型之前,您必須通過調用 UIImagePickerController 類上的方法 isSourceTypeAvailable(_ :) 來檢查攝像頭:
class func isSourceTypeAvailable(_ type: UIImagePickerControllerSourceType) -> Bool
調用此方法將返回一個布爾值,以查看設備是否支持傳入源類型。
在 DetailViewController.swift
中,找到 takePicture(_ :) 的位置。 添加以下代碼以創建圖像選擇器并設置其 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
??}
}
設置圖像選擇器的委托
除了源類型,UIImagePickerController 實例需要一個委托。 當用戶從 UIImagePickerController 的界面中選擇一個圖像時,委托將發送消息 imagePickerController(_:didFinishPickingMediaWithInfo :)。 (如果用戶點擊取消按鈕,則委托會收到消息 imagePickerControllerDidCancel(_ :))
圖像選擇器的委托將是 DetailViewController 的實例。 在 DetailViewController.swift
的頂部,聲明 DetailViewController 符合 UINavigationControllerDelegate 和 UIImagePickerControllerDelegate 協議。
class DetailViewController: UIViewController, UITextFieldDelegate,
UINavigationControllerDelegate, UIImagePickerControllerDelegate
{
為什么是 UINavigationControllerDelegate?UIImagePickerController 的 delegate
屬性實際上是從其父類 UINavigationController 繼承的,雖然 UIImagePickerController 具有自己的 委托協議,但其繼承的 delegate
屬性被聲明為引用符合 UINavigationControllerDelegate 的對象。
在 DetailViewController.swift
中,將 DetailViewController 的實例設置為 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
}
以模態方式呈現圖片選擇器
一旦 UIImagePickerController 具有源類型和代理,您可以通過模式顯示視圖控制器來顯示它。
在 DetailViewController.swift
中,將代碼添加到 takePicture(_ :) 的末尾以呈現 UIImagePickerController。
imagePicker.delegate = self
??// Place image picker on the screen
??present(imagePicker, animated: true, completion: nil)
}
構建并運行應用程序。 選擇 item 以查看其詳細信息,然后點擊 UIToolbar 上的相機按鈕,... 應用程序崩潰。 看看在控制臺中的崩潰的描述。
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] [訪問]此應用程序已崩潰,因為它嘗試訪問隱私敏感數據而沒有使用說明。 應用程序的 Info.plist 必須包含一個 NSPhotoLibraryUsageDescription 鍵,其中的字符串值向用戶解釋應用程序如何使用此數據。
當嘗試訪問私人信息(例如用戶的照片)時,iOS會向用戶詢問是否要允許訪問應用程序。 在此提示中包含一個描述為什么應用程序想要訪問此信息。 Homepwner
缺少此描述,因此應用程序會崩潰。
權限
iOS上有許多功能需要用戶批準才能使用。 以下是這些功能的一個子集:
- 相機和照片
- 位置
- 麥克風
- HealthKit 數據
- 日歷
- 提醒
對于每一個權限,您的應用程序必須提供一個 使用說明(usage description)
,指定您的應用程序要訪問此信息的原因。 每當應用程序訪問該功能時,將向用戶呈現此描述。
在項目導航器中,選中最頂層的 Homepwner
,并打開頂部的 Info
選項卡(圖15.10)。
圖15.10打開項目信息
將鼠標懸停在此 Custom iOS Target Properties
列表中的最后一項上,然后單擊 +
按鈕。 將此新條目的 key
設置為 NSCameraUsageDescription
,將 Type
設置為 String
。
雙擊此行的值,然后輸入字符串 “This app uses the camera to associate photos with items.”。這是將呈現給用戶的字符串。
現在重復上述相同的步驟添加照片庫的使用說明。Key
將是 String
類型的 NSPhotoLibraryUsageDescription
,Value
為 This app uses the Photos library to associate photos with items.
。
Custom iOS Target Properties
部分現在將如圖15.11所示。 (列表中的項可能有不同的順序。)
圖15.11 添加新 key
構建并運行應用程序并選擇一個 item。 點擊相機按鈕,您將看到您提供的使用說明一起顯示的權限對話框(圖15.12顯示了庫的描述)。 接受后,UIImagePickerController 的界面將出現在屏幕上(圖15.13顯示了攝像頭界面),您可以拍照,如果您的設備沒有相機則可以選擇現有的圖片。
圖15.12 照片庫使用說明
(如果您正在使用模擬器,照片庫中已經有一些默認圖片,如果要添加自己的圖片,可以將圖片從計算機拖動到模擬器上,并將它添加到模擬器的照片庫,或者,您可以在模擬器中打開 Safari
,并導航到帶有圖片的頁面。右擊圖片,然后選擇 圖片存儲為(Save Image)
將其保存在模擬器的照片庫中。)
圖15.13 UIImagePickerController 的預覽界面
保存圖片
選擇一個圖片會關閉 UIImagePickerController 并返回到詳情視圖。 但是,一旦圖片選擇器被關閉,您就沒有了圖片的引用。 要解決這個問題,你將要實現委托方法 imagePickerController(_:didFinishPickingMediaWithInfo :)。 當選擇照片時,圖片選擇器的委托將調用此方法。
在 DetailViewController.swift
中,實現此方法以將圖片放入 UIImageView,然后調用方法關閉圖片選擇器。
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)
}
再次構建并運行應用程序。 拍攝(或選擇)照片。 圖片選擇器被關閉,您將返回到 DetailViewController 的視圖,您將在其中看到所選照片。
Homepwner
的用戶可以有數百個 item ,每個用戶可以擁有與之相關聯的大圖像。 保存數百個 Item 的內容不難,但要在內存中保存數百張圖像則是不好的:首先,您將獲得低內存警告。 然后,如果您的應用程序的內存占用量繼續增長,則操作系統將會終止它。 您將在下一節中實現的解決方案是將 圖片 存儲到磁盤,并在需要時將其存入RAM。 這個做法將由一個新類 ImageStore 完成。 當應用程序接收到低內存通知時,ImageStore 的緩存將被刷新以釋放所獲取的圖片占用的內存。
創建 ImageStore
在第16章中,您將創建一個 Item 的實例來將它們的屬性寫入一個文件,然后在應用程序啟動時被讀入。 然而,由于圖片往往非常大,將它們與其他數據分開是一個好主意。 您將存儲用戶在 ImageStore 類的實例中使用的圖片。 圖片存儲將根據需要提取和緩存圖片。 如果設備內存不足,則可以刷新緩存。
創建一個名為 ImageStore
的新的 Swift 文件。 在 ImageStore.swift
中,定義 ImageStore 類并添加一個屬性,該屬性是 NSCache 的一個實例。
import Foundation
import UIKit
class ImageStore {
??let cache = NSCache<NSString,UIImage>()
}
緩存的運作非常像一本字典(在第2章中看到)。 您可以添加,刪除和更新給定的 key 相關聯的值。 與字典不同,如果系統內存不足,緩存將自動刪除對象。 雖然這可能是本章中的一個問題(因為圖片只會存在于緩存中),當您還想將圖片寫入文件系統時,您將在第16章中解決這個問題。
請注意,緩存將 NSString 的實例與 UIImage 相關聯。 NSString 是 Objective-C 的 String 版本。 由于 NSCache 的實現方式所限(它是一個 Objective-C 類,大多數Apple的類一直在使用),它要求您使用 NSString 而不是 String。
現在實現從字典添加,檢索和刪除圖像的三種方法。
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 類型的關鍵字,以便其余的代碼庫不必考慮 NSCache 的底層實現。 然后,當將每個 String 傳遞到緩存時,將每個 String 轉換為 NSString。
讓視圖控制器訪問 ImageStore
DetailViewController 需要一個 ImageStore 實例來獲取和存儲圖片。 您將把這個依賴項注入到 DetailViewController 的指定的構造器中,就像在第10章中為 ItemsViewController 和 ItemStore 所做的那樣。
在 DetailViewController.swift
中,為 ImageStore 添加一個屬性。
var item: Item! {
??didSet {
????navigationItem.title = item.name
??}
}
var imageStore: ImageStore!
現在在 ItemsViewController.swift
中執行相同操作。
var itemStore: ItemStore!
var imageStore: ImageStore!
接下來,仍然在 ItemsViewController.swift
中,更新 prepare(for:sender :) 在 DetailViewController 上設置 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
創建并注入 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
創建和使用 key
當一個圖片被添加到 store 時,它要有唯一的 key 并被放入到緩存中,關聯的 Item 對象也應該有這個 key。 當 DetailViewController 想要從 store 獲得圖片時,它會請求其 item
獲得 key 并搜索緩存中的圖像。
添加一個屬性到 Item.swift 存儲 key。
let dateCreated: Date
let itemKey: String
緩存中的 key 必須保證是唯一的。 雖然有許多方法可以將一個唯一的字符串組合在一起,您將使用 Cocoa Touch 機制來創建通用唯一標識符 universally unique identifier
(UUID),也稱為全局唯一標識符 globally unique identifier
(GUID)。
類型 NSUUID 的對象表示 UUID,并使用時間,計數器和硬件標識符生成,通常是 Wi-Fi 卡的MAC地址。 當以字符串的形式表示時,UUID看起來像這樣:
4A73B5D2-A6F4-4B40-9F82-EA1E34C1DC04
在 Item.swift
中,生成一個 UUID 并將其設置為 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)
}
每當捕捉到一張圖片,它都將被添加到 store。 ImageStore 和 Item 都將知道圖像的 key,所以兩者都可以根據需要進行訪問(圖15.14)。
圖15.14 從緩存訪問圖像
類似地,當 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
現在 ImageStore 可以存儲圖片和 Item 的實例并且由一個 key 來獲取圖片(圖15.14),您需要使 DetailViewController 獲取所選 Item 的圖片并將其放在其 imageView
中。
當用戶在 ItemsViewController 中點擊一行和 UIImagePickerController 被關閉時,DetailViewController 的視圖將會出現。 在這兩種情況下,imageView 都應該顯示正在顯示的 item 的圖片。 而目前僅在 UIImagePickerController 被關閉時才會發生。
在 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
}
運行應用程序。 創建一個 item 并從表格視圖中選擇它。 然后點擊相機按鈕拍照。 圖片將按原樣出現。 從 item 的 詳情 返回到 item 列表。 與以前不同,如果您點擊并向下鉆取以查看添加了圖片的 item 的詳情時,您將看到圖片。
青銅挑戰:編輯圖片
UIImagePickerController 具有內置界面,用于在選擇圖像后對其進行編輯。 允許用戶編輯圖片,并使用編輯的圖片,而不是 DetailViewController 中的原始圖像。
白銀挑戰:刪除圖片
添加一個清除 item 圖片的按鈕。
黃金挑戰:相機覆蓋線
UIImagePickerController 具有 cameraOverlayView
屬性。 用它來使 UIImagePickerController 在圖像捕獲區域的中間顯示十字準線。
更多:瀏覽實現類文件
您的兩個視圖控制器在其實現類文件中都有很多方法。 要成為一名高效的 iOS 開發人員,您必須能夠快速便捷地瀏覽您正在尋找的代碼。 Xcode中的源代碼編輯器跳轉欄是您可以使用的一個工具(圖15.15)。
圖15.15 源代碼編輯器跳轉欄
跳轉條顯示您在項目中的完整位置(以及光標在給定文件中的位置)。 跳轉詳細信息如圖15.16。
圖15.16 跳轉欄詳情
跳欄的導航痕跡導航反映了項目導航層級。 如果您單擊任何部分,將在項目層次結構中顯示該部分的 彈出窗口。 從那里,您可以輕松導航到項目的其他部分。
圖15.17顯示了 Homepwner
文件夾的文件彈出窗口。
圖15.17 文件彈出窗口
也許最有用的是在實現類文件中輕松瀏覽的能力。 如果您點擊導航痕跡中的最后一個元素,您將獲得一個包含文件內容的彈出窗口,包括該文件中實現的所有方法。
當彈出窗口可見時,您可以輸入文本以過濾列表項。 在任何時候,您可以使用上下箭頭鍵,然后按 Return(回車) 鍵在代碼中跳轉到該方法。 圖15.18顯示了在 ItemsViewController.swift
中搜索 tableview
時獲得的內容。
圖15.18文件 彈出窗口 與搜索 “tableview”
// MARK:
隨著你的類越來越長,找到一個埋在一大堆方法中的方法將變得越來越困難。 組織您的方法的一個好方法是使用 // MARK:
注釋。
兩個有用的 // MARK: 注釋是分隔符和標簽:
// This is a divider
// MARK: -
// This is a label
// MARK: My Awesome Methods
分隔線和標簽可以組合使用 :
// MARK: - View life cycle
override func viewDidLoad() { ... }
override func viewWillAppear(_ animated: Bool) { ... }
// MARK: - Actions
func addNewItem(_ sender: UIBarButtonItem) {...}
添加 // MARK:
注釋到你的代碼不會改變代碼本身; 它只是告訴 Xcode 如何可視化組織你的方法。 您可以通過在跳轉欄中打開當前文件項來查看結果。 圖15.19 是一個組織良好的組織的 ItemsViewController.swift
圖15.19 帶有 //MARK: 的文件彈出窗口
如果您習慣使用 // MARK:
注釋,您將強制自己組織您的代碼。 如果你組織的好,這將使您的代碼更易于閱讀,更易于使用。