• 日常搜索
  • 百度一下
  • Google
  • 在线工具
  • 搜转载

使用CloudKit创建购物清单应用程序

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

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

先决条件

请记住,我将使用Xcode 9 和 swift 3。如果您使用的是旧版本的 Xcode,请记住您使用的是不同版本的 Swift 编程语言

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

1.购物清单详情

目前,用户可以通过点击详细信息披露指示器来修改购物清单的名称,但用户也应该能够通过在列表视图控制器中点击一个来查看购物清单的内容。为了完成这项工作,我们首先需要一个新的UIViewController子类。

第 1 步:创建ListViewController

该类ListViewController将在表格视图中显示购物清单的内容。该类的接口ListViewController看起来与该类的接口相似ListsViewController。我们导入CloudKit和SVProgre ssh UD框架并使类符合uitableviewdataSource和UITableViewDelegate协议。因为我们将使用表格视图,所以我们声明了一个const ant,  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?

    ...

}

我们声明了三个出口:messageLabelof type UILabel!、  tableViewof typeUITableView!和 activityIndicatorViewof type UIActivityIndicatorView!。列表视图控制器保留对它在属性中显示的购物清单的引用,该list属性的类型为CKRecord!。购物清单中的项目存储在items类型为 的属性中[CKRecord]。最后,我们使用辅助变量 ,selection来跟踪用户选择了购物清单中的哪个项目。这将在本教程的后面部分变得清晰。

第 2 步:创建用户界面

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

将表格视图、标签和活动指示器视图添加到视图控制器的视图。不要忘记连接视图控制器的出口以及表格视图的出口。

选择表格视图并 在 Attributes Inspector中将Prototype Cells设置为 1。选择原型单元格,将Style 设置为 Right Detail,将Identifier设置为 ItemCell, 将Accessory 设置为 Disclosure Indicator。这是完成后视图控制器的外观。

使用CloudKit创建购物清单应用程序  第1张

第三步:配置视图控制器

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

// 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属性中的项目更新视图控制器的用户界面。

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

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

// 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课程。从实现协议的 tableView(_:didSelectRowAtIndexPath:)方法开始UITableViewDelegate。

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

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

// 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
    }
}

为了满足编译器的要求,我们还需要在ListsViewController.swiftSegueList的顶部声明常量。

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。Reference 字段类型正是为此目的而设计的,即管理关系。

使用CloudKit创建购物清单应用程序  第2张返回 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方法。实现类似于类的fetchLists方法ListsViewController。但是,有一个重要的区别。

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)
        })
    }
}

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

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

引用操作与 Core Data 中的删除规则非常相似。如果引用的对象(本示例中的购物清单)被删除,则 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类的接口。

在顶部,我们导入 CloudKit和SVProgressHUD 框架。我们声明了 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。从弹出的菜单中 选择 Show ,并将 segue 的标识符设置为ItemDetail。

在添加项视图控制器的导航栏中添加两个条形按钮项,左侧是取消按钮,右侧是保存按钮。将每个条形按钮项连接到其相应的操作。在视图控制器的视图中添加一个文本字段,不要忘记连接视图控制器的出口。这是添加项目视图控制器完成后的样子。

使用CloudKit创建购物清单应用程序  第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:)中,我们通过调用becomeFirstResponder来显示键盘nameTextField。

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

该setupView方法调用两个辅助方法,updateNameTextField并且 updateSaveButton. 这些辅助方法的实现很简单。在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类中介绍了保存记录。我们最感兴趣的是该商品如何保持对其购物清单的引用。我们首先CKReference通过调用指定的初始化程序来创建一个实例 init(recordID:action:)。CKReference几分钟前,当我们创建用于获取购物清单项目的查询时,我们介绍了创建实例的细节。

告诉项目有关此参考的信息很容易。我们调用属性,将setObjec(_:forKey:)实例作为值和键传入。键对应于我们在 CloudKit Dashboard 中分配的字段名称。将项目保存到 icloud 与我们之前介绍的相同。这就是使用 CloudKit 引用的容易程度。itemCKReference"list"

@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数组进行排序。CKRecordsortInPlaceMutableCollectionType

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 步:删除项目

要删除项目,我们需要实现 tableView(_:commitEditingStyle:forRowAtIndexPath:)协议UITableViewDataSource。我们获取需要删除的购物清单项目并将其传递给该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记录类型目前 有两个字段,  name和 list。我想向您展示通过添加新字段来更新数据模型所涉及的内容。打开 CloudKit 仪表板并向 Items记录添加一个新字段。将字段名称设置为 number并将字段类型设置为 Int(64)。不要忘记保存您的更改。

使用CloudKit创建购物清单应用程序  第4张

现在让我们添加修改项目编号的功能。打开 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?
    
    ...
    
}

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

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

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

使用CloudKit创建购物清单应用程序  第5张

班级的save(_:)行动AddItemViewController也略有变化。让我们看看它是什么样子的。

我们只需要添加两行代码。在顶部,我们将步进器的值存储在一个常数中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
    }
}

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

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 仪表板方面做得很好。在本系列中我们还没有涵盖很多内容,但现在您已经学到了足够的知识,可以开始在您自己的项目中使用 CloudKit。


文章目录
  • 先决条件
  • 1.购物清单详情
    • 第 1 步:创建ListViewController
    • 第 2 步:创建用户界面
    • 第三步:配置视图控制器
    • 第 4 步:表视图数据源方法
    • 第 5 步:处理选择
  • 2.获取物品
    • 第 1 步:创建记录类型
    • 第 2 步:获取项目
  • 3.添加项目
    • 第 1 步:创建 AddItemViewController
    • 第 2 步:创建用户界面
    • 第三步:配置视图控制器
    • 第 4 步:保存项目
    • 第 5 步:更新ListViewController
    • 第 6 步:删除项目
    • 第 7 步:更新项目
  • 4.更新数据模型
  • 结论