В предыдущем посте этой серии мы добавили возможность добавлять, обновлять и удалять списки покупок. Список покупок без каких-либо предметов не очень полезен. В этом руководстве мы добавим возможность добавлять, обновлять и удалять элементы из списка покупок. Это означает, что мы будем работать со ссылками и классом CKReference
.
Мы также подробнее рассмотрим модель данных приложения списка покупок. Насколько легко вносить изменения в модель данных и как приложение реагирует на изменения, которые мы вносим в инструментальную панель CloudKit?
Предпосылки
Помните, что я буду использовать Xcode 9 и Swift 3 . Если вы используете более старую версию Xcode, имейте в виду, что вы используете другую версию языка программирования Swift.
В этом уроке мы продолжим с того места, на котором остановились в предыдущем посте этой серии. Вы можете скачать или клонировать проект с GitHub .
1. Список покупок
В настоящее время пользователь может изменить имя списка покупок, коснувшись индикатора раскрытия подробностей, но пользователь также должен иметь возможность видеть содержимое списка покупок, коснувшись одного в контроллере представления списков. Чтобы сделать это, нам сначала нужен новый подкласс UIViewController
.
Шаг 1: Создание ListViewController
Класс ListViewController
будет отображать содержимое списка покупок в виде таблицы. Интерфейс класса ListViewController
выглядит аналогично ListsViewController
класса ListsViewController
. Мы импортируем платформы CloudKit и SVProgressHUD и согласовываем класс с протоколами UITableViewDataSource
и UITableViewDelegate
. Поскольку мы будем использовать табличное представление, мы объявляем константу ItemCell
, которая будет служить идентификатором повторного использования ячейки.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
|
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?
…
}
|
Мы объявляем три выхода: messageLabel
типа UILabel!
, tableView
типа UITableView!
и UIActivityIndicatorView!
типа UIActivityIndicatorView!
, Контроллер представления списка хранит ссылку на список покупок, который он отображает, в свойстве list
, которое имеет тип CKRecord!
, Предметы в списке покупок хранятся в свойстве items
, которое имеет тип [CKRecord]
. Наконец, мы используем вспомогательную переменную selection
, чтобы отслеживать, какой элемент в списке покупок выбрал пользователь. Это станет ясно позже в этом уроке.
Шаг 2: Создание пользовательского интерфейса
Откройте Main.storyboard , добавьте контроллер представления и установите его класс в ListViewController
в Identity Inspector . Выберите ячейку прототипа контроллера представления списков, нажмите клавишу Control и перетащите из ячейки прототипа в контроллер представления списка. Выберите « Показать» в появившемся меню и установите для идентификатора значение « Список» в Инспекторе атрибутов .
Добавьте представление таблицы, метку и представление индикатора активности в представление контроллера представления. Не забудьте подключить выходы контроллера представления, а также выходы таблицы.
Выберите представление таблицы и установите ячейки прототипа равными 1 в инспекторе атрибутов . Выберите ячейку прототипа и задайте для Style значение Right Detail , Identifier для ItemCell и Accessory to Disclosure Indicator . Вот как должен выглядеть контроллер вида, когда вы закончите.
Шаг 3: Настройка View Controller
Прежде чем мы вернемся к платформе CloudKit, нам нужно подготовить контроллер представления для данных, которые он собирается получать. Начните с обновления реализации viewDidLoad
. Мы устанавливаем заголовок контроллера представления на имя списка покупок и вызываем два вспомогательных метода, setupView
и fetchItems
.
01
02
03
04
05
06
07
08
09
10
11
|
// MARK: —
// MARK: View Life Cycle
override func viewDidLoad() {
super.viewDidLoad()
// Set Title
title = list.objectForKey(«name») as?
setupView()
fetchItems()
}
|
Метод setupView
идентичен методу, который мы реализовали в классе ListsViewController
.
1
2
3
4
5
6
7
|
// MARK: —
// MARK: View Methods
private func setupView() {
tableView.hidden = true
messageLabel.hidden = true
activityIndicatorView.startAnimating()
}
|
Пока мы в этом, давайте также реализуем другой знакомый вспомогательный метод, updateView
. В updateView
мы обновляем пользовательский интерфейс контроллера представления на основе элементов, хранящихся в свойстве items
.
1
2
3
4
5
6
7
|
private func updateView() {
let hasRecords = items.count > 0
tableView.hidden = !hasRecords
messageLabel.hidden = hasRecords
activityIndicatorView.stopAnimating()
}
|
Я собираюсь оставить fetchItems
пустыми. Мы вернемся к этому методу, как только закончим настройку контроллера представления списка.
1
2
3
4
5
|
// MARK: —
// MARK: Helper Methods
private func fetchItems() {
}
|
Шаг 4. Методы источника данных табличного представления
Мы почти готовы принять заявку на очередной тестовый запуск. Прежде чем мы это сделаем, нам нужно реализовать протокол UITableViewDataSource
. Если вы читали предыдущие части этой серии статей, то реализация будет выглядеть знакомо.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
|
// 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?
// 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
.
1
2
3
4
5
|
// MARK: —
// MARK: Table View Delegate Methods
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
tableView.deselectRowAtIndexPath(indexPath, animated: true)
}
|
Нам также нужно обновить prepareForSegue(segue:sender:)
чтобы обработать переход, который мы создали несколько минут назад. Это означает, что нам нужно добавить новый case
в оператор switch
.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
|
// 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!
// Fetch Selection
let list = lists[tableView.indexPathForSelectedRow!.row]
// Configure View Controller
listViewController.list = list
case SegueListDetail:
…
default:
break
}
}
|
Чтобы удовлетворить компилятор, нам также нужно объявить константу SegueList
в начале ListsViewController.swift .
01
02
03
04
05
06
07
08
09
10
11
12
|
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 Dashboard. Перейдите к панели инструментов CloudKit, создайте новый тип записи и назовите его Items . У каждого элемента должно быть имя, поэтому создайте новое поле, задайте для имени поля имя и установите для типа поля значение String .
Каждый товар должен также знать, к какому списку покупок он принадлежит. Это означает, что каждому предмету нужна ссылка на свой список покупок. Создайте новое поле, задайте имя поля для списка и установите тип поля « Ссылка» . Тип поля Reference был разработан для этой конкретной цели, управления отношениями.
Вернитесь к Xcode, откройте ListsViewController.swift и объявите новую константу вверху для типа записи Items .
01
02
03
04
05
06
07
08
09
10
11
12
13
|
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
класса ListsViewController
. Однако есть важное отличие.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
|
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
. Нас не интересует каждый элемент в личной базе данных пользователя. Нас интересуют только те товары, которые связаны с определенным списком покупок. Это отражено в предикате экземпляра CKQuery
.
Мы создаем предикат, передавая экземпляр CKReference
, который мы создаем, вызывая init(recordID:action:)
. Этот метод принимает два аргумента: экземпляр CKRecordID
который ссылается на запись списка покупок, и экземпляр CKReferenceAction
который определяет, что происходит при удалении списка покупок.
Справочные действия очень похожи на правила удаления в Базовых данных. Если указанный объект (список покупок в этом примере) удален, то среда CloudKit проверяет действие ссылки, чтобы определить, что должно произойти с записями, которые содержат ссылку на удаленную запись. CKReferenceAction
имеет два значения элемента:
-
None
: если ссылка удалена, с записями, ссылающимися на удаленную запись, ничего не происходит. -
DeleteSelf
: если ссылка удалена, каждая запись,DeleteSelf
на удаленную запись, также удаляется.
Поскольку ни один элемент не должен существовать без списка покупок, мы установили действие ссылки на DeleteSelf
.
Метод processResponseForQuery(records:error:)
не содержит ничего нового. Мы обрабатываем ответ на запрос и соответственно обновляем пользовательский интерфейс.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
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
Пришло время реализовать возможность добавлять товары в список покупок. Начните с создания нового подкласса AddItemViewController
, AddItemViewController
. Интерфейс контроллера представления аналогичен AddListViewController
класса AddListViewController
.
Вверху мы импортируем фреймворки CloudKit и SVProgressHUD . Мы объявляем протокол AddItemViewControllerDelegate
, который будет служить той же цели, что и протокол AddListViewControllerDelegate
. Протокол определяет два метода, один для добавления элементов и один для обновления элементов.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
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
свойств для ссылки на список покупок, в который будет добавлен элемент, и элемент свойства для элемента, который мы создаем или обновляем.
Прежде чем создавать пользовательский интерфейс, давайте реализуем два действия, которые нам понадобятся в раскадровке: cancel(_:)
и save(_:)
. Мы обновим реализацию действия save(_:)
позже в этом руководстве.
1
2
3
4
5
6
7
8
9
|
// MARK: —
// MARK: Actions
@IBAction func cancel(sender: AnyObject) {
navigationController?.popViewControllerAnimated(true)
}
@IBAction func save(sender: AnyObject) {
navigationController?.popViewControllerAnimated(true)
}
|
Шаг 2: Создание пользовательского интерфейса
Откройте Main.storyboard , добавьте элемент панели кнопок на панель навигации контроллера представления списка и установите System Item на Add в Инспекторе Атрибутов . Перетащите контроллер представления из библиотеки объектов и установите его класс AddItemViewController . Создайте переход из только что созданного элемента панели кнопок в контроллер представления добавления элемента. Выберите « Показать» в появившемся меню и установите идентификатор перехода в ItemDetail .
Добавьте два элемента панели кнопок на панель навигации контроллера добавления элемента, кнопку отмены слева и кнопку сохранения справа. Подключите каждый элемент кнопки панели к соответствующему действию. Добавьте текстовое поле в представление контроллера представления и не забудьте подключить выходы контроллера представления. Вот как должен выглядеть контроллер представления добавленного элемента, когда вы закончите.
Шаг 3: Настройка View Controller
Реализация контроллера представления добавления элемента не содержит ничего, что мы еще не рассмотрели. Однако есть одно исключение, которое мы обсудим чуть позже. Давайте начнем с настройки контроллера представления в viewDidLoad
.
Мы вызываем setupView
, вспомогательный метод, и обновляем значение newItem
. Если свойство item
равно nil
, newItem
равно true
. Это помогает нам определить, создаем ли мы или обновляем элемент списка покупок.
Мы также добавляем контроллер представления в качестве наблюдателя для уведомлений типа UITextFieldTextDidChangeNotification
. Это означает, что контроллер представления уведомляется, когда изменяется содержимое nameTextField
.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
|
// 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
1
2
3
|
override func viewDidAppear(animated: Bool) {
nameTextField.becomeFirstResponder()
}
|
Метод setupView
вызывает два вспомогательных метода, updateNameTextField
и updateSaveButton
. Реализация этих вспомогательных методов проста. В updateNameTextField
мы заполняем текстовое поле. В updateSaveButton
мы updateSaveButton
или отключаем кнопку сохранения в зависимости от содержимого текстового поля.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
// MARK: —
// MARK: View Methods
private func setupView() {
updateNameTextField()
updateSaveButton()
}
// MARK: —
private func updateNameTextField() {
if let name = item?.objectForKey(«name») as?
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
чтобы включить или отключить кнопку сохранения.
1
2
3
4
5
|
// MARK: —
// MARK: Notification Handling
func textFieldTextDidChange(notification: NSNotification) {
updateSaveButton()
}
|
Шаг 4: Сохранение предметов
Метод save(_:)
является наиболее интересным методом класса AddItemViewController
, потому что он показывает нам, как работать со ссылками CloudKit. Посмотрите на реализацию метода save(_:)
ниже.
Большая часть его реализации должна выглядеть знакомой, поскольку мы рассмотрели сохранение записей в классе AddListViewController
. Что нас больше всего интересует, так это то, как товар хранит ссылку на свой список покупок. Сначала мы создаем экземпляр CKReference
, вызывая указанный инициализатор init(recordID:action:)
. Мы рассмотрели детали создания экземпляра CKReference
несколько минут назад, когда создавали запрос для выборки элементов списка покупок.
Рассказать об этом предмете легко. Мы вызываем setObjec(_:forKey:)
для свойства item
, передавая экземпляр CKReference
в качестве значения и "list"
в качестве ключа. Ключ соответствует имени поля, которое мы присвоили в CloudKit Dashboard. Сохранение элемента в iCloud идентично тому, что мы рассмотрели ранее. Вот так легко работать с ссылками CloudKit.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
|
@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:)
содержит ничего нового. Мы проверяем, появились ли какие-либо ошибки, и, если ошибок не было, мы уведомляем делегата.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
|
// 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
. Начните с соответствия класса AddItemViewControllerDelegate
протоколу AddItemViewControllerDelegate
. Это также хороший момент для объявления константы для передачи с идентификатором ItemDetail .
1
2
3
4
5
6
7
8
9
|
import UIKit
import CloudKit
import SVProgressHUD
let SegueItemDetail = «ItemDetail»
class ListViewController: UIViewController, UITableViewDataSource, UITableViewDelegate, AddItemViewControllerDelegate {
…
}
|
Реализация протокола AddItemViewControllerDelegate
тривиальна. В controller(_:didAddItem:)
мы добавляем новый элемент в items
, сортируем items
, перезагружаем табличное представление и вызываем updateView
.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
|
// 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
и перезагружаем табличное представление.
1
2
3
4
5
6
7
|
func controller(controller: AddItemViewController, didUpdateItem item: CKRecord) {
// Sort Items
sortItems()
// Update Table View
tableView.reloadData()
}
|
В sortItems
мы сортируем массив экземпляров CKRecord
по имени, используя функцию sortInPlace
, метод протокола MutableCollectionType
.
01
02
03
04
05
06
07
08
09
10
11
12
13
|
private func sortItems() {
items.sortInPlace {
var result = false
let name0 = $0.objectForKey(«name») as?
let name1 = $1.objectForKey(«name») as?
if let itemName0 = name0, itemName1 = name1 {
result = itemName0.localizedCaseInsensitiveCompare(itemName1) == .OrderedAscending
}
return result
}
}
|
Нам нужно реализовать еще две функции: обновление и удаление элементов списка покупок.
Шаг 6: Удаление элементов
Чтобы удалить элементы, нам нужно реализовать tableView(_:commitEditingStyle:forRowAtIndexPath:)
протокола UITableViewDataSource
. Мы выбираем элемент списка покупок, который необходимо удалить, и передаем его в метод deleteRecord(_:)
.
1
2
3
4
5
6
7
8
9
|
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:)
в частной базе данных и обрабатываем ответ в обработчике завершения.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
|
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
и пользовательский интерфейс. Если что-то пошло не так, мы уведомляем пользователя, показывая предупреждение.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
|
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 . Обратите внимание, что ничего не происходит в tableView(_:didSelectRowAtIndexPath:)
. Все, что мы делаем, это отменим выбор строки, которую нажал пользователь.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
|
// 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
.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
// 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!
// 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 Dashboard.
4. Обновление модели данных
Если вы когда-либо работали с Core Data, то знаете, что обновление модели данных следует проводить с осторожностью. Вам необходимо убедиться, что вы ничего не сломали и не повредили постоянные хранилища приложения. CloudKit немного более гибок.
Тип записи Предметов в настоящее время имеет два поля: имя и список . Я хочу показать вам, что нужно для обновления модели данных, добавив новое поле. Откройте панель управления CloudKit и добавьте новое поле в запись « Элементы» . Установите имя поля в число и установите тип поля в Int (64) . Не забудьте сохранить свои изменения.
Давайте теперь добавим возможность изменять номер элемента. Откройте AddItemViewController.swift и объявите два выхода, метку и степпер.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
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
.
1
2
3
4
5
6
|
@IBAction func numberDidChange(sender: UIStepper) {
let number = Int(sender.value)
// Update Number Label
numberLabel.text = «\(number)»
}
|
Откройте Main.storyboard и добавьте метку и степпер в контроллер представления добавления элемента. Подключите выходы контроллера представления к соответствующим элементам пользовательского интерфейса и подключите действие numberDidChange(_:)
к степперу для события Value Changed .
Действие save(_:)
класса AddItemViewController
также немного изменяется. Давайте посмотрим, как это выглядит.
Нам нужно только добавить две строки кода. Вверху мы храним значение степпера в постоянном number
. Когда мы настраиваем item
, мы устанавливаем number
в качестве значения для цифровой клавиши, и это все, что нужно сделать.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
|
@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, и обновляет степпер, если это так.
1
2
3
4
5
|
private func updateNumberStepper() {
if let number = item?.objectForKey(«number») as?
numberStepper.value = number
}
}
|
Мы вызываем updateNumberStepper
в методе AddItemViewController
класса AddItemViewController
.
1
2
3
4
5
|
private func setupView() {
updateNameTextField()
updateNumberStepper()
updateSaveButton()
}
|
Чтобы визуализировать номер каждого элемента, нам нужно сделать одно изменение в ListViewController
. В tableView(_:cellForRowAtIndexPath:)
мы устанавливаем содержимое правой метки ячейки равным значению поля номера элемента.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
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?
// Configure Cell
cell.textLabel?.text = itemName
} else {
cell.textLabel?.text = «-«
}
if let itemNumber = item.objectForKey(«number») as?
// Configure Cell
cell.detailTextLabel?.text = «\(itemNumber)»
} else {
cell.detailTextLabel?.text = «1»
}
return cell
}
|
Это все, что нам нужно сделать для реализации изменений, которые мы внесли в модель данных. Нет необходимости выполнять миграцию или что-то подобное. CloudKit заботится о мельчайших деталях.
Вывод
Теперь у вас должна быть надлежащая основа в структуре CloudKit. Я надеюсь, что вы согласны с тем, что Apple отлично поработала с этой платформой и облачной панелью управления CloudKit. Мы многое еще не рассмотрели в этой серии, но к настоящему времени вы уже достаточно научились, чтобы начать использовать CloudKit в своих собственных проектах.
Если у вас есть какие-либо вопросы или комментарии, не стесняйтесь оставлять их в комментариях ниже или обращаться ко мне в Twitter .