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

使用CloudKit创建购物清单应用程序:添加记录

在 本系列的第一个教程中,我们探索了 CloudKit框架和基础架构。我们还为我们将要构建的示例应用程序(一个购物清单应用程序)奠定了基础。在本教程中,我们将重点介绍添加、编辑和删除购物清单。

先决条件

正如我在上一篇教程中提到的 ,我将使用 Xcode 9 和 swift 4。如果您使用的是旧版本的 Xcode,请记住您可能使用的是不同版本的 Swift 编程语言

在本教程中,我们将继续使用我们在第 一个教程中创建的项目。您可以从 GitHub下载它 (标签添加记录)。

1. 设置 CocoaPods

购物清单应用程序将使用 SVProgressHUD库,这是一个由Sam Vermette 创建的流行库  ,可以轻松显示进度指示器。您可以手动将库添加到您的项目中,但我强烈建议使用 CocoaPods 来管理依赖项。你是 CocoaPods 的新手吗?阅读这份 CocoaPods 的介绍性教程 ,以加快速度。

第 1 步:创建 Podfile

打开 finder 并导航到 Xcode 项目的根目录。创建一个新文件,将其命名为 Podfile,并向其中添加以下 Ruby 行。

# Uncomment the next line to define a global platform for your project
# platform :ios, '9.0'target 'Lists' do
  # Comment the next line if you're not using Swift and don't want to use dynamic frameworks
  use_frameworks!

  pod 'SVProgresshUD', '~> 1.1'

end

第一行指定平台 iOS和项目的部署目标 iOS 9.0。如果您使用的是 Swift,第二行很重要。Swift 不支持静态库,但 CocoaPods 从 0.36版本 开始提供使用框架的选项。然后,我们为项目的Lists 目标指定依赖 项。 如果您的目标名称不同,则将Lists替换为您的目标名称。 

第 2 步:安装依赖项

打开 终端,导航到 Xcode 项目的根目录,然后运行 pod install. 这将为您做很多事情,例如安装 Podfile中指定的依赖项 和创建 Xcode 工作区。

完成 CocoaPods 设置后,关闭项目并打开为您创建的工作区 CocoaPods。后者非常重要。 打开工作区,而不是项目。工作区包括两个项目,  Lists 项目和一个名为 Pods的项目。

2. 列出购物清单

第 1 步:家政服务

我们已准备好重新关注 CloudKit 框架。但是,首先,我们需要通过重命名 ViewController 上课 ListsViewController 上课。

首先将 ViewController.swift重命名 为 ListsViewController.swift。打开 ListsViewController.swift 并更改名称 ViewController 上课 ListsViewController 上课。

接下来,打开 Main.storyboard , 在  左侧 Document Outline中展开 View Controller  Scene ,选择View Controller。打开 右侧 的Identity Inspector 并将Class更改 为 ListsViewController。

使用CloudKit创建购物清单应用程序:添加记录  第1张

第 2 步:添加表格视图

当用户打开应用程序时,他们会看到他们的购物清单。我们将在表格视图中显示购物清单。让我们从设置用户界面开始。在 Lists View Controller Scene中选择 Lists  View Controller,然后 从 Xcode 的 Editor 菜单中 选择 Embed In > Navigation Controller 。

将表格视图添加到视图控制器的视图并为其创建必要的布局约束。选择表格视图后,打开 Attributes Inspector 并将 Prototype Cells设置 为 1。选择原型单元并将 Style设置 为 Basic 并将 Identifier设置 为 ListCell。

使用CloudKit创建购物清单应用程序:添加记录  第2张选择表视图后,打开 Connections Inspector。将 table view dataSource 和 delegate outlets 连接到 Lists View Controller。

第 3 步:空状态

尽管我们只是创建一个示例应用程序来说明 CloudKit 的工作原理,但如果出现问题或在 icloud 上找不到购物清单,我想显示一条消息。向视图控制器添加标签,使其与视图控制器的视图一样大,为其创建必要的布局约束,并使标签的文本居中。

因为我们正在处理网络请求,所以只要应用程序正在等待来自 iCloud的响应,我还想显示一个活动指示器视图。将活动指示器视图添加到视图控制器的视图,并将其置于其父视图的中心。在 Attributes Inspector中,勾选标记为 Hides When Stopped的复选框。

使用CloudKit创建购物清单应用程序:添加记录  第3张

第 4 步:连接插座

打开 ListsViewController.swift 并为标签、表格视图和活动指示器视图声明一个出口。这也是使 ListsViewController 类符合 uitableviewDataSource 和 UITableViewDelegate 协议的好时机。

请注意,我还为 SVProgressHUD 框架添加了一个导入语句,并且我为我们在情节提要中创建的原型单元格的重用标识符声明了一个静态常量。

import UIKit
import CloudKit
import SVProgressHUD

class ListsViewController: UIViewController, UITableViewDelegate, UITableViewDataSource{
    static let ListCell = "ListCell"
    @IBOutlet weak var tableView: UITableView!
    @IBOutlet weak var messageLabel: UILabel!
    @IBOutlet weak var activityIndicatorView: UIActivityIndicatorView!
    ...
    
}

回到故事板并将出口与 Lists View Controller Scene中的相应视图连接起来。

第 5 步:准备表格视图

在从 iCloud获取数据之前,我们需要确保表格视图已准备好显示数据。我们首先需要创建一个属性, lists来保存我们将要获取的记录。请记住,记录是 CKRecord 类的实例。这意味着将保存来自 iCloud 的数据的属性是 type  ,一个 实例[CKRecord]数组 。CKRecord

import UIKit
import CloudKit
import SVProgressHUD

class ListsViewController: UIViewController, UITableViewDelegate, UITableViewDataSource{
    static let ListCell = "ListCell"
    @IBOutlet weak var tableView: UITableView!
    @IBOutlet weak var messageLabel: UILabel!
    @IBOutlet weak var activityIndicatorView: UIActivityIndicatorView!
    
    var lists = [CKRecord]()
    
    ...
    
}

首先,我们需要实现 UITableViewDataSource 协议的三个方法:

  • numberOfSectionsInTableView(_:)

  • numberOfRowsInSection(_:)

  • cellForRowAtIndexPath(_:)

如果您有任何使用表视图的经验,那么这些方法中的每一个的实现都很简单。但是, cellForRowAtIndexPath(_:) 可能需要一些解释。请记住, CKRecord 实例是键值对的增压字典。要访问特定键的值,请 objectForKey(_:) 在 CKRecord 对象上调用。这就是我们在 cellForRowAtIndexPath(_:). 我们获取对应于表视图行的记录,并要求它提供 key 的值 "name"。如果键值对不存在,我们会显示一个破折号来表示列表还没有名称。

// MARK: -
// MARK: UITableView Delegate Methods
extension ListsViewController{
    
    func numberOfSections(in tableView: UITableView) -> Int {
        return 1
    }
    
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return lists.count
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        // Dequeue Reusable Cell
        let cell = tableView.dequeueReusableCell(withIdentifier: ListsViewController.ListCell, for: indexPath)
        
        // Configure Cell
        cell.accessoryType = .detailDisclosureButton
        
        // fetch Record
        let list = lists[indexPath.row]
        
        if let listName = list.object(forKey: "name") as? String {
            // Configure Cell
            cell.textLabel?.text = listName
            
        } else {
            cell.textLabel?.text = "-"
        }
        
        return cell
    }

}

第 6 步:准备用户界面

我们还有一步要做:准备用户界面。在视图控制器的 viewDidLoad 方法中,删除 fetchUserRecordID 方法调用和调用 setupView,一个辅助方法。

override func viewDidLoad() {
    super.viewDidLoad()
    
    setupView()
}

该 setupView 方法准备用户界面以获取记录列表。我们隐藏标签和表格视图,并告诉活动指示器视图开始动画

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

在设备或 iOS 模拟器中构建并运行应用程序。如果您已按照上述步骤操作,您应该会看到一个空视图,中间有一个旋转的活动指示器视图。

使用CloudKit创建购物清单应用程序:添加记录  第4张

第 7 步:创建记录类型

在我们获取任何记录之前,我们需要在 CloudKit 仪表板中为购物清单创建一个记录类型。CloudKit 仪表板是一个 Web 应用程序,可让开发人员管理存储在 Apple iCloud 服务器上的数据。

在 Project Navigator中选择项目 并从目标列表中选择 Lists 目标。打开  顶部 的功能选项卡并展开iCloud 部分。在 iCloud 容器列表下方,单击标 有 CloudKit Dashboard的按钮。

使用CloudKit创建购物清单应用程序:添加记录  第5张使用您的开发者帐户登录,并确保在  左上角选择了Lists应用程序。在左侧,  从 Schema 部分中选择Record Types 。默认情况下,每个应用程序都有一个 用户 记录类型。要创建新的记录类型,请单击第三列顶部的加号按钮。我们将遵循 Apple 的命名约定并将记录类型命名为 Lists,而不是 List。

使用CloudKit创建购物清单应用程序:添加记录  第6张请注意,第一个字段是自动为您创建的。创建一个字段名称 并将 字段类型 设置为 String。不要忘记单击  底部 的保存按钮以创建列表 记录类型。我们将在本系列的后面部分重新访问 CloudKit 仪表板。 

接下来,通过转到 Indexes 选项卡并为 name 添加新的 SORTABLE 和另一个 QUERYABLE 索引类型,为您的文档属性启用索引,然后单击 Save。

使用CloudKit创建购物清单应用程序:添加记录  第7张最后,转到 SECURITY ROLES 选项卡,并出于本开发练习的目的,选中所有复选框以确保您的用户可以访问该表。 

第 8 步:执行查询

创建 Lists 记录类型后,终于可以从 iCloud 获取一些记录了。CloudKit 框架提供了两个api来与 iCloud 交互:一个方便的 API 和一个基于 NSOperation 类的 API。我们将在本系列中使用这两个 API,但现在我们将保持简单并使用便捷的 API。

在 Xcode 中,打开 ListsViewController.swift并 调用 fetchLists . viewDidLoad该 fetchLists 方法是另一种辅助方法。让我们看一下该方法的实现。

override func viewDidLoad() {
    super.viewDidLoad()
    
    setupView()
    fetchLists()
}

因为购物清单记录存储在用户的私有数据库中,所以我们首先获取对默认容器私有数据库的引用。要获取用户的购物清单,我们需要使用 CKQuery 类对私有数据库执行查询。

private func fetchLists() {
        // Fetch Private Database
        let privateDatabase = CKContainer.default().privateCloudDatabase
        
        // Initialize Query
        let query = CKQuery(recordType: "Lists", predicate: NSPredicate(value: true))
        
        // Configure Query
        query.sortDescriptors = [NSSortDescriptor(key: "name", ascending: true)]
        
        // Perform Query
        privateDatabase.perform(query, inZoneWith: nil) { (records, error) in
            records?.forEach({ (record) in
                
                guard error == nil else{
                    print(error?.localizedDescription as Any)
                    return
                }
                
                print(record.value(forKey: "name") ?? "")
                self.lists.append(record)
                DispatchQueue.main.sync {
                    self.tableView.reloadData()
                    self.messageLabel.text = ""
                    updateView()
                }
            })
    
        }
    }

我们 CKQuery 通过调用 init(recordType:predicate:) 指定的初始化程序、传入记录类型和一个 NSPredicate 对象来初始化一个实例。 

在执行查询之前,我们设置查询的 sortDescriptors 属性。我们创建一个数组,其中包含一个 NSSortDescriptor 对象,其键 "name" 和升序设置为 true。

执行查询就像调用 performQuery(_:inZoneWithID:completionHandler:) on 一样简单, 作为第一个参数privateDatabase传入 。query第二个参数指定将在其上执行查询的记录区域的标识符。通过传入 nil,查询在数据库的默认区域上执行,我们得到查询返回的每条记录的实例。 

在方法结束时,我们调用 updateView. 在这个辅助方法中,我们根据 lists 属性的内容更新用户界面。

  private func updateView(){
        let hasRecords = self.lists.count > 0
        
        self.tableView.isHidden = !hasRecords
        messageLabel.isHidden = hasRecords
        activityIndicatorView.stopAnimating()
    }

构建并运行应用程序以测试我们到目前为止所获得的内容。我们目前没有任何记录,但我们将在本教程的下一部分修复它。

使用CloudKit创建购物清单应用程序:添加记录  第8张

3. 添加购物清单

第 1 步:创建 AddListViewController 类

因为添加和编辑购物清单非常相似,我们将同时实现两者。创建一个新文件并将其命名为 AddListViewController.swift。打开新创建的文件并创建一个 UIViewController 名为 AddListViewController. 在顶部,为 UIKit、  CloudKit和 SVProgressHUD 框架添加导入语句。声明两个出口,一个是 type  UITextField! ,一个是 type  UIBarButtonItem!。最后但并非最不重要的一点是,创建两个动作 cancel(_:) 和 save(_:).

import UIKit
import CloudKit
import SVProgressHUD

class AddListViewController: UIViewController {
    
    @IBOutlet weak var nameTextField: UITextField!
    @IBOutlet weak var saveButton: UIBarButtonItem!
    
    @IBAction func cancel(sender: AnyObject) {
        
    }
    
    @IBAction func save(sender: AnyObject) {
        
    }
    
}

第 2 步:创建用户界面

打开 Main.storyboard 并将视图控制器添加到情节提要。选择视图控制器后,打开右侧的 Identity Inspector 并将 Class设置 为 AddListViewController。

使用CloudKit创建购物清单应用程序:添加记录  第9张用户将能够通过点击列表视图控制器中的按钮导航到添加列表视图控制器。 

将一个条形按钮项从 对象库拖到列表视图控制器 的导航栏 。选择条形按钮项目后,打开 Attributes Inspector 并将 System Item设置 为 Add。按 Control 并从栏按钮项拖动到添加列表视图控制器,然后  从出现的菜单中选择Show Detail 。

选择刚刚创建的 segue,并 在右侧 的 Attributes Inspector 中将Identifier设置 为 ListDetail。

使用CloudKit创建购物清单应用程序:添加记录  第10张向添加列表视图控制器的导航栏添加两个条形按钮项,一个在左侧,一个在右侧。将  左侧栏按钮项 的System Item设置为Cancel  ,将右侧栏按钮项的 System Item 设置为 Save。最后,向添加列表视图控制器添加一个文本字段。将文本字段  居中并在 Attributes Inspector中将其Alignment设置为居中。

使用CloudKit创建购物清单应用程序:添加记录  第11张

最后,将您在 AddListViewController.swift中创建的出口和操作连接 到场景中相应的用户界面元素。

第 3 步: AddListViewControllerDelegate 协议

在我们实现这个 AddListViewController 类之前,我们需要声明一个协议,我们将使用它来从添加列表视图控制器到列表视图控制器进行通信。该协议定义了两种方法,一种用于添加,一种用于更新购物清单。这就是协议的样子。

protocol AddListViewControllerDelegate {
    func controller(controller: AddListViewController, didAddList list: CKRecord)
    func controller(controller: AddListViewController, didUpdateList list: CKRecord)
}

我们还需要声明三个属性:一个用于委托,一个用于创建或更新的购物清单,以及一个帮助变量,指示我们是在创建新的购物清单还是编辑现有记录。

import UIKit
import CloudKit
import SVProgressHUD

protocol AddListViewControllerDelegate {
    func controller(controller: AddListViewController, didAddList list: CKRecord)
    func controller(controller: AddListViewController, didUpdateList list: CKRecord)
}

class AddListViewController: UIViewController {
    
    @IBOutlet weak var nameTextField: UITextField!
    @IBOutlet weak var saveButton: UIBarButtonItem!
    
    var delegate: AddListViewControllerDelegate?
    var newList: Bool = true
    
    var list: CKRecord?
    
    @IBAction func cancel(sender: AnyObject) {
        
    }
    
    @IBAction func save(sender: AnyObject) {
        
    }
    
}

类的实现 AddListViewController 很简单。与视图生命周期相关的方法简短易懂。在 viewDidLoad中,我们首先调用 setupView 辅助方法。我们稍后会实现这个方法。然后我们根据属性的值更新 newList 辅助变量的值 list 。如果 list 等于 nil,那么我们知道我们正在创建一条新记录。在 viewDidLoad中,我们还添加了视图控制器作为 UITextFieldTextDidChangeNotification 通知的观察者。

override func viewDidLoad() {
        super.viewDidLoad()
        
        self.setupView()
        
        // Update Helper
        self.newList = self.list == nil
        
        // Add Observer
        let notificationCenter = NotificationCenter.default
        notificationCenter.addObserver(self, selector: #selector(AddListViewController.textFieldTextDidChange(notification:)), name: NSNotification.Name.UITextFieldTextDidChange, object: nameTextField)
    }
    
    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        nameTextField.becomeFirstResponder()
    }

在 viewDidAppear(_:)中,我们调用 becomeFirstResponder 文本字段将键盘呈现给用户。

在 setupView中,我们调用了两个辅助方法, updateNameTextField 并且 updateSaveButton。在 中,如果 不是 updateNameTextField,我们填充文本字段 。换句话说,如果我们正在编辑现有记录,那么我们会使用该记录的名称填充文本字段。listnil

该 updateSaveButton 方法负责启用和禁用右上角的栏按钮项。如果购物清单的名称不是空字符串,我们只启用保存按钮。

private func setupView() {
        updateNameTextField()
        updateSaveButton()
    }
    
    // MARK: -
    private func updateNameTextField() {
        if let name = list?.object(forKey: "name") as? String {
            nameTextField.text = name
        }
    }
    
    // MARK: -
    private func updateSaveButton() {
        let text = nameTextField.text
        
        if let name = text {
            saveButton.isEnabled = !name.isEmpty
        } else {
            saveButton.isEnabled = false
        }
    }

第 4 步:实施行动

cancel(_:) 操作非常简单。 我们从导航堆栈中弹出顶部视图控制器。动作save(_:) 更有趣。 在此方法中,我们从文本字段中提取用户的输入并获取对默认容器私有数据库的引用。

如果我们要添加一个新的购物清单,那么我们 CKRecord 通过调用创建一个新实例 ,并 作为记录类型init(recordType:)传入 。RecordTypeLists然后我们通过设置键的记录值来更新购物清单的名称 "name"。

因为保存记录涉及网络请求并且可能需要大量时间,所以我们显示了一个进度指示器。要保存新记录或对现有记录的任何更改,我们调用 saveRecord(_:completionHandler:) , privateDatabase将记录作为第一个参数传入。第二个参数是另一个完成处理程序,当保存记录完成时调用,成功或不成功。

完成处理程序接受两个参数,一个可选的 CKRecord 和一个可选的 NSError。正如我之前提到的,可以在任何线程上调用完成处理程序,这意味着我们需要针对它编写代码。我们通过显式调用 processResponse(_:error:) 主线程上的方法来做到这一点。

@IBAction func cancel(sender: AnyObject) {
        self.dismiss(animated: true, completion: nil) 
    }
    
    @IBAction func save(sender: AnyObject) {
        
        // Helpers
        let name = self.nameTextField.text! as NSString
        
        // Fetch Private Database
        let privateDatabase = CKContainer.default().privateCloudDatabase
        
        if list == nil {
            list = CKRecord(recordType: "Lists")
        }
        
        // Configure Record
        list?.setObject(name, forKey: "name")
        
        // Show Progress HUD
        SVProgressHUD.show()
        
        // Save Record
        privateDatabase.save(list!) { (record, error) -> Void in
            DispatchQueue.main.sync {
                // Dismiss Progress HUD
                SVProgressHUD.dismiss()
                
                // Process Response
                self.processResponse(record: record, error: error)
            }

        }
    }

在 processResponse(_:error:)中,我们验证是否引发了错误。如果我们确实遇到了问题,我们会向用户显示警报。如果一切顺利,我们通知委托并从导航堆栈中弹出视图控制器。

// MARK: -
    // MARK: Helper Methods
    private func processResponse(record: CKRecord?, error: Error?) {
        var message = ""
        
        if let error = error {
            print(error)
            message = "We were not able to save your list."
            
        } else if record == nil {
            message = "We were not able to save your list."
        }
        
        if !message.isEmpty {
            // Initialize Alert Controller
            let alertController = UIAlertController(title: "Error", message: message, preferredStyle: .alert)
            
            // Present Alert Controller
            present(alertController, animated: true, completion: nil)
            
        } else {
            // Notify Delegate
            if newList {
                delegate?.controller(controller: self, didAddList: list!)
            } else {
                delegate?.controller(controller: self, didUpdateList: list!)
            }
            
            // Pop View Controller
            self.dismiss(animated: true, completion: nil)

        }
    }

最后但同样重要的是,当视图控制器收到 UITextFieldTextDidChangeNotification 通知时,它会调用 updateSaveButton 以更新保存按钮。

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

第 5 步:将所有内容捆绑在一起

在 ListsViewController 课堂上,我们仍然需要处理一些事情。让我们从使类符合 AddListViewControllerDelegate 协议开始。

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

这也意味着我们需要实现 AddListViewControllerDelegate 协议的方法。在该 controller(_:didAddList:) 方法中,我们将新记录添加到 CKRecord 对象数组中。updateView 然后我们对记录数组进行排序,重新加载表视图,并在视图控制器上调用 。

// MARK: -
// MARK: Add List View Controller Delegate Methods
func controller(controller: AddListViewController, didAddList list: CKRecord) {
    // Add List to Lists
    lists.append(list)
    
    // Sort Lists
    sortLists()
    
    // Update Table View
    tableView.reloadData()

    // Update View
    updateView()
}

该 sortLists 方法非常基本。我们调用 sortInPlace 记录数组,根据记录的名称对数组进行排序。

private func sortLists() {
        self.lists.sort {
            var result = false
            let name0 = $0.object(forKey: "name") as? String
            let name1 = $1.object(forKey: "name") as? String
            
            if let listName0 = name0, let listName1 = name1 {
                result = listName0.localizedCaseInsensitiveCompare(listName1) == .orderedAscending
            }
            
            return result
        }
    }

AddListViewControllerDelegate 协议 的第二种方法的实现 controller(_:didUpdateList:)看起来几乎相同。因为我们没有添加记录,所以我们只需要对记录数组进行排序并重新加载表格视图。无需调用 updateView 视图控制器,因为根据定义,记录数组不是空的。

func controller(controller: AddListViewController, didUpdateList list: CKRecord) {
    // Sort Lists
    sortLists()
    
    // Update Table View
    tableView.reloadData()
}

要编辑记录,用户需要点击表格视图行的附件按钮。这意味着我们需要实现协议的 tableView(_:accessoryButtonTappedForRowWithIndexPath:) 方法 UITableViewDelegate 。在我们实现这个方法之前,声明一个辅助属性, selection来存储用户的选择。

class ListsViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
    static let ListCell = "ListCell"
    
    @IBOutlet weak var messageLabel: UILabel!
    @IBOutlet weak var tableView: UITableView!
    @IBOutlet weak var activityIndicatorView: UIActivityIndicatorView!
    
    var lists = [CKRecord]()
    
    var selection: Int?
    
    ...
    
}

在 tableView(_:accessoryButtonTappedForRowWithIndexPath:)中,我们将用户的选择存储在中, selection 并告诉视图控制器执行导致添加列表视图控制器的 segue。

// MARK: -
    // MARK: Segue Life Cycle
    func tableView(_ tableView: UITableView, accessoryButtonTappedForRowWith indexPath: IndexPath) {
       
        tableView.deselectRow(at: indexPath as IndexPath, animated: true)
        
        // Save Selection
        selection = indexPath.row
        
        // Perform Segue
        performSegue(withIdentifier: "ListDetail", sender: self)
    }
    
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        
        // Fetch Destination View Controller
        let addListViewController = segue.destination as! AddListViewController
        
        // Configure View Controller
        addListViewController.delegate = self
        
        if let selection = selection {
            // Fetch List
            let list = lists[selection]
            
            // Configure View Controller
            addListViewController.list = list
        }
    }

我们快到了。当执行带有标识符的 segue 时 ListDetail ,我们需要配置 AddListViewController 推送到导航堆栈上的实例。我们在 prepareForSegue(_:sender:).

segue 为我们提供了对目标视图控制器的引用,即 AddListViewController 实例。我们设置 delegate 属性,如果购物清单被更新,我们将视图控制器的 list 属性设置为选定的记录。

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

构建并运行应用程序以查看结果。您现在应该能够添加新的购物清单并编辑现有购物清单的名称。

4. 删除购物清单

添加删除购物清单的功能并没有太多额外的工作。用户应该能够通过从右向左滑动表格视图行并点击显示的删除按钮来删除购物清单。为了使这成为可能,我们需要实现协议的另外两个方法 UITableViewDataSource :

  • tableView(_:canEditRowAtIndexPath:)

  • tableView(_:commitEditingStyle:forRowAtIndexPath:)

的实现 tableView(_:canEditRowAtIndexPath:) 很简单,如下所示。

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

在 tableView(_:commitEditingStyle:forRowAtIndexPath:)中,我们从记录数组中获取正确的记录并 deleteRecord(_:) 在视图控制器上调用,传入需要删除的记录。

func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {    guard editingStyle == .delete else { return }
    
    // Fetch Record
    let list = lists[indexPath.row]
    
    // Delete Record
    deleteRecord(list)
}

该 deleteRecord(_:) 方法现在应该看起来很熟悉。我们显示一个进度指示器并调用 deleteRecordWithID(_:completionHandler:) 默认容器的私有数据库。请注意,我们传递的是记录标识符,而不是记录本身。完成处理程序接受两个参数,一个可选的 CKRecordID 和一个可选的 NSError。

private func deleteRecord(_ list: CKRecord) {
        // Fetch Private Database
        let privateDatabase = CKContainer.default().privateCloudDatabase
        
        // Show Progress HUD
        SVProgressHUD.show()
        
        // Delete List
        privateDatabase.delete(withRecordID: list.recordID) { (recordID, error) -> Void in
            DispatchQueue.main.sync {
                SVProgressHUD.dismiss()
                
                // Process Response
                self.processResponseForDeleteRequest(list, recordID: recordID, error: error)
            }
        }
    }

在完成处理程序中,我们关闭进度指示器并 processResponseForDeleteRequest(_:recordID:error:) 在主线程上调用。在这个方法中,我们检查  CloudKit API 给我们 的recordID 和 的值,并 相应地更新。如果删除请求成功,那么我们更新用户界面和记录数组。errormessage

private func processResponseForDeleteRequest(_ record: CKRecord, recordID: CKRecordID?, error: Error?) {
        var message = ""
        
        if let error = error {
            print(error)
            message = "We are unable to delete the list."
            
        } else if recordID == nil {
            message = "We are unable to delete the list."
        }
        
        if message.isEmpty {
            // Calculate Row Index
            let index = self.lists.index(of: record)
            
            if let index = index {
                // Update Data Source
                self.lists.remove(at: index)
                
                if lists.count > 0 {
                    // Update Table View
                    self.tableView.deleteRows(at: [NSIndexPath(row: index, section: 0) as IndexPath], with: .right)
                    
                } else {
                    // Update Message Label
                    messageLabel.text = "No Records Found"
                    
                    // Update View
                    updateView()
                }
            }
            
        } else {
            // Initialize Alert Controller
            let alertController = UIAlertController(title: "Error", message: message, preferredStyle: .alert)
            
            // Present Alert Controller
            present(alertController, animated: true, completion: nil)
        }
    }

而已。是时候使用一些数据正确测试应用程序了。在设备上或 iOS 模拟器中运行应用程序并添加一些购物清单。您应该能够添加、编辑和删除购物清单。

结论

尽管这篇文章相当长,但请记住我们只是简单地与 CloudKit API 进行了交互。CloudKit 框架的便捷 API 是轻量级且易于使用的。

然而,本教程也说明了作为开发人员的工作并不仅限于与 CloudKit API 交互。处理错误、在请求进行时向用户显示、更新用户界面以及告诉用户发生了什么是很重要的。


文章目录
  • 先决条件
  • 1. 设置 CocoaPods
    • 第 1 步:创建 Podfile
    • 第 2 步:安装依赖项
  • 2. 列出购物清单
    • 第 1 步:家政服务
    • 第 2 步:添加表格视图
    • 第 3 步:空状态
    • 第 4 步:连接插座
    • 第 5 步:准备表格视图
    • 第 6 步:准备用户界面
    • 第 7 步:创建记录类型
    • 第 8 步:执行查询
  • 3. 添加购物清单
    • 第 1 步:创建 AddListViewController 类
    • 第 2 步:创建用户界面
    • 第 3 步: AddListViewControllerDelegate 协议
    • 第 4 步:实施行动
    • 第 5 步:将所有内容捆绑在一起
  • 4. 删除购物清单
  • 结论