iOS 从零开始使用 Swift:构建购物清单应用程序 2

iOS 从零开始使用 Swift系列:
探索 iOS SDK
探索 Foundation 框架
使用 UIKit 的第一步
自动布局基础
表格视图基础
导航控制器和视图控制器层次结构
iOS 上的数据持久性和沙盒
构建购物清单应用程序 1
构建购物清单应用程序 2
下一步该去哪里

前面的lessen中,我们为购物清单的应用打下了基础。在本课的第一部分,我们通过允许用户编辑和删除列表中的项目来进一步优化应用程序。稍后,我们添加了从列表中选择项目以创建购物清单的功能。

1. 删除项目

就用户体验和整体可用性而言,从列表中删除项目是一项重要的补充。添加此能力包括:

  • 从视图控制器的 items 属性中删除项目
  • 更新表格视图
  • 将更改保存到磁盘

让我们看看这在实践中是如何工作的。我们首先需要在导航栏上添加一个编辑按钮。在视图控制器的 viewDidLoad() 方法中,创建一个实例 UIBarButtonItem 并将其分配给 rightBarButtonItem 视图控制器的 navigationItem 属性。

正如我们在上一课中所做的那样,我们通过调用 init(barButtonSystemItem:target:action:)、传入、  作为目标和选择器.Edit的成员值来创建一个条形按钮项。UIBarButtonSystemItemself"editItems:"

override func viewDidLoad() {
    super.viewDidLoad()
     
    // Register Class
    tableView.registerClass(UITableViewCell.classForCoder(), forCellReuseIdentifier: CellIdentifier)
     
    // Create Add Button
    navigationItem.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: .Add, target: self, action: "addItem:")
     
    // Create Edit Button
    navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .Edit, target: self, action: "editItems:")
}

的实现editItems(_:) 只是一行代码,如下所示。每当用户点击编辑按钮时,表格视图就会进入或退出编辑模式。我们通过使用一个小技巧来做到这一点。我们询问表格视图是否处于编辑模式,它返回一个 Bool 值,然后反转返回值(true 变为 false ,反之亦然)。我们在 table view 上调用的方法是 setEditing(_:animated:),一个接受动画参数的专用 setter。

func editItems(sender: UIBarButtonItem) {
    tableView.setEditing(!tableView.editing, animated: true)
}

如果您在模拟器中运行购物清单应用程序并点击编辑按钮,您应该会看到表格视图被切换到和退出编辑模式。

该协议的两种方法 UITableViewDataSource 对于在表格视图中启用编辑很重要:

  • tableView(_:canEditRowAtIndexPath:)
  • tableView(_:commitEditingStyle:forRowAtIndexPath:)

如果用户点击编辑按钮,表格视图会通过向数据源发送消息来询问其数据源哪些行是可编辑的 tableView(_:canEditRowAtIndexPath:)。如果 true 为特定索引路径返回,则表视图指示相应的表视图单元格需要切换到或退出编辑模式,具体取决于表视图的编辑模式。这转化为表格视图单元格显示或隐藏其编辑控件。实现 tableView(_:canEditRowAtIndexPath:)如下所示的方法,看看它在实践中是如何工作的。

override func tableView(tableView: UITableView, canEditRowAtIndexPath indexPath: NSIndexPath) -> Bool {
    if indexPath.row == 1 {
        return false
    }
     
    return true
}

上述实现 tableView(_:canEditRowAtIndexPath:) 使用户能够编辑表格视图中的每一行,但第二行除外。在模拟器中运行应用程序并试一试。

对于购物清单应用程序,用户应该能够编辑表格视图中的每一行。这意味着 tableView(_:canEditRowAtIndexPath:) 应该总是返回 true

override func tableView(tableView: UITableView, canEditRowAtIndexPath indexPath: NSIndexPath) -> Bool {
    return true
}

您可能已经注意到,当您尝试删除表格视图中的一行时,没有任何反应。谜题还没有完成。每当用户点击行的删除按钮时,表格视图都会向其数据源发送一条消息 tableView(_:commitEditingStyle:forRowAtIndexPath:)。相应方法的第二个参数表示用户执行了何种类型的操作,插入或删除一行。对于购物清单应用程序,我们将只实现对从表视图中删除行的支持。

从表视图中删除行包括:

  • items 从视图控制器的属性中删除相应的项目 
  • 通过删除相应的行来更新表视图

让我们检查 tableView(_:commitEditingStyle:forRowAtIndexPath:). 该方法首先检查编辑样式是否等于 .Delete (的成员值 UITableViewCellEditingStyle),因为我们只想允许用户从表视图中删除行。

如果编辑样式等于 .Delete,则从 items 属性中删除相应的项目,从表格视图中删除相应的行,并将更新的项目列表保存到磁盘。

override func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
    if editingStyle == .Delete {
        // Delete Item from Items
        items.removeAtIndex(indexPath.row)
         
        // Update Table View
        tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Right)
         
        // Save Changes
        saveItems()
    }
}

在模拟器中运行应用程序并删除一些项目。不要忘记退出并重新启动应用程序以验证项目是否已从列表中永久删除。

2. 编辑项目

我们可以重用 AddItemViewController 该类来编辑项目。然而,因为使用单个视图控制器来添加和编辑项目通常会使实现过于复杂,所以我通常最终创建一个单独的类来编辑项目。这最初可能会导致我们重复自己,但它会给我们更多的灵活性。

第 1 步:创建编辑视图控制器

创建一个新的 UIViewController 子类并命名它 EditItemViewControllerAddItemViewController 和 类的接口EditItemViewController 非常相似。

import UIKit
 
protocol EditItemViewControllerDelegate {
    func controller(controller: EditItemViewController, didUpdateItem item: Item)
}
 
class EditItemViewController: UIViewController {
 
    @IBOutlet var nameTextField: UITextField!
    @IBOutlet var priceTextField: UITextField!
     
    var item: Item!
     
    var delegate: EditItemViewControllerDelegate?
     
    ...
 
}

不同之处在于添加了一个属性 ,item来存储对正在编辑的项目的引用以及EditItemViewControllerDelegate协议方法的定义。请注意,它item是 type Item!,强制展开的可选选项。因为如果没有要编辑的项目,编辑项目视图控制器是没有用的,我们希望item属性总是有一个值。

打开Main.storyboardUIViewController , 从 Object Library中拖出一个 实例,将其类设置为 EditItemViewController,然后从列表视图控制器创建一个手动 显示转场 到编辑项视图控制器。将 segue 的标识符设置为 EditItemViewController.

将文本字段从 对象库拖到 视图控制器的视图中,并如下图所示放置它们。选择顶部的文本字段,打开 Attributes Inspector ,然后 在 Placeholder 字段中输入 Name 。选择底部文本字段,然后在 Attributes Inspector中,将其占位符文本设置为 Price 并将 Keyboard设置 为 Number Pad。选择视图控制器对象,打开 Connections Inspector,然后将  和  出口与用户界面中的相应文本字段连接。nameTextFieldpriceTextField

在视图控制器的 viewDidLoad() 方法中,像我们在 AddItemViewController 类中一样创建保存按钮。

// MARK: -
// MARK: View Life Cycle
override func viewDidLoad() {
    super.viewDidLoad()
     
    // Create Save Button
    navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .Save, target: self, action: "save:")
}

动作的实现 与我们在 课堂上save(_:) 实现的非常相似 。AddItemViewController不过,我想指出一些细微的差异。

// MARK: -
// MARK: Actions
func save(sender: UIBarButtonItem) {
    if let name = nameTextField.text, let priceAsString = priceTextField.text, let price = Float(priceAsString) {
        // Update Item
        item.name = name
        item.price = price
         
        // Notify Delegate
        delegate?.controller(self, didUpdateItem: item)
         
        // Pop View Controller
        navigationController?.popViewControllerAnimated(true)
    }
}

我们没有将名称和价格值传递给委托,而是直接更新项目并将更新后的项目传递给视图控制器的委托。因为视图控制器是导航控制器的子视图控制器,所以我们通过从导航堆栈中弹出视图控制器来解除视图控制器。

第 2 步:显示编辑项目视图控制器

几分钟后,我们将实现使用户能够从列表视图控制器中选择项目以将它们添加到购物清单的功能。用户将能够通过点击列表视图中的一行来执行此操作。问题是,如果为将商品添加到购物清单而保留点击行,用户将如何编辑商品?

UIKit 框架为这个用例提供了一个详细信息披露按钮。详细信息披露按钮位于表格视图单元格的右侧。要向表格视图单元格添加详细信息披露按钮,我们需要重新访问 tableView(_:cellForRowAtIndexPath:) 列表视图控制器并修改它,如下所示。

override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
    // Dequeue Reusable Cell
    let cell = tableView.dequeueReusableCellWithIdentifier(CellIdentifier, forIndexPath: indexPath)
     
    // Fetch Item
    let item = items[indexPath.row]
     
    // Configure Table View Cell
    cell.textLabel?.text = item.name
    cell.accessoryType = .DetailDisclosureButton
     
    return cell
}

每个表格视图单元格都有一个 accessoryType 属性。在 tableView(_:cellForRowAtIndexPath:)中,我们将其设置为 .DetailDisclosureButton的成员值 UITableViewCellAccessoryType。您可能已经注意到 Apple 的工程师不喜欢简称。

当点击详细信息披露按钮时,表格视图如何通知其委托?不出所料, 协议 为此目的UITableViewDelegate 定义了 方法。tableView(_:accessoryButtonTappedForRowWithIndexPath:)看看它的实现。

// MARK: -
// MARK: Table View Delegate Methods
override func tableView(tableView: UITableView, accessoryButtonTappedForRowWithIndexPath indexPath: NSIndexPath) {
    // Fetch Item
    let item = items[indexPath.row]
     
    // Update Selection
    selection = item
     
    // Perform Segue
    performSegueWithIdentifier("EditItemViewController", sender: self)
}

我们从属性中获取正确的项目 items 并将其存储在 selection列表视图控制器的属性中,我们稍后将声明它。然后我们使用标识符执行 segue  EditItemViewController

在我们更新 的实现之前 prepareForSegue(_:sender:),我们需要声明用于存储所选项目的属性。我们还需要使 ListViewController 类 符合EditItemViewControllerDelegate 协议。

import UIKit
 
class ListViewController: UITableViewController, AddItemViewControllerDelegate, EditItemViewControllerDelegate {
 
    let CellIdentifier = "Cell Identifier"
     
    var items = [Item]()
    var selection: Item?
     
    ...
 
}

如下所示,更新后的实现 prepareForSegue(_:sender:) 非常简单。我们得到一个对编辑项视图控制器的引用并设置它的 delegate 和 item 属性。因为selection属性是 type Item?,所以我们将它绑定到item常量以安全地展开它。

override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
    if segue.identifier == "AddItemViewController" {
      ...
         
    } else if segue.identifier == "EditItemViewController" {
        if let editItemViewController = segue.destinationViewController as? EditItemViewController, let item = selection {
            editItemViewController.delegate = self
            editItemViewController.item = item
        }
    }
}

实施EditItemViewController 几乎完成。在它的 方法中,我们用 属性viewDidLoad() 的数据填充文本字段 。item因为text文本字段的属性是 type String?,所以我们使用字符串插值从Float 项目 price 属性的值创建一个字符串。字符串插值在 Swift 中非常强大。

override func viewDidLoad() {
    super.viewDidLoad()
     
    // Create Save Button
    navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .Save, target: self, action: "save:")
     
    // Populate Text Fields
    nameTextField.text = item.name
    priceTextField.text = "\(item.price)"
}

第 3 步:采用委托协议

采用 EditItemViewControllerDelegate 协议意味着实现 controller(_:didUpdateItem:) 如下所示的方法。我们不更新表格视图的数据源可能会让您感到惊讶。我们在委托方法中所做的只是重新加载表格视图的一行。

// MARK: -
// MARK: Edit Item View Controller Delegate Methods
func controller(controller: EditItemViewController, didUpdateItem item: Item) {
    // Fetch Index for Item
    if let index = items.indexOf(item) {
        // Update Table View
        tableView.reloadRowsAtIndexPaths([NSIndexPath(forRow: index, inSection: 0)], withRowAnimation: .Fade)
    }
     
    // Save Items
    saveItems()
}

我们不需要更新表格视图的数据源 items 数组的原因是更新的项目是通过引用传递给编辑项目视图控制器的。换句话说,编辑项视图控制器更新的对象与 items 数组中包含的对象相同。这是使用类实例的好处之一。它们通过引用传递。

不要忘记保存项目列表以确保将编辑写入磁盘。运行应用程序以测试编辑功能。

3. 创建购物清单视图控制器

在我们探索购物清单视图控制器的数据源之前,让我们创建一些可以使用的脚手架。创建一个新的 UITableViewController 子类并命名它 ShoppingListViewController

打开 ShoppingListViewController.swift 并添加两个 type 属性[Item]

  • items,其中将包含项目的完整列表
  • shoppingList,它将只包含购物清单的项目
import UIKit
 
class ShoppingListViewController: UITableViewController {
 
    var items = [Item]()
    var shoppingList = [Item]()
     
    ...
 
}

这个想法是每次对列表进行更改时加载项目列表,解析项目列表,并仅提取那些 inShoppingList 属性设置为 的项目true。然后将这些项目添加到 shoppingList 数组中。

另一种选择是将购物清单存储在单独的文件中。这种方法的缺点是我们必须保持列表视图控制器中的项目和购物清单中的项目同步。如果你问我,这是自找麻烦。

第 1 步:添加属性观察者

属性观察器是响应属性值变化的好方法。让我们看看我们如何利用属性观察器shoppingList在值发生items变化时更新属性。

var items = [Item]() {
    didSet {
        buildShoppingList()
    }
}

语法很容易理解。属性观察者被包裹在一个闭包中。有两种类型的属性观察者:

  • willSet,在设置属性的新值之前调用
  • didSet,在设置属性的新值后调用

在上面的示例中,buildShoppingList()每当为 分配新值时,我们都会调用items。这就是实现的 buildShoppingList()样子。

// MARK: -
// MARK: Helper Methods
func buildShoppingList() {
    shoppingList = items.filter({ (item) -> Bool in
        return item.inShoppingList
    })
}

我们过滤数组的元素items,仅包括inShoppingList设置为 的项目true。结果分配给shoppingList

我们还为该属性创建了一个didSet属性观察者shoppingList。在这个属性观察器中,我们更新表视图以反映表视图shoppingList的数据源的内容。

var shoppingList = [Item]() {
    didSet {
        tableView.reloadData()
    }
}

第 2 步:显示购物清单

实现 UITableViewDataSource 协议的方法现在应该是小菜一碟了。看看下面的实现。

// MARK: -
// MARK: Table View Data Source Methods
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
    return 1
}
 
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    return shoppingList.count
}
 
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
    // Dequeue Reusable Cell
    let cell = tableView.dequeueReusableCellWithIdentifier(CellIdentifier, forIndexPath: indexPath)
     
    // Fetch Item
    let item = shoppingList[indexPath.row]
     
    // Configure Table View Cell
    cell.textLabel?.text = item.name
     
    return cell
}

不要忘记声明单元重用标识符并 UITableViewCell 像我们在类中一样 注册ListViewController 类。

import UIKit
 
class ShoppingListViewController: UITableViewController {
 
    let CellIdentifier = "Cell Identifier"
     
    ...
 
}
// MARK: -
// MARK: View Life Cycle
override func viewDidLoad() {
    super.viewDidLoad()
     
    // Register Class
    tableView.registerClass(UITableViewCell.classForCoder(), forCellReuseIdentifier: CellIdentifier)
}

第 3 步:加载项目

正如我之前解释的,使用商品列表来存储和构建购物清单的主要优势是应用程序仅将每个商品存储在一个位置。这使得更新列表和购物清单中的项目不那么令人头疼。loadItems() 和 pathForItems()方法与我们在类中实现的方法 相同 ListViewController 。

private func loadItems() {
    if let filePath = pathForItems() where NSFileManager.defaultManager().fileExistsAtPath(filePath) {
        if let archivedItems = NSKeyedUnarchiver.unarchiveObjectWithFile(filePath) as? [Item] {
            items = archivedItems
        }
    }
}
private func pathForItems() -> String? {
    let paths = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, NSSearchPathDomainMask.UserDomainMask, true)
     
    if let documents = paths.first, let documentsURL = NSURL(string: documents) {
        return documentsURL.URLByAppendingPathComponent("items").path
    }
     
    return nil
}

当您发现自己在重复代码时,您应该会听到警铃响起。在实现新功能时,复制代码不会有问题。但是,之后,您应该考虑重构您的代码,以尽量减少应用程序代码库中的重复数量。这是软件开发中一个非常重要的概念,通常被称为 DRY,  Don’t Repeat Yourself。Chris Peters  在 Envato Tuts+ 上写了一篇关于 DRY 编程的精彩文章。

在我们 ShoppingListViewController 使用类之前,我们需要更新类的viewDidLoad() 方法。除了设置视图控制器的标题之外,我们还加载了项目列表,它会自动填充 shoppingList 我们之前看到的数组。

override func viewDidLoad() {
    super.viewDidLoad()
     
    // Set Title
    title = "Shopping List"
     
    // Load Items
    loadItems()
     
    // Register Class
    tableView.registerClass(UITableViewCell.classForCoder(), forCellReuseIdentifier: CellIdentifier)
}

最后一步是通过更新故事板来初始化购物清单视图控制器。这包括添加一个 UITableViewController 实例,将其类设置为 ShoppingListViewController,将其嵌入到导航控制器中,以及在标签栏控制器和导航控制器之间创建关系segue。选择购物清单视图控制器的表格视图并将原型单元格的数量设置为 0

运行应用程序以查看是否一切正常。当然,购物清单目前是空的,我们没有办法将商品添加到购物清单中。让我们在下一步中解决这个问题。

4. 将商品添加到购物清单

正如我之前写的,这个想法是在列表视图控制器中点击时将一个项目添加到购物清单中。为了改善用户体验,如果购物清单中有商品,我们会在商品名称左侧显示一个绿色复选标记。如果点击购物清单中已经存在的商品,则会将其从购物清单中移除,并且绿色复选标记消失。这意味着我们需要看一下  协议的tableView(_:didSelectRowAtIndexPath:) 方法 。UITableViewDelegate

在我们实施之前 tableView(_:didSelectRowAtIndexPath:),请下载本课的源文件。在名为 Resources的文件夹中,找到名为 checkmark.png 和 checkmark@2x.png的文件。将这两个文件都添加到项目中,因为我们稍后需要它们。

在 的第一行中 tableView(_:didSelectRowAtIndexPath:),我们向表格视图发送一条消息 deselectRowAtIndexPath(_:animated:) 以取消选择用户点击的行。每当点击一行时,它应该只高亮一下,因此添加了这个。接下来,我们获取与用户选择对应的项目并更新项目的 inShoppingList 属性(true 变为 false ,反之亦然)。

override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
    tableView.deselectRowAtIndexPath(indexPath, animated: true)
     
    // Fetch Item
    let item = items[indexPath.row]
     
    // Update Item
    item.inShoppingList = !item.inShoppingList
     
    // Update Cell
    let cell = tableView.cellForRowAtIndexPath(indexPath)
     
    if item.inShoppingList {
        cell?.imageView?.image = UIImage(named: "checkmark")
    } else {
        cell?.imageView?.image = nil
    }
     
    // Save Items
    saveItems()
}

根据项目 inShoppingList 属性的值,我们显示或隐藏绿色复选标记。我们通过设置 image 表格视图单元格的 imageView 属性来显示复选标记。表格视图单元格在其左侧包含一个图像视图( UIImageView 该类的一个实例)。通过将图像视图的 image 属性设置为 nil,图像视图为空白,不显示图像。

实施 tableView(_:didSelectRowAtIndexPath:) 通过将项目列表保存到磁盘来确保更改是永久性的。

当用户点击列表视图控制器中的项目时,购物清单如何知道?如果对列表视图控制器中的项目列表进行了更改,购物列表视图控制器将不会自动更新其表视图。为了防止紧密耦合,我们不希望列表视图控制器和购物列表视图控制器直接相互通信。

这个问题的一种解决方案是使用通知。每当列表视图控制器对项目列表进行更改时,它都会将具有特定名称的通知发布到通知中心,这是一个管理通知的对象。对某些通知感兴趣的对象可以将自己添加为这些通知的观察者,这意味着当这些通知发布到通知中心时它们可以做出响应。

这一切是如何运作的?涉及三个步骤:

  • 购物清单视图控制器首先告诉通知中心它有兴趣接收名称为 ShoppingListDidChangeNotification
  • 列表视图控制器在更新项目列表时向通知中心发布通知
  • 当购物清单视图控制器收到来自通知中心的通知时,它会更新其数据源和表格视图作为响应

在我们实现我刚才描述的三个步骤之前,最好仔细看看这个 NSNotificationCenter 类。

简而言之, NSNotificationCenter 管理通知的广播。应用程序中的对象可以在通知中心注册以接收通知, addObserver(_:selector:name:object:) 其中:

  • 第 一个参数 是接收通知的对象(观察者)
  • selector 是观察者收到通知时调用的动作
  • name 是通知的名称
  • object 是触发发送通知的对象

如果最后一个参数设置为 nil,则观察者会收到具有指定名称的每个通知。

第 1 步:接收通知

重新访问该类的 viewDidLoad() 方法 ShoppingListViewController 并将视图控制器实例添加为观察者以接收名称为 ShoppingListDidChangeNotification.

override func viewDidLoad() {
    super.viewDidLoad()
     
    // Set Title
    title = "Shopping List"
     
    // Load Items
    loadItems()
     
    // Register Class
    tableView.registerClass(UITableViewCell.classForCoder(), forCellReuseIdentifier: CellIdentifier)
     
    // Add Observer
    NSNotificationCenter.defaultCenter().addObserver(self, selector: "updateShoppingList:", name: "ShoppingListDidChangeNotification", object: nil)
}

当视图控制器收到具有该名称的通知时触发的操作是 updateShoppingList(_:). 最后一个参数 object是 nil,因为哪个对象发送了通知并不重要。

第 2 步:响应通知

观察者收到通知时触发的方法具有特定格式,如下所示。它接受一个参数,即类型为 的通知对象 NSNotification

通知对象保留对发布通知的对象的引用,它还可以包含带有附加信息的字典。该 updateShoppingList(_:) 方法的实现非常简单。我们调用 loadItems() 视图控制器,这意味着项目列表是从磁盘加载的。由于我们之前实现的属性观察器,其余的会自动发生。

// MARK: -
// MARK: Notification Handling
func updateShoppingList(notification: NSNotification) {
    loadItems()
}

第 3 步:发送通知

第三个难题是每当列表视图控制器更改项目列表时发布通知。我们可以在类的 saveItems() 方法中 做到这一点ListViewController 。

private func saveItems() {
    if let filePath = pathForItems() {
        NSKeyedArchiver.archiveRootObject(items, toFile: filePath)
         
        // Post Notification
        NSNotificationCenter.defaultCenter().postNotificationName("ShoppingListDidChangeNotification", object: self)
    }
}

defaultCenter() 我们首先通过调用类来 请求对默认通知中心的引用 NSNotificationCenter 。接下来,我们调用 postNotificationName(_:object:) 默认的通知中心,传入通知的名称 ShoppingListDidChangeNotification,以及发布通知的对象。

在构建项目之前,请确保修改 ListViewController.swifttableView(_:cellForRowAtIndexPath:)如下 所示,以显示购物清单中已经存在的项目的绿色复选标记。

override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
    // Dequeue Reusable Cell
    let cell = tableView.dequeueReusableCellWithIdentifier(CellIdentifier, forIndexPath: indexPath)
     
    // Fetch Item
    let item = items[indexPath.row]
     
    // Configure Table View Cell
    cell.textLabel?.text = item.name
    cell.accessoryType = .DetailDisclosureButton
     
    if item.inShoppingList {
        cell.imageView?.image = UIImage(named: "checkmark")
    } else {
        cell.imageView?.image = nil
    }
     
    return cell
}

运行购物清单应用程序,试一试。您是否注意到对商品的编辑会自动反映在购物清单中?

准备好出版了吗?

伟大的。将购物清单应用发布到 App Store 的按钮在哪里?我们还没有完成。尽管我们已经奠定了购物清单应用程序的基础,但它还没有准备好发布。还有一些事情需要考虑。

可扩展性

购物清单应用程序是购物清单的适度实现。如果应用程序包含数百或数千个项目,那么添加搜索功能以及按字母顺序对项目排序的部分将是相关的(正如我们在本系列前面所做的那样)。重要的是要意识到每次更新项目时都会将项目列表完整地写入磁盘。当列表很小时,这没有问题,但如果列表随着时间的推移增长到数百或数千个项目,就会出现问题。

关系

此外,用户可能希望保存多个购物清单。你会怎么处理呢?一种选择是将每个购物清单存储在一个单独的文件中,但是您将如何处理对项目所做的更改?您要更新包含该商品的每个购物清单吗?当您开始处理关系时,最好选择 SQLite 数据存储。

如果您选择走这条路,Core Data 是一个很好的伴侣。它是一个强大的框架,并具有许多功能,使我们的购物清单应用程序中的许多代码都过时了。Core Data 确实带来了更多的开销,因此首先要考虑 Core Data 是否适合您的应用程序,换句话说,是否值得开销。

附加的功能

附加功能也有很多潜力。商品的价格属性在购物清单应用程序的当前实现中仍未使用。此外,如果用户可以通过点击项目从购物清单中检查项目,那将是很好的。如您所见,购物清单应用程序的当前实现只是一个小小的开始。

结论

尽管购物清单应用程序还没有为 App Store 做好准备,但您不能否认它按计划运行,它向您展示了 Cocoa 开发的几个新方面,例如通知和实现自定义委托协议。

您现在知道对 iOS SDK 有什么期望以及 iOS 开发是什么样的。您可以决定是否要继续您的旅程并成为一名熟练的 iOS 开发人员。如果您确实选择继续进行 iOS 开发,那么我将在本系列的下一部分和最后一部分中为您提供一些很棒的资源。

ios-from-scratch-with-swift-building-a-shopping-list-application-2–cms-25516