使用CloudKit构建购物清单应用程序:添加关系

本系列上一篇文章中 ,我们添加了添加,更新和删除购物清单的功能。 但是,其中没有任何物品的购物清单不是很有用。 在本教程中,我们将添加从购物清单添加,更新和删除商品的功能。 这意味着我们将使用引用和CKReference类。

我们还将仔细研究购物清单应用程序的数据模型。 更改数据模型有多容易?应用程序如何响应我们在CloudKit仪表板中所做的更改?

先决条件

请记住,我将使用Xcode 9Swift 3 。 如果您使用的是旧版本的Xcode,请记住您正在使用其他版本的Swift编程语言。

在本教程中,我们将继续在本系列的上一篇文章中停下的地方。 您可以从GitHub下载或克隆项目。

1.购物清单明细

当前,用户可以通过点击详细公开指示符来修改购物清单的名称,但是用户也应该能够通过在列表视图控制器中点击一个来查看购物清单的内容。 为了使它起作用,我们首先需要一个新的UIViewController子类。

步骤1:建立ListViewController

ListViewController类将在表视图中显示购物清单的内容。 ListViewController类的接口看起来类似于ListsViewController类的接口。 我们导入CloudKitSVProgressHUD框架,并使该类符合UITableViewDataSourceUITableViewDelegate协议。 因为我们将使用表视图,所以我们声明一个常量ItemCell ,它将用作单元重用标识符。

import UIKit
import CloudKit
import SVProgressHUD

class ListViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
    
    static let ItemCell = "ItemCell"
    
    @IBOutlet weak var messageLabel: UILabel!
    @IBOutlet weak var tableView: UITableView!
    @IBOutlet weak var activityIndicatorView: UIActivityIndicatorView!
    
    var list: CKRecord!
    var items = [CKRecord]()
    
    var selection: Int?

    ...

}

我们声明三个出口: UILabel!类型的messageLabel UILabel! ,类型为UITableView! tableView UITableView! ,以及类型为UIActivityIndicatorView! activityIndicatorView UIActivityIndicatorView! 。 列表视图控制器在list属性中保持对正在显示的购物清单的引用,该属性的类型为CKRecord! 。 购物清单中的items存储在items属性中,该属性的类型为[CKRecord] 。 最后,我们使用辅助变量selection来跟踪用户在购物清单中选择了哪个项目。 这将在本教程的后面部分变得清楚。

步骤2:建立使用者介面

打开Main.storyboard ,添加一个视图控制器,然后在Identity Inspector中将其类设置为ListViewController 。 选择列表视图控制器的原型单元,按Control键,然后从原型单元拖动到列表视图控制器。 从弹出的菜单中选择显示 ,然后在Attributes Inspector中将标识符设置为List

将表视图,标签和活动指示器视图添加到视图控制器的视图。 不要忘记连接视图控制器和表格视图的插座。

选择表视图,然后在“ 属性”检查器中将“ 原型单元”设置为1 。 选择原型单元,然后将样式设置为Right Detail ,将标识符设置ItemCell ,并将附件设置Disclosure Indicator 。 完成后,这就是视图控制器的外观。

列表视图控制器

步骤3:配置视图控制器

在重新访问CloudKit框架之前,我们需要为将要接收的数据准备视图控制器。 首先更新viewDidLoad的实现。 我们将视图控制器的标题设置为购物清单的名称,并调用两个帮助器方法setupViewfetchItems

// MARK: -
// MARK: View Life Cycle
override func viewDidLoad() {
    super.viewDidLoad()
    
    // Set Title
    title = list.objectForKey("name") as? String
    
    setupView()
    fetchItems()
}

setupView方法与我们在ListsViewController类中实现的方法相同。

// MARK: -
// MARK: View Methods
private func setupView() {
    tableView.hidden = true
    messageLabel.hidden = true
    activityIndicatorView.startAnimating()
}

在讨论的同时,我们还实现另一个熟悉的辅助方法updateView 。 在updateView ,我们基于存储在items属性中的items更新视图控制器的用户界面。

private func updateView() {
    let hasRecords = items.count > 0
    
    tableView.hidden = !hasRecords
    messageLabel.hidden = hasRecords
    activityIndicatorView.stopAnimating()
}

我现在fetchItemsfetchItems留空。 完成设置列表视图控制器后,我们将重新访问此方法。

// MARK: -
// MARK: Helper Methods
private func fetchItems() {
    
}

步骤4:表格视图数据源方法

我们几乎准备将应用程序带到另一个测试运行。 在执行此操作之前,我们需要实现UITableViewDataSource协议。 如果您已经阅读了本系列的前几期,那么该实现将很熟悉。

// MARK: -
// MARK: Table View Data Source Methods
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
    return 1;
}

func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    return items.count
}

func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
    // Dequeue Reusable Cell
    let cell = tableView.dequeueReusableCellWithIdentifier(ListViewController.ItemCell, forIndexPath: indexPath)
    
    // Configure Cell
    cell.accessoryType = .DetailDisclosureButton
    
    // Fetch Record
    let item = items[indexPath.row]
    
    if let itemName = item.objectForKey("name") as? String {
        // Configure Cell
        cell.textLabel?.text = itemName
        
    } else {
        cell.textLabel?.text = "-"
    }
    
    return cell
}

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

步骤5:处理选择

要将所有内容捆绑在一起,我们需要重新访问ListsViewController类。 首先实现UITableViewDelegate协议的tableView(_:didSelectRowAtIndexPath:)方法。

// MARK: -
// MARK: Table View Delegate Methods
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
    tableView.deselectRowAtIndexPath(indexPath, animated: true)
}

我们还需要更新prepareForSegue(segue:sender:)来处理我们刚才创建的segue。 这意味着我们需要在switch语句中添加一个新的case

// MARK: -
// MARK: Segue Life Cycle
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
    guard let identifier = segue.identifier else { return }
    
    switch identifier {
    case SegueList:
        // Fetch Destination View Controller
        let listViewController = segue.destinationViewController as! ListViewController
        
        // Fetch Selection
        let list = lists[tableView.indexPathForSelectedRow!.row]
        
        // Configure View Controller
        listViewController.list = list
    case SegueListDetail:
        ...
    default:
        break
    }
}

为了满足编译器,我们还需要声明SegueListListsViewController.swift顶部不变。

import UIKit
import CloudKit
import SVProgressHUD

let RecordTypeLists = "Lists"

let SegueList = "List"
let SegueListDetail = "ListDetail"

class ListsViewController: UIViewController, UITableViewDataSource, UITableViewDelegate, AddListViewControllerDelegate {
    ...
}

生成并运行该应用程序,以查看所有内容是否正确连接。 由于我们尚未实现fetchItems方法,因此不会显示任何项目。 这是我们需要解决的问题。

2.提取项目

步骤1:创建记录类型

在我们从CloudKit后端获取项目之前,我们需要在CloudKit仪表板中创建一个新的记录类型。 导航到CloudKit仪表板,创建一个新的记录类型,并将其命名为Items 。 每个项目都应有一个名称,因此创建一个新字段,将字段名称设置为name ,并将字段类型设置为String

每个项目还应该知道它属于哪个购物清单。 这意味着每个商品都需要参考其购物清单。 创建一个新字段,将字段名称设置为list ,并将字段类型设置为Reference 。 “ 引用”字段类型就是为此特定目的而设计的,用于管理关系。

项目记录类型

回到Xcode,打开ListsViewController.swift ,并在顶部为Items记录类型声明一个新常量。

import UIKit
import CloudKit
import SVProgressHUD

let RecordTypeLists = "Lists"
let RecordTypeItems = "Items"

let SegueList = "List"
let SegueListDetail = "ListDetail"

class ListsViewController: UIViewController, UITableViewDataSource, UITableViewDelegate, AddListViewControllerDelegate {
    ...
}

步骤2:提取项目

打开ListViewController.swift并导航到fetchItems方法。 该实现类似于ListsViewController类的fetchLists方法。 但是,有一个重要的区别。

private func fetchItems() {
    // Fetch Private Database
    let privateDatabase = CKContainer.defaultContainer().privateCloudDatabase
    
    // Initialize Query
    let reference = CKReference(recordID: list.recordID, action: .DeleteSelf)
    let query = CKQuery(recordType: RecordTypeItems, predicate: NSPredicate(format: "list == %@", reference))
    
    // Configure Query
    query.sortDescriptors = [NSSortDescriptor(key: "name", ascending: true)]
    
    // Perform Query
    privateDatabase.performQuery(query, inZoneWithID: nil) { (records, error) -> Void in
        dispatch_async(dispatch_get_main_queue(), { () -> Void in
            // Process Response on Main Thread
            self.processResponseForQuery(records, error: error)
        })
    }
}

fetchItemsfetchLists之间的区别是我们传递给CKQuery初始化程序的谓词。 我们对用户的私有数据库中的每个项目都不感兴趣。 我们只对与特定购物清单相关的商品感兴趣。 这反映在CKQuery实例的谓词中。

我们通过传入CKReference实例来创建谓词,该实例是通过调用init(recordID:action:) 。 这个方法接受两个参数:一个CKRecordID实例引用的购物清单记录和CKReferenceAction确定何时被删除的购物清单会发生什么情况。

参考操作与核心数据中的删除规则非常相似。 如果删除了引用的对象(在此示例中为购物清单),则CloudKit框架将检查引用操作,以确定持有对已删除记录的引用的记录应如何处理。 CKReferenceAction枚举有两个成员值:

  • None :如果删除了引用,则引用已删除记录的记录不会发生任何事情。
  • DeleteSelf :如果删除引用,则引用删除记录的每个记录也会被删除。

因为没有购物清单,不应该存在任何商品,因此我们将引用动作设置为DeleteSelf

processResponseForQuery(records:error:)方法不包含任何新内容。 我们处理查询的响应并相应地更新用户界面。

private func processResponseForQuery(records: [CKRecord]?, error: NSError?) {
    var message = ""
    
    if let error = error {
        print(error)
        message = "Error Fetching Items for List"
        
    } else if let records = records {
        items = records
        
        if items.count == 0 {
            message = "No Items Found"
        }
        
    } else {
        message = "No Items Found"
    }
    
    if message.isEmpty {
        tableView.reloadData()
    } else {
        messageLabel.text = message
    }
    
    updateView()
}

生成并运行该应用程序。 您还看不到任何商品,但用户界面应更新以反映购物清单为空。

3.添加项目

步骤1:建立AddItemViewController

是时候实现将商品添加到购物清单的功能了。 首先创建一个新的UIViewController子类AddItemViewController 。 视图控制器的接口类似于AddListViewController类的接口。

在顶部,我们导入CloudKitSVProgressHUD框架。 我们声明了AddItemViewControllerDelegate协议,该协议的作用与AddListViewControllerDelegate协议相同。 该协议定义了两种方法,一种用于添加项目,另一种用于更新项目。

import UIKit
import CloudKit
import SVProgressHUD

protocol AddItemViewControllerDelegate {
    func controller(controller: AddItemViewController, didAddItem item: CKRecord)
    func controller(controller: AddItemViewController, didUpdateItem item: CKRecord)
}

class AddItemViewController: UIViewController {
    
    @IBOutlet weak var nameTextField: UITextField!
    @IBOutlet weak var saveButton: UIBarButtonItem!
    
    var delegate: AddItemViewControllerDelegate?
    var newItem: Bool = true
    
    var list: CKRecord!
    var item: CKRecord?
    
    ...
    
}

我们声明两个出口,一个文本字段和一个条形按钮项。 我们还为委托声明了一个属性,并声明了一个帮助程序变量newItem ,该变量有助于我们确定是要创建新项目还是更新现有项目。 最后,我们声明一个属性list以引用要添加该商品的购物清单,以及一个我们正在创建或更新的商品的属性item

在创建用户界面之前,让我们在情节提要中实现我们需要执行的两个动作: cancel(_:)save(_:) 。 在本教程的后面,我们将更新save(_:)操作的实现。

// MARK: -
// MARK: Actions
@IBAction func cancel(sender: AnyObject) {
    navigationController?.popViewControllerAnimated(true)
}

@IBAction func save(sender: AnyObject) {
    navigationController?.popViewControllerAnimated(true)
}

步骤2:建立使用者介面

打开Main.storyboard ,在列表视图控制器的导航栏中添加一个按钮按钮项,然后在Attributes Inspector中将System Item设置为Add 。 从对象库中拖动视图控制器,并将其类设置为AddItemViewController 。 从我们刚刚创建的条形按钮项到添加项视图控制器,创建一个序列。 从弹出的菜单中选择显示 ,然后将segue的标识符设置为ItemDetail

将两个条形按钮项添加到“添加项”视图控制器的导航栏中,左侧为“取消”按钮,右侧为“保存”按钮。 将每个条形按钮项连接到其相应的动作。 在视图控制器的视图中添加一个文本字段,不要忘记连接视图控制器的插座。 这就是完成后添加项目视图控制器的外观。

添加项目视图控制器

步骤3:配置视图控制器

添加项目视图控制器的实现不包含我们尚未讨论的内容。 不过,有一个例外,我们将在稍后讨论。 让我们从在viewDidLoad配置视图控制器开始。

我们调用一个辅助方法setupView ,并更新newItem的值。 如果item属性等于nil ,则newItem等于true 。 这有助于我们确定是要创建还是更新购物清单项目。

我们还将视图控制器添加为UITextFieldTextDidChangeNotification类型的通知的观察者。 这意味着当nameTextField的内容更改时,将通知视图控制器。

// MARK: -
// MARK: View Life Cycle
override func viewDidLoad() {
    super.viewDidLoad()
    
    setupView()
    
    // Update Helper
    newItem = item == nil
    
    // Add Observer
    let notificationCenter = NSNotificationCenter.defaultCenter()
    notificationCenter.addObserver(self, selector: "textFieldTextDidChange:", name: UITextFieldTextDidChangeNotification, object: nameTextField)
}

viewDidAppear(animated:) ,我们通过调用显示键盘becomeFirstRespondernameTextField

override func viewDidAppear(animated: Bool) {
    nameTextField.becomeFirstResponder()
}

setupView方法调用两个帮助程序方法, updateNameTextFieldupdateSaveButton 。 这些辅助方法的实现很简单。 在updateNameTextField ,我们填充文本字段。 在updateSaveButton ,我们根据文本字段的内容启用或禁用保存按钮。

// MARK: -
// MARK: View Methods
private func setupView() {
    updateNameTextField()
    updateSaveButton()
}

// MARK: -
private func updateNameTextField() {
    if let name = item?.objectForKey("name") as? String {
        nameTextField.text = name
    }
}

// MARK: -
private func updateSaveButton() {
    let text = nameTextField.text
    
    if let name = text {
        saveButton.enabled = !name.isEmpty
    } else {
        saveButton.enabled = false
    }
}

在查看save(_:)方法的更新实现之前,我们需要实现textFieldDidChange(_:)方法。 我们要做的就是调用updateSaveButton以启用或禁用保存按钮。

// MARK: -
// MARK: Notification Handling
func textFieldTextDidChange(notification: NSNotification) {
    updateSaveButton()
}

步骤4:保存项目

save(_:)方法是AddItemViewController类中最有趣的方法,因为它向我们展示了如何使用CloudKit引用。 看一下下面的save(_:)方法的实现。

由于我们在AddListViewController类中介绍了保存记录,因此大多数实现看起来都很熟悉。 我们最感兴趣的是该商品如何保持对其购物清单的引用。 我们首先通过调用指定的初始化程序init(recordID:action:)创建CKReference实例。 几分钟前,当我们创建用于获取购物清单项目的查询时,我们介绍了创建CKReference实例的详细信息。

告诉项目有关此参考的内容很容易。 我们在item属性上调用setObjec(_:forKey:) ,传入CKReference实例作为值,并以"list"作为键。 密钥对应于我们在CloudKit仪表板中分配的字段名称。 将项目保存到iCloud与我们之前介绍的相同。 这就是使用CloudKit引用的难易程度。

@IBAction func save(sender: AnyObject) {
    // Helpers
    let name = nameTextField.text
    
    // Fetch Private Database
    let privateDatabase = CKContainer.defaultContainer().privateCloudDatabase
    
    if item == nil {
        // Create Record
        item = CKRecord(recordType: RecordTypeItems)
        
        // Initialize Reference
        let listReference = CKReference(recordID: list.recordID, action: .DeleteSelf)
        
        // Configure Record
        item?.setObject(listReference, forKey: "list")
    }
    
    // Configure Record
    item?.setObject(name, forKey: "name")
    
    // Show Progress HUD
    SVProgressHUD.show()
    
    // Save Record
    privateDatabase.saveRecord(item!) { (record, error) -> Void in
        dispatch_async(dispatch_get_main_queue(), { () -> Void in
            // Dismiss Progress HUD
            SVProgressHUD.dismiss()
            
            // Process Response
            self.processResponse(record, error: error)
        })
    }
}

processResponse(record:error:)的实现没有新内容。 我们检查是否弹出任何错误,如果没有错误抛出,我们会通知委托人。

// MARK: -
// MARK: Helper Methods
private func processResponse(record: CKRecord?, error: NSError?) {
    var message = ""
    
    if let error = error {
        print(error)
        message = "We were not able to save your item."
        
    } else if record == nil {
        message = "We were not able to save your item."
    }
    
    if !message.isEmpty {
        // Initialize Alert Controller
        let alertController = UIAlertController(title: "Error", message: message, preferredStyle: .Alert)
        
        // Present Alert Controller
        presentViewController(alertController, animated: true, completion: nil)
        
    } else {
        // Notify Delegate
        if newItem {
            delegate?.controller(self, didAddItem: item!)
        } else {
            delegate?.controller(self, didUpdateItem: item!)
        }
        
        // Pop View Controller
        navigationController?.popViewControllerAnimated(true)
    }
}

步骤5:更新ListViewController

ListViewController类中,我们仍有一些工作要做。 首先使ListViewController类符合AddItemViewControllerDelegate协议。 这也是一个使用标识符ItemDetail为segue声明常量的好时机。

import UIKit
import CloudKit
import SVProgressHUD

let SegueItemDetail = "ItemDetail"

class ListViewController: UIViewController, UITableViewDataSource, UITableViewDelegate, AddItemViewControllerDelegate {
    ...
}

实现AddItemViewControllerDelegate协议很简单。 在controller(_:didAddItem:) ,我们将新项目添加到items ,对items排序,重新加载表视图,然后调用updateView

// MARK: -
// MARK: Add Item View Controller Delegate Methods
func controller(controller: AddItemViewController, didAddItem item: CKRecord) {
    // Add Item to Items
    items.append(item)
    
    // Sort Items
    sortItems()
    
    // Update Table View
    tableView.reloadData()
    
    // Update View
    updateView()
}

controller(_:didUpdateItem:)的实现更加容易。 我们对items排序并重新加载表格视图。

func controller(controller: AddItemViewController, didUpdateItem item: CKRecord) {
    // Sort Items
    sortItems()
    
    // Update Table View
    tableView.reloadData()
}

sortItems ,我们使用sortInPlace函数( MutableCollectionType协议的一种方法)按名称对CKRecord实例的数组进行排序。

private func sortItems() {
    items.sortInPlace {
        var result = false
        let name0 = $0.objectForKey("name") as? String
        let name1 = $1.objectForKey("name") as? String
        
        if let itemName0 = name0, itemName1 = name1 {
            result = itemName0.localizedCaseInsensitiveCompare(itemName1) == .OrderedAscending
        }
        
        return result
    }
}

我们还需要实现另外两个功能:更新和删除购物清单项目。

步骤6:删除项目

要删除项目,我们需要实现UITableViewDataSource协议的tableView(_:commitEditingStyle:forRowAtIndexPath:) 。 我们获取需要删除的购物清单项目,并将其传递给deleteRecord(_:)方法。

func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
    guard editingStyle == .Delete else { return }
    
    // Fetch Record
    let item = items[indexPath.row]
    
    // Delete Record
    deleteRecord(item)
}

deleteRecord(_:)实现不包含任何新内容。 我们在私有数据库上调用deleteRecordWithID(_:completionHandler:)并在完成处理程序中处理响应。

private func deleteRecord(item: CKRecord) {
    // Fetch Private Database
    let privateDatabase = CKContainer.defaultContainer().privateCloudDatabase
    
    // Show Progress HUD
    SVProgressHUD.show()
    
    // Delete List
    privateDatabase.deleteRecordWithID(item.recordID) { (recordID, error) -> Void in
        dispatch_async(dispatch_get_main_queue(), { () -> Void in
            // Dismiss Progress HUD
            SVProgressHUD.dismiss()
            
            // Process Response
            self.processResponseForDeleteRequest(item, recordID: recordID, error: error)
        })
    }
}

processResponseForDeleteRequest(record:recordID:error:) ,我们更新了items属性和用户界面。 如果出现问题,我们会通过显示警报来通知用户。

private func processResponseForDeleteRequest(record: CKRecord, recordID: CKRecordID?, error: NSError?) {
    var message = ""
    
    if let error = error {
        print(error)
        message = "We are unable to delete the item."
        
    } else if recordID == nil {
        message = "We are unable to delete the item."
    }
    
    if message.isEmpty {
        // Calculate Row Index
        let index = items.indexOf(record)
        
        if let index = index {
            // Update Data Source
            items.removeAtIndex(index)
            
            if items.count > 0 {
                // Update Table View
                tableView.deleteRowsAtIndexPaths([NSIndexPath(forRow: index, inSection: 0)], withRowAnimation: .Right)
                
            } else {
                // Update Message Label
                messageLabel.text = "No Items Found"
                
                // Update View
                updateView()
            }
        }
        
    } else {
        // Initialize Alert Controller
        let alertController = UIAlertController(title: "Error", message: message, preferredStyle: .Alert)
        
        // Present Alert Controller
        presentViewController(alertController, animated: true, completion: nil)
    }
}

步骤7:更新项目

用户可以通过点击详细信息显示指示器来更新项目。 这意味着我们需要实现tableView(_:accessoryButtonTappedForRowWithIndexPath:)委托方法。 在这种方法中,我们存储用户的选择并手动执行ListDetail segue。 请注意, tableView(_:didSelectRowAtIndexPath:)方法中没有任何反应。 我们要做的就是取消选择用户点击的行。

// MARK: -
// MARK: Table View Delegate Methods
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
    tableView.deselectRowAtIndexPath(indexPath, animated: true)
}

func tableView(tableView: UITableView, accessoryButtonTappedForRowWithIndexPath indexPath: NSIndexPath) {
    tableView.deselectRowAtIndexPath(indexPath, animated: true)
    
    // Save Selection
    selection = indexPath.row
    
    // Perform Segue
    performSegueWithIdentifier(SegueItemDetail, sender: self)
}

prepareForSegue(_:sender:) ,我们使用selection属性的值获取购物清单项目,并配置目标视图控制器( AddItemViewController类的实例)。

// MARK: -
// MARK: Segue Life Cycle
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
    guard let identifier = segue.identifier else { return }
    
    switch identifier {
    case SegueItemDetail:
        // Fetch Destination View Controller
        let addItemViewController = segue.destinationViewController as! AddItemViewController
        
        // Configure View Controller
        addItemViewController.list = list
        addItemViewController.delegate = self
        
        if let selection = selection {
            // Fetch Item
            let item = items[selection]
            
            // Configure View Controller
            addItemViewController.item = item
        }
    default:
        break
    }
}

这就是我们删除和更新购物清单项目所需要做的。 在下一节中,我将向您展示在CloudKit仪表板中更新数据模型有多么容易。

4.更新数据模型

如果您曾经使用过Core Data,那么您会知道更新数据模型时应谨慎行事。 您需要确保您不会破坏任何东西或破坏应用程序的任何持久性存储。 CloudKit更加灵活。

Items记录类型当前具有两个字段, namelist 。 我想向您展示通过添加新字段来更新数据模型的过程。 打开CloudKit仪表板,然后向Items记录添加一个新字段。 将字段名称设置为number并将字段类型设置为Int(64) 。 不要忘记保存您的更改。

更新项目记录类型

现在,让我们添加修改项目编号的功能。 打开AddItemViewController.swift并声明两个出口,一个标签和一个步进器。

import UIKit
import CloudKit
import SVProgressHUD

protocol AddItemViewControllerDelegate {
    func controller(controller: AddItemViewController, didAddItem item: CKRecord)
    func controller(controller: AddItemViewController, didUpdateItem item: CKRecord)
}

class AddItemViewController: UIViewController {
    
    @IBOutlet weak var numberLabel: UILabel!
    @IBOutlet weak var numberStepper: UIStepper!
    @IBOutlet weak var nameTextField: UITextField!
    @IBOutlet weak var saveButton: UIBarButtonItem!
    
    var delegate: AddItemViewControllerDelegate?
    var newItem: Bool = true
    
    var list: CKRecord!
    var item: CKRecord?
    
    ...
    
}

我们还需要添加一个在步进器的值更改时触发的动作。 在numberLabel numberDidChange(_:) ,我们更新numberLabel的内容。

@IBAction func numberDidChange(sender: UIStepper) {
    let number = Int(sender.value)
    
    // Update Number Label
    numberLabel.text = "\(number)"
}

打开Main.storyboard并将标签和步进器添加到添加项目视图控制器。 将视图控制器的出口连接到相应的用户界面元素,并将numberDidChange(_:)操作连接到Value Value事件的步进器。

更新添加项目视图控制器

AddItemViewController类的save(_:)操作也略有变化。 让我们看看它是什么样的。

我们只需要添加两行代码。 在顶部,我们将步进器的值存储在常数number 。 配置item ,我们将number设置为数字键的值,仅此而已。

@IBAction func save(sender: AnyObject) {
    // Helpers
    let name = nameTextField.text
    let number = Int(numberStepper.value)
    
    // Fetch Private Database
    let privateDatabase = CKContainer.defaultContainer().privateCloudDatabase
    
    if item == nil {
        // Create Record
        item = CKRecord(recordType: RecordTypeItems)
        
        // Initialize Reference
        let listReference = CKReference(recordID: list.recordID, action: .DeleteSelf)
        
        // Configure Record
        item?.setObject(listReference, forKey: "list")
    }
    
    // Configure Record
    item?.setObject(name, forKey: "name")
    item?.setObject(number, forKey: "number")
    
    // Show Progress HUD
    SVProgressHUD.show()
    
    print(item?.recordType)
    
    // Save Record
    privateDatabase.saveRecord(item!) { (record, error) -> Void in
        dispatch_async(dispatch_get_main_queue(), { () -> Void in
            // Dismiss Progress HUD
            SVProgressHUD.dismiss()
            
            // Process Response
            self.processResponse(record, error: error)
        })
    }
}

我们还需要实现一个辅助方法来更新添加项视图控制器的用户界面。 updateNumberStepper方法检查记录是否具有名为number的字段,并更新步进器。

private func updateNumberStepper() {
    if let number = item?.objectForKey("number") as? Double {
        numberStepper.value = number
    }
}

我们在AddItemViewController类的setupView方法中调用updateNumberStepper

private func setupView() {
    updateNameTextField()
    updateNumberStepper()
    updateSaveButton()
}

为了可视化每个项目的数量,我们需要对ListViewController进行一次更改。 在tableView(_:cellForRowAtIndexPath:) ,我们将单元格右标签的内容设置为项目的数字字段的值。

func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
    // Dequeue Reusable Cell
    let cell = tableView.dequeueReusableCellWithIdentifier(ListViewController.ItemCell, forIndexPath: indexPath)
    
    // Configure Cell
    cell.accessoryType = .DetailDisclosureButton
    
    // Fetch Record
    let item = items[indexPath.row]
    
    if let itemName = item.objectForKey("name") as? String {
        // Configure Cell
        cell.textLabel?.text = itemName
        
    } else {
        cell.textLabel?.text = "-"
    }
    
    if let itemNumber = item.objectForKey("number") as? Int {
        // Configure Cell
        cell.detailTextLabel?.text = "\(itemNumber)"
        
    } else {
        cell.detailTextLabel?.text = "1"
    }
    
    return cell
}

这就是我们实现对数据模型所做的更改所需要做的所有工作。 无需执行迁移或类似的操作。 CloudKit处理细腻的细节。

结论

现在,您应该在CloudKit框架中拥有适当的基础。 我希望您同意Apple在此框架和CloudKit Dashboard方面做得很好。 本系列文章中没有涉及很多内容,但是到目前为止,您已经学到了足够的知识,可以在自己的项目中开始使用CloudKit。

如果您有任何问题或意见,请随时将它们留在下面的评论中,或通过Twitter与我联系。

翻译自: https://code.tutsplus.com/tutorials/building-a-shopping-list-application-with-cloudkit-adding-relationships--cms-25342

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值