В предыдущем сокращении мы заложили основу для приложения списка покупок. В первой части этого урока мы дополнительно доработаем приложение, предоставив пользователю возможность редактировать и удалять элементы из списка. Позже мы добавим возможность выбирать товары из списка для создания списка покупок.
1. Удаление элементов
Удаление элементов из списка является важным дополнением с точки зрения пользовательского опыта и общего удобства использования. Добавление этой способности включает в себя:
- удаление элемента из свойства
items
контроллера представления - обновление табличного представления
- сохранение изменений на диск
Посмотрим, как это работает на практике. Сначала нам нужно добавить кнопку редактирования на панель навигации. В viewDidLoad()
контроллера представления создайте экземпляр UIBarButtonItem
и назначьте его rightBarButtonItem
свойства navigationItem
контроллера представления.
Как и в предыдущем уроке , мы создаем элемент кнопки панели, вызывая init(barButtonSystemItem:target:action:)
, передавая .Edit
, значение элемента UIBarButtonSystemItem
, self
в качестве цели и "editItems:"
в качестве селектора ,
01
02
03
04
05
06
07
08
09
10
11
12
|
override func viewDidLoad() {
super.viewDidLoad()
// Register Class
tableView.registerClass(UITableViewCell.classForCoder(), forCellReuseIdentifier: CellIdentifier)
// Create Add Button
navigationItem.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: .Add, target: self, action: «addItem:»)
// Create Edit Button
navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .Edit, target: self, action: «editItems:»)
}
|
Реализация editItems(_:)
— это только одна строка кода, как вы можете видеть ниже. Всякий раз, когда пользователь нажимает кнопку редактирования, представление таблицы переключается в режим редактирования или выходит из него. Мы делаем это с помощью небольшой хитрости. Мы спрашиваем табличное представление, находится ли оно в режиме редактирования, которое возвращает значение Bool
, и инвертирует возвращаемое значение ( true
становится false
и наоборот). Метод, который мы вызываем в табличном представлении, это setEditing(_:animated:)
, специализированный установщик, который принимает параметр анимации.
1
2
3
|
func editItems(sender: UIBarButtonItem) {
tableView.setEditing(!tableView.editing, animated: true)
}
|
Если вы запустите приложение списка покупок в симуляторе и коснитесь кнопки редактирования, вы увидите, что представление таблицы переключается в режим редактирования и выходит из него.
Два метода протокола UITableViewDataSource
важны для включения редактирования в табличном представлении:
-
tableView(_:canEditRowAtIndexPath:)
-
tableView(_:commitEditingStyle:forRowAtIndexPath:)
Если пользователь нажимает кнопку редактирования, табличное представление запрашивает источник данных, какие строки можно редактировать, отправляя источнику данных сообщение tableView(_:canEditRowAtIndexPath:)
. Если для определенного пути индекса возвращается значение true
, табличное представление указывает соответствующей ячейке табличного представления, что ему необходимо переключиться в режим редактирования или выйти из него, в зависимости от режима редактирования табличного представления. Это переводит в ячейку табличного представления, показывая или скрывая ее элементы редактирования. tableView(_:canEditRowAtIndexPath:)
метод tableView(_:canEditRowAtIndexPath:)
как показано ниже, чтобы увидеть, как это работает на практике.
1
2
3
4
5
6
7
|
override func tableView(tableView: UITableView, canEditRowAtIndexPath indexPath: NSIndexPath) -> Bool {
if indexPath.row == 1 {
return false
}
return true
}
|
Приведенная выше реализация tableView(_:canEditRowAtIndexPath:)
позволяет пользователю редактировать каждую строку в табличном представлении, за исключением второй строки. Запустите приложение в симуляторе и попробуйте.
Для приложения списка покупок пользователь должен иметь возможность редактировать каждую строку в табличном представлении. Это означает, что tableView(_:canEditRowAtIndexPath:)
всегда должен возвращать true
.
1
2
3
|
override func tableView(tableView: UITableView, canEditRowAtIndexPath indexPath: NSIndexPath) -> Bool {
return true
}
|
Возможно, вы заметили, что ничего не происходит, когда вы пытаетесь удалить строку в табличном представлении. Загадка еще не закончена. Всякий раз, когда пользователь нажимает кнопку удаления строки, табличное представление отправляет своему источнику данных сообщение tableView(_:commitEditingStyle:forRowAtIndexPath:)
. Второй аргумент соответствующего метода указывает, какой тип действия пользователь выполнил, вставив или удалив строку. Для приложения списка покупок мы только реализуем поддержку удаления строк из табличного представления.
Удаление строк из табличного представления включает в себя:
- удаление соответствующего элемента из свойства
items
контроллера представления - Обновление табличного представления путем удаления соответствующей строки
Давайте tableView(_:commitEditingStyle:forRowAtIndexPath:)
реализацию tableView(_:commitEditingStyle:forRowAtIndexPath:)
. Метод начинается с проверки того, равен ли стиль редактирования .Delete
(значение элемента UITableViewCellEditingStyle
), потому что мы хотим только разрешить пользователю удалять строки из табличного представления.
Если стиль редактирования равен .Delete
, соответствующий элемент удаляется из свойства items
, соответствующая строка удаляется из табличного представления, а обновленный список элементов сохраняется на диск.
01
02
03
04
05
06
07
08
09
10
11
12
|
override func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
if editingStyle == .Delete {
// Delete Item from Items
items.removeAtIndex(indexPath.row)
// Update Table View
tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Right)
// Save Changes
saveItems()
}
}
|
Запустите приложение в симуляторе и удалите несколько предметов. Не забудьте выйти и перезапустить приложение, чтобы убедиться, что элементы навсегда удалены из списка.
2. Редактирование элементов
Мы могли бы повторно использовать класс AddItemViewController
для редактирования элементов. Однако, поскольку использование одного контроллера представления для добавления и редактирования элементов часто может усложнить реализацию, я обычно заканчиваю тем, что создаю отдельный класс для редактирования элементов. Изначально это может привести к тому, что мы будем повторяться, но это даст нам больше гибкости в будущем.
Шаг 1. Создание редактирующего контроллера вида
Создайте новый подкласс UIViewController
и назовите его EditItemViewController
. Интерфейс классов AddItemViewController
и EditItemViewController
очень похожи.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
|
import UIKit
protocol EditItemViewControllerDelegate {
func controller(controller: EditItemViewController, didUpdateItem item: Item)
}
class EditItemViewController: UIViewController {
@IBOutlet var nameTextField: UITextField!
@IBOutlet var priceTextField: UITextField!
var item: Item!
var delegate: EditItemViewControllerDelegate?
…
}
|
Различия заключаются в добавлении свойства item
для хранения ссылки на редактируемый элемент и определении метода протокола EditItemViewControllerDelegate
. Обратите внимание, что item
имеет тип Item!
Принудительная распаковка необязательна. Поскольку контроллер представления редактирования элемента бесполезен без элемента для редактирования, мы ожидаем, что свойство item
всегда будет иметь значение.
Откройте Main.storyboard , перетащите экземпляр UIViewController
из библиотеки объектов , установите для его класса значение EditItemViewController
и создайте последовательность показа вручную из контроллера представления списка в контроллер представления редактирования элемента. Установите идентификатор перехода в EditItemViewController
.
Перетащите текстовые поля из библиотеки объектов в представление контроллера представления и расположите их, как показано на рисунке ниже. Выберите верхнее текстовое поле, откройте инспектор атрибутов и введите « Имя» в поле « Заполнитель» . Выделите нижнее текстовое поле и в инспекторе атрибутов установите для его текста-заполнителя значение « Цена» и установите для « Клавиатура» значение « Цифровая панель» . Выберите объект контроллера представления, откройте инспектор соединений и соедините nameTextField
и priceTextField
с соответствующим текстовым полем в пользовательском интерфейсе.
В viewDidLoad()
контроллера представления создайте кнопку сохранения, как мы делали в классе AddItemViewController
.
1
2
3
4
5
6
7
8
|
// MARK: —
// MARK: View Life Cycle
override func viewDidLoad() {
super.viewDidLoad()
// Create Save Button
navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .Save, target: self, action: «save:»)
}
|
Реализация действия save(_:)
очень похожа на ту, что мы реализовали в классе AddItemViewController
. Однако есть несколько тонких различий, на которые я хочу обратить внимание.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
|
// MARK: —
// MARK: Actions
func save(sender: UIBarButtonItem) {
if let name = nameTextField.text, let priceAsString = priceTextField.text, let price = Float(priceAsString) {
// Update Item
item.name = name
item.price = price
// Notify Delegate
delegate?.controller(self, didUpdateItem: item)
// Pop View Controller
navigationController?.popViewControllerAnimated(true)
}
}
|
Вместо того, чтобы передавать значения имени и цены делегату, мы напрямую обновляем элемент и передаем обновленный элемент делегату контроллера представления. Поскольку контроллер представления является дочерним контроллером представления контроллера навигации, мы увольняем контроллер представления, выталкивая его из стека навигации.
Шаг 2: Отображение контроллера представления элемента редактирования
Через несколько минут мы реализуем функциональность, которая позволяет пользователю выбирать элементы в контроллере представления списка, чтобы добавить их в список покупок. Пользователь сможет сделать это, нажав строку в представлении списка. Вопрос в том, как пользователь сможет редактировать товар, если постукивание по строке зарезервировано для добавления товара в список покупок?
Инфраструктура UIKit предоставляет кнопку подробного раскрытия информации именно для этого варианта использования. Кнопка раскрытия подробностей расположена справа от ячейки табличного представления. Чтобы добавить кнопку подробного раскрытия в ячейку табличного представления, нам нужно повторно просмотреть tableView(_:cellForRowAtIndexPath:)
в контроллере представления списка и изменить его, как показано ниже.
01
02
03
04
05
06
07
08
09
10
11
12
13
|
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
// Dequeue Reusable Cell
let cell = tableView.dequeueReusableCellWithIdentifier(CellIdentifier, forIndexPath: indexPath)
// Fetch Item
let item = items[indexPath.row]
// Configure Table View Cell
cell.textLabel?.text = item.name
cell.accessoryType = .DetailDisclosureButton
return cell
}
|
Каждая ячейка табличного представления имеет свойство accessoryType
. В tableView(_:cellForRowAtIndexPath:)
мы устанавливаем для него значение .DetailDisclosureButton
, значение элемента UITableViewCellAccessoryType
. Возможно, вы заметили, что инженеры Apple не любят короткие имена.
Как табличное представление уведомляет своего делегата при нажатии кнопки раскрытия информации? Неудивительно, что протокол UITableViewDelegate
определяет метод tableView(_:accessoryButtonTappedForRowWithIndexPath:)
для этой цели. Посмотрите на его реализацию.
01
02
03
04
05
06
07
08
09
10
11
12
|
// MARK: —
// MARK: Table View Delegate Methods
override func tableView(tableView: UITableView, accessoryButtonTappedForRowWithIndexPath indexPath: NSIndexPath) {
// Fetch Item
let item = items[indexPath.row]
// Update Selection
selection = item
// Perform Segue
performSegueWithIdentifier(«EditItemViewController», sender: self)
}
|
Мы выбираем правильный элемент из свойства items
и сохраняем его в selection
, свойстве контроллера представления списка, которое мы объявим через минуту. Затем мы выполняем переход с идентификатором EditItemViewController
.
Прежде чем мы обновим реализацию prepareForSegue(_:sender:)
, нам нужно объявить свойство для хранения выбранного элемента. Нам также необходимо согласовать класс ListViewController
с протоколом EditItemViewControllerDelegate
.
01
02
03
04
05
06
07
08
09
10
11
12
|
import UIKit
class ListViewController: UITableViewController, AddItemViewControllerDelegate, EditItemViewControllerDelegate {
let CellIdentifier = «Cell Identifier»
var items = [Item]()
var selection: Item?
…
}
|
Обновленная реализация prepareForSegue(_:sender:)
довольно проста, как вы можете видеть ниже. Мы получаем ссылку на контроллер представления элемента edit и устанавливаем его delegate
и свойства item
. Поскольку свойство selection
имеет тип Item?
мы привязываем его к константе item
чтобы безопасно развернуть его.
01
02
03
04
05
06
07
08
09
10
11
|
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == «AddItemViewController» {
…
} else if segue.identifier == «EditItemViewController» {
if let editItemViewController = segue.destinationViewController as?
editItemViewController.delegate = self
editItemViewController.item = item
}
}
}
|
Реализация EditItemViewController
почти завершена. В его viewDidLoad()
мы заполняем текстовые поля данными свойства item
. Поскольку свойство text текстового поля имеет тип String?
Мы используем строковую интерполяцию для создания строки из значения Float
свойства price
элемента. Строковая интерполяция в Swift довольно мощная.
01
02
03
04
05
06
07
08
09
10
|
override func viewDidLoad() {
super.viewDidLoad()
// Create Save Button
navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .Save, target: self, action: «save:»)
// Populate Text Fields
nameTextField.text = item.name
priceTextField.text = «\(item.price)»
}
|
Шаг 3: Принятие протокола делегата
Принятие протокола EditItemViewControllerDelegate
означает реализацию метода controller(_:didUpdateItem:)
как показано ниже. Вас может удивить, что мы не обновляем источник данных табличного представления. Все, что мы делаем в методе делегата, это перезагрузим одну строку табличного представления.
01
02
03
04
05
06
07
08
09
10
11
12
|
// MARK: —
// MARK: Edit Item View Controller Delegate Methods
func controller(controller: EditItemViewController, didUpdateItem item: Item) {
// Fetch Index for Item
if let index = items.indexOf(item) {
// Update Table View
tableView.reloadRowsAtIndexPaths([NSIndexPath(forRow: index, inSection: 0)], withRowAnimation: .Fade)
}
// Save Items
saveItems()
}
|
Причина, по которой нам не нужно обновлять источник данных табличного представления, массив items
, заключается в том, что обновленный элемент был передан по ссылке в контроллер представления редактирования элемента. Другими словами, объект, который обновил контроллер представления элемента редактирования, является тем же объектом, который содержится в массиве items
. Это одно из приятных преимуществ работы с экземплярами классов. Они передаются по ссылке.
Не забудьте сохранить список элементов, чтобы убедиться, что изменения записаны на диск. Запустите приложение, чтобы проверить функциональность редактирования.
3. Создание контроллера просмотра списка покупок
Прежде чем мы исследуем источник данных контроллера представления списка покупок, давайте создадим некоторые леса для работы с ними. Создайте новый подкласс UITableViewController
и назовите его ShoppingListViewController
.
Откройте ShoppingListViewController.swift и добавьте два свойства типа [Item]
:
-
items
, которые будут содержать полный список элементов -
shoppingList
, который будет содержать только элементы списка покупок
01
02
03
04
05
06
07
08
09
10
|
import UIKit
class ShoppingListViewController: UITableViewController {
var items = [Item]()
var shoppingList = [Item]()
…
}
|
Идея состоит в том, чтобы загружать список элементов каждый раз, когда в него inShoppingList
изменения, анализировать список элементов и извлекать только те элементы, для которых свойству inShoppingList
значение true
. Эти элементы затем добавляются в массив shoppingList
.
Другой вариант — сохранить список покупок в отдельном файле. Недостатком этого подхода является то, что мы должны поддерживать синхронизацию элементов в контроллере представления списка и элементов в списке покупок. Это напрашивается на неприятности, если вы спросите меня.
Шаг 1: Добавление наблюдателей за недвижимостью
Наблюдатели за недвижимостью — отличный способ реагировать на изменения стоимости имущества. Давайте посмотрим, как мы можем использовать наблюдатели свойства для обновления свойства shoppingList
при каждом изменении значения items
.
1
2
3
4
5
|
var items = [Item]() {
didSet {
buildShoppingList()
}
}
|
Синтаксис достаточно прост для понимания. Наблюдатели за недвижимостью завернуты в закрытие. Существует два типа наблюдателей за недвижимостью:
-
willSet
, который вызывается до установки нового значения свойства -
didSet
, который вызывается после установки нового значения свойства
В приведенном выше примере мы вызываем buildShoppingList()
всякий раз, когда новым items
присваивается новое значение. Вот как выглядит реализация buildShoppingList()
.
1
2
3
4
5
6
7
|
// MARK: —
// MARK: Helper Methods
func buildShoppingList() {
shoppingList = items.filter({ (item) -> Bool in
return item.inShoppingList
})
}
|
Мы фильтруем элементы массива items
, включая только те элементы, для которых inShoppingList
имеет значение true
. Результат присваивается shoppingList
.
Мы также создаем didSet
свойства didSet
для свойства shoppingList
. В этом наблюдателе свойств мы обновляем табличное представление, чтобы отразить содержимое shoppingList
, источника данных табличного представления.
1
2
3
4
5
|
var shoppingList = [Item]() {
didSet {
tableView.reloadData()
}
}
|
Шаг 2: Отображение списка покупок
Реализация методов протокола UITableViewDataSource
должна быть детской игрой на данный момент. Посмотрите на реализацию ниже.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
|
// MARK: —
// MARK: Table View Data Source Methods
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return shoppingList.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
// Dequeue Reusable Cell
let cell = tableView.dequeueReusableCellWithIdentifier(CellIdentifier, forIndexPath: indexPath)
// Fetch Item
let item = shoppingList[indexPath.row]
// Configure Table View Cell
cell.textLabel?.text = item.name
return cell
}
|
Не забудьте объявить идентификатор повторного использования ячейки и зарегистрировать класс UITableViewCell
как мы делали в классе ListViewController
.
1
2
3
4
5
6
7
8
9
|
import UIKit
class ShoppingListViewController: UITableViewController {
let CellIdentifier = «Cell Identifier»
…
}
|
1
2
3
4
5
6
7
8
|
// MARK: —
// MARK: View Life Cycle
override func viewDidLoad() {
super.viewDidLoad()
// Register Class
tableView.registerClass(UITableViewCell.classForCoder(), forCellReuseIdentifier: CellIdentifier)
}
|
Шаг 3: Загрузка предметов
Как я объяснил ранее, ключевое преимущество использования списка товаров для хранения и построения списка покупок состоит в том, что приложение хранит каждый товар только в одном месте. Это делает обновление элементов в списке и в списке покупок менее головной болью. loadItems()
и pathForItems()
идентичны тем, которые мы реализовали в классе ListViewController
.
1
2
3
4
5
6
7
|
private func loadItems() {
if let filePath = pathForItems() where NSFileManager.defaultManager().fileExistsAtPath(filePath) {
if let archivedItems = NSKeyedUnarchiver.unarchiveObjectWithFile(filePath) as?
items = archivedItems
}
}
}
|
1
2
3
4
5
6
7
8
9
|
private func pathForItems() -> String?
let paths = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, NSSearchPathDomainMask.UserDomainMask, true)
if let documents = paths.first, let documentsURL = NSURL(string: documents) {
return documentsURL.URLByAppendingPathComponent(«items»).path
}
return nil
}
|
Когда вы обнаружите, что дублируете код, вы должны услышать сигнал тревоги. Дублирование кода не вызывает проблем, пока вы реализуете новую функцию. Однако после этого вам следует рассмотреть возможность рефакторинга вашего кода, чтобы минимизировать количество дублирования в кодовой базе приложения. Это очень важная концепция в разработке программного обеспечения, и ее часто называют СУХОЙ , не повторяйте себя . Крис Питерс написал отличную статью на Envato Tuts + о СУХОМ программировании.
Прежде чем мы используем класс ShoppingListViewController
, нам нужно обновить метод viewDidLoad()
класса. Помимо установки заголовка контроллера представления, мы также загружаем список предметов, который автоматически заполняет массив shoppingList
как мы видели ранее.
01
02
03
04
05
06
07
08
09
10
11
12
|
override func viewDidLoad() {
super.viewDidLoad()
// Set Title
title = «Shopping List»
// Load Items
loadItems()
// Register Class
tableView.registerClass(UITableViewCell.classForCoder(), forCellReuseIdentifier: CellIdentifier)
}
|
Последний шаг — инициализация контроллера представления списка покупок путем обновления раскадровки. Это включает в себя добавление экземпляра UITableViewController
, установку его класса на ShoppingListViewController
, встраивание его в контроллер навигации и создание перехода между контроллером панели вкладок и контроллером навигации. Выберите табличное представление контроллера представления списка покупок и установите число ячеек прототипа равным 0 .
Запустите приложение, чтобы увидеть, все ли работает правильно. Конечно, список покупок в настоящее время пуст, и у нас нет способа добавить товары в список покупок. Давайте исправим это на следующем шаге.
4. Добавление товаров в список покупок
Как я писал ранее, идея состоит в том, чтобы добавить элемент в список покупок при его нажатии в контроллере представления списка. Чтобы улучшить взаимодействие с пользователем, если элемент присутствует в списке покупок, мы показываем зеленую галочку слева от названия элемента. Если элемент, который уже находится в списке покупок, коснулся, он удаляется из списка покупок, и зеленая галочка исчезает. Это означает, что нам нужно взглянуть на метод tableView(_:didSelectRowAtIndexPath:)
протокола UITableViewDelegate
.
Прежде чем мы реализуем tableView(_:didSelectRowAtIndexPath:)
, загрузите исходные файлы этого урока. В папке с именем Resources найдите файлы с именами checkmark.png и [email protected] . Добавьте оба этих файла в проект, потому что они понадобятся нам через несколько минут.
В первой строке tableView(_:didSelectRowAtIndexPath:)
мы отправляем табличному представлению сообщение deselectRowAtIndexPath(_:animated:)
чтобы отменить выбор строки, которую коснулся пользователь. При каждом нажатии на строку она должна выделяться только на мгновение, отсюда и это добавление. Затем мы выбираем элемент, который соответствует выбору пользователя, и обновляем свойство inShoppingList
элемента ( true
становится false
и наоборот).
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
|
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
tableView.deselectRowAtIndexPath(indexPath, animated: true)
// Fetch Item
let item = items[indexPath.row]
// Update Item
item.inShoppingList = !item.inShoppingList
// Update Cell
let cell = tableView.cellForRowAtIndexPath(indexPath)
if item.inShoppingList {
cell?.imageView?.image = UIImage(named: «checkmark»)
} else {
cell?.imageView?.image = nil
}
// Save Items
saveItems()
}
|
На основании значения свойства inShoppingList
мы либо показываем, либо скрываем зеленую галочку. Мы показываем галочку, устанавливая свойство image
свойства imageView
ячейки табличного представления. Ячейка табличного представления включает в себя представление изображения слева (экземпляр класса UIImageView
). Если установить для свойства изображения представления image
значение nil
, представление изображения будет пустым, без изображения.
Реализация tableView(_:didSelectRowAtIndexPath:)
завершается сохранением списка элементов на диске, чтобы убедиться, что изменения являются постоянными.
Как список покупок узнает, когда пользователь нажимает на элемент в контроллере представления списка? Контроллер представления списка покупок не будет автоматически обновлять свое представление таблицы, если было внесено изменение в список элементов в контроллере представления списка. Чтобы предотвратить тесную связь, мы не хотим, чтобы контроллер представления списка и контроллер представления списка покупок общались напрямую друг с другом.
Одним из решений этой проблемы является использование уведомлений. Всякий раз, когда контроллер представления списка вносит изменения в список элементов, он отправляет уведомление с определенным именем в центр уведомлений, объект, который управляет уведомлениями. Объекты, которые заинтересованы в определенных уведомлениях, могут добавлять себя в качестве наблюдателей этих уведомлений, что означает, что они могут отвечать, когда эти уведомления публикуются в центре уведомлений.
Как все это работает? Есть три этапа:
- контроллер представления списка покупок начинает с уведомления центра уведомлений о том, что он заинтересован в получении уведомлений с именем
ShoppingListDidChangeNotification
- контроллер представления списка отправляет уведомление в центр уведомлений всякий раз, когда он обновляет список элементов
- когда контроллер представления списка покупок получает уведомление от центра уведомлений, он обновляет свой источник данных и представление таблицы в ответ
Прежде чем мы реализуем три шага, которые я только что описал, неплохо бы поближе познакомиться с классом NSNotificationCenter
.
NSNotificationCenter
говоря, NSNotificationCenter
управляет NSNotificationCenter
уведомлений. Объекты в приложении могут регистрироваться в центре уведомлений для получения уведомлений с использованием addObserver(_:selector:name:object:)
где:
- первый аргумент — это объект, который будет получать уведомления (наблюдатель)
-
selector
— это действие, которое вызывается у наблюдателя при получении уведомления. -
name
это имя уведомления -
object
— это объект, который запускает отправку уведомления
Если последний аргумент имеет значение nil
, наблюдатель получает каждое уведомление с указанным именем.
Шаг 1: Получение уведомлений
Пересмотрите метод viewDidLoad()
класса ShoppingListViewController
и добавьте экземпляр контроллера представления в качестве наблюдателя для получения уведомлений с именем ShoppingListDidChangeNotification
.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
|
override func viewDidLoad() {
super.viewDidLoad()
// Set Title
title = «Shopping List»
// Load Items
loadItems()
// Register Class
tableView.registerClass(UITableViewCell.classForCoder(), forCellReuseIdentifier: CellIdentifier)
// Add Observer
NSNotificationCenter.defaultCenter().addObserver(self, selector: «updateShoppingList:», name: «ShoppingListDidChangeNotification», object: nil)
}
|
Действие запускается, когда контроллер представления получает уведомление с таким именем updateShoppingList(_:)
. Последний параметр, object
, равен nil
, потому что не имеет значения, какой объект отправил уведомление.
Шаг 2: Ответ на уведомления
Метод, который срабатывает, когда наблюдатель получает уведомление, имеет определенный формат, как вы можете видеть ниже. Он принимает один аргумент, объект уведомления, который имеет тип NSNotification
.
Объект уведомления содержит ссылку на объект, который разместил уведомление, и может также содержать словарь с дополнительной информацией. Реализация метода updateShoppingList(_:)
довольно проста. Мы вызываем loadItems()
на контроллере представления, что означает, что список элементов загружается с диска. Остальное происходит автоматически благодаря наблюдателям свойств, которые мы реализовали ранее.
1
2
3
4
5
|
// MARK: —
// MARK: Notification Handling
func updateShoppingList(notification: NSNotification) {
loadItems()
}
|
Шаг 3: Отправка уведомлений
Третий фрагмент головоломки — это публикация уведомления всякий раз, когда список элементов изменяется контроллером представления списка. Мы можем сделать это в saveItems()
класса ListViewController
.
1
2
3
4
5
6
7
8
|
private func saveItems() {
if let filePath = pathForItems() {
NSKeyedArchiver.archiveRootObject(items, toFile: filePath)
// Post Notification
NSNotificationCenter.defaultCenter().postNotificationName(«ShoppingListDidChangeNotification», object: self)
}
}
|
Сначала мы запрашиваем ссылку на центр уведомлений по умолчанию, вызывая defaultCenter()
класса NSNotificationCenter
. Затем мы вызываем postNotificationName(_:object:)
в центре уведомлений по умолчанию, передавая имя уведомления ShoppingListDidChangeNotification
и объект, отправляющий уведомление.
Перед созданием проекта обязательно tableView(_:cellForRowAtIndexPath:)
в ListViewController.swift, как показано ниже, чтобы отобразить зеленую галочку для элементов, которые уже присутствуют в списке покупок.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
|
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
// Dequeue Reusable Cell
let cell = tableView.dequeueReusableCellWithIdentifier(CellIdentifier, forIndexPath: indexPath)
// Fetch Item
let item = items[indexPath.row]
// Configure Table View Cell
cell.textLabel?.text = item.name
cell.accessoryType = .DetailDisclosureButton
if item.inShoppingList {
cell.imageView?.image = UIImage(named: «checkmark»)
} else {
cell.imageView?.image = nil
}
return cell
}
|
Запустите приложение со списком покупок, чтобы раскрутить его. Вы заметили, что изменения в предмете автоматически отражаются в списке покупок?
Готов к публикации?
Отлично. Где кнопка для публикации приложения со списком покупок в App Store? Мы еще не совсем закончили. Несмотря на то, что мы заложили основу приложения списка покупок, оно не готово к публикации. Есть также несколько вещей, чтобы рассмотреть.
Масштабируемость
Приложение списка покупок представляет собой скромную реализацию списка покупок. Если бы приложение содержало сотни или тысячи элементов, было бы целесообразно добавить возможности поиска, а также разделы для сортировки элементов по алфавиту (как мы делали ранее в этой серии). Важно понимать, что список элементов записывается на диск полностью при каждом обновлении элемента. Это не проблема, если список небольшой, но это произойдет, если список со временем увеличится до сотен или тысяч элементов.
Отношения
Кроме того, пользователи могут захотеть сохранить более одного списка покупок. Как бы вы справились с этим? Один из вариантов — хранить каждый список покупок в отдельном файле, но как вы будете реагировать на изменения, внесенные в товары? Собираетесь ли вы обновить каждый список покупок, который содержит товар? Когда вы начинаете работать со связями, лучше выбрать хранилище данных SQLite.
Core Data — отличный компаньон, если вы решите пойти по этому пути. Это мощный фреймворк, обладающий множеством функций, которые делают большую часть кода в нашем приложении списка покупок устаревшим. Это правда, что Core Data приносит с собой немного больше накладных расходов, поэтому важно сначала рассмотреть вопрос о том, подходят ли Core Data для вашего приложения, другими словами, стоит ли это накладных расходов.
Дополнительные возможности
Существует также большой потенциал для дополнительных функций. Свойство цены товаров остается неиспользованным в текущей реализации приложения списка покупок. Кроме того, было бы неплохо, если бы пользователь мог отмечать товар из списка покупок, нажав на товар. Как видите, текущая реализация приложения со списком покупок — лишь скромное начало.
Вывод
Даже если приложение списка покупок не совсем готово для App Store, вы не можете отрицать, что оно работает как запланировано, и оно продемонстрировало вам несколько новых аспектов разработки Cocoa, таких как уведомления и реализация пользовательского протокола делегата.
Теперь вы знаете, чего ожидать от iOS SDK и на что похожа разработка iOS. Вам решать, хотите ли вы продолжить свое путешествие и стать опытным разработчиком iOS. Если вы решите продолжить разработку для iOS, я предоставлю вам несколько полезных ресурсов в следующей и последней части этой серии.
Если у вас есть какие-либо вопросы или комментарии, вы можете оставить их в комментариях ниже или обратиться ко мне в Twitter .