在本系列的上一篇文章中 ,我们添加了添加,更新和删除购物清单的功能。 但是,其中没有任何物品的购物清单不是很有用。 在本教程中,我们将添加从购物清单添加,更新和删除商品的功能。 这意味着我们将使用引用和CKReference
类。
我们还将仔细研究购物清单应用程序的数据模型。 更改数据模型有多容易?应用程序如何响应我们在CloudKit仪表板中所做的更改?
先决条件
请记住,我将使用Xcode 9和Swift 3 。 如果您使用的是旧版本的Xcode,请记住您正在使用其他版本的Swift编程语言。
在本教程中,我们将继续在本系列的上一篇文章中停下的地方。 您可以从GitHub下载或克隆项目。
1.购物清单明细
当前,用户可以通过点击详细公开指示符来修改购物清单的名称,但是用户也应该能够通过在列表视图控制器中点击一个来查看购物清单的内容。 为了使它起作用,我们首先需要一个新的UIViewController
子类。
步骤1:建立ListViewController
ListViewController
类将在表视图中显示购物清单的内容。 ListViewController
类的接口看起来类似于ListsViewController
类的接口。 我们导入CloudKit和SVProgressHUD框架,并使该类符合UITableViewDataSource
和UITableViewDelegate
协议。 因为我们将使用表视图,所以我们声明一个常量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
的实现。 我们将视图控制器的标题设置为购物清单的名称,并调用两个帮助器方法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
属性中的items
更新视图控制器的用户界面。
private func updateView() {
let hasRecords = items.count > 0
tableView.hidden = !hasRecords
messageLabel.hidden = hasRecords
activityIndicatorView.stopAnimating()
}
我现在fetchItems
将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
类。 首先实现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
}
}
为了满足编译器,我们还需要声明SegueList
在ListsViewController.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)
})
}
}
fetchItems
和fetchLists
之间的区别是我们传递给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
类的接口。
在顶部,我们导入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的标识符设置为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:)
,我们通过调用显示键盘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
类中介绍了保存记录,因此大多数实现看起来都很熟悉。 我们最感兴趣的是该商品如何保持对其购物清单的引用。 我们首先通过调用指定的初始化程序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记录类型当前具有两个字段, name和list 。 我想向您展示通过添加新字段来更新数据模型的过程。 打开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与我联系。