SA1010 — Модель данных (Data Model)
Содержание страницы
В предыдущей главе вы создали табличное представление для контрольныхсписков, заставили его отображать строки элементов и добавили возможность отмечать элементы как завершенные (или не завершенные). Однако все это было сделано с использованием жестко закодированных поддельных данных. Это не подходит для реального приложения to-do, так как ваши пользователи хотят хранить свои собственные пользовательские элементы to-do.
Для эффективного хранения, управления и отображения информации о делах необходима модель данных, позволяющая легко хранить информацию о делах (и получать к ней доступ). Именно это вы и собираетесь сделать в этой главе.
В этой главе рассматриваются следующие вопросы:
- Model-View-Controller: краткое объяснение основ MVC, которые занимают центральное место в программировании iOS.
- Модель данных: Создание модели данных для хранения данных для контрольныхсписков.
- Очистите код: упростите свой код, чтобы его было легче понять и поддерживать.
Модель-Вид-Контроллер
Во-первых, небольшой крюк в страну концепций программирования, чтобы вы поняли некоторые принципы использования модели данных. Ни одна книга по программированию для iOS не может избежать объяснения Model-View-Controller, или сокращенно MVC.
MVC — это один из трех фундаментальных шаблонов проектирования iOS. Вы уже видели два других: делегирование, заставляющее один объект делать что-то от имени другого, и целевое действие, связывающее события, такие как нажатия кнопок, с методами действия.
Шаблон Model-View-Controller гласит, что объекты в вашем приложении можно разделить на три группы:
- Модельные объекты. Эти объекты содержат ваши данные и любые операции с ними. Например, если бы вы писали приложение для кулинарной книги, модель состояла бы из рецептов. В игре это будет дизайн уровней, счет игрока и позиции монстров. Операции, выполняемые объектами модели данных, иногда называются бизнес — правилами или логикой предметнойобласти. Для контрольныхсписков контрольные списки и их элементы образуют модель данных.
- Просмотр объектов. Они составляют визуальную часть приложения: изображения, кнопки, метки, текстовые поля, ячейки табличного представления и так далее. В игре представления формируют визуальное представление игрового мира, например анимацию монстров и счетчик фрагов. Представление может рисовать само себя и реагировать на вводимые пользователем данные, но обычно оно не обрабатывает никакой логики приложения. Многие представления, например
UITableView
, могут быть повторно использованы во многих различных приложениях, поскольку они не привязаны к конкретной модели данных. - Объекты контроллера. Контроллер — это объект, который соединяет объекты модели данных с представлениями. Он прослушивает нажатия на представления, заставляет объекты модели данных выполнять некоторые вычисления в ответ и обновляет представления, чтобы отразить новое состояние вашей модели. Контроллер отвечает за это. В iOS контроллер называется “view controller”.
Концептуально, именно так эти три строительных блока сочетаются друг с другом:

Как работает Model-View-Controller
Контроллер представления имеет одно основное представление, доступное через его view
свойство, которое содержит кучу подвидов. Нередко экран имеет десятки просмотров одновременно. Вид верхнего уровня обычно заполняет весь экран. Вы создаете макет экрана контроллера вида в раскадровке.
В контрольныхсписках основное представлениеUITableView
-это представление, а его подвиды-ячейки табличного представления. Каждая ячейка также имеет несколько собственных подвидов, а именно текстовую метку и аксессуар.
Как правило, контроллер представления обрабатывает один экран приложения. Если ваше приложение имеет более одного экрана, каждый из них обрабатывается своим собственным контроллером представлений и имеет свои собственные представления. Ваше приложение переходит от одного контроллера представления к другому.
Часто вам придется создавать собственные контроллеры представлений, но iOS также поставляется с готовыми к использованию контроллерами представлений, такими как контроллер выбора изображений для фотографий, контроллер составления почты, позволяющий писать электронную почту, и, конечно же, контроллер табличного представления для отображения списков элементов.
Представления и контроллеры представлений
Помните, что представление и контроллер представления — это две разные вещи.
Вид-это объект, который рисует что-то на экране, например кнопку или метку. Вид-это то, что вы видите.
Контроллер представления-это то, что делает работу за кулисами. Это мост, который находится между вашей моделью данных и представлениями.
Многие новички дают своим контроллерам представлений такие имена, как
FirstView
илиMainView
. Это очень сбивает с толку! Если что-то является контроллером представления, его имя должно заканчиваться на “ViewController”, а не на “View”.Иногда мне хочется, чтобы Apple оставила слово “view” из “view controller” и просто назвала его “controller”, поскольку это гораздо менее вводит в заблуждение.
Модель данных
До сих пор вы помещали кучу поддельных данных в табличное представление. Данные состоят из текстовой строки и флажка, который может быть включен или выключен.
Как вы видели в предыдущей главе, вы не можете использовать ячейки для запоминания данных, так как ячейки постоянно используются повторно, а их старое содержимое перезаписывается.
Ячейки табличного представления являются частью представления. Их цель-отобразить данные приложения, но на самом деле эти данные поступают откуда-то еще: из модели данных.
Запомните это хорошенько: строки-это данные, ячейки-это представления.
Контроллер табличного представления-это то, что связывает их вместе посредством реализации методов источника данных и делегирования табличного представления.

Контроллер табличного представления (источник данных) получает данные из модели и помещает их в ячейки
Модель данных для этого приложения будет представлять собой список дел. Каждый из этих элементов получит свою собственную строку в таблице.
Для каждого пункта нужно сохранить две части информации: текст (“Выгулять собаку”, “Почистить зубы”, “Съесть мороженое”) и то,установлена ли галочка или нет.
Это две части информации в строке, поэтому вам нужны две переменные для каждой строки.
Первая итерация
Во-первых, я покажу вам громоздкий способ запрограммировать это. Это сработает, но не очень умно. Несмотря на то, что это не самый лучший подход, я все равно хотел бы, чтобы вы последовали его примеру, скопировали код в Xcode и запустили приложение, чтобы понять, как работает этот подход.
Понимание того, почему такой подход проблематичен, поможет вам лучше оценить правильное решение.
В ChecklistViewController.swiftдобавьте следующие константы сразу после class ChecklistViewController
строки.
class ChecklistViewController: UITableViewController { let row0text = "Walk the dog" let row1text = "Brush teeth" let row2text = "Learn iOS development" let row3text = "Soccer practice" let row4text = "Eat ice cream" . . .
Эти константы определяются вне любого метода (они не являются “локальными”), поэтому они могут использоваться всеми методами в ChecklistViewController
.
BOS Измените методы источника данных на:
`override func tableView(
_ tableView: UITableView,
numberOfRowsInSection section: Int
) -> Int {
return 5
}
override func tableView(
_ tableView: UITableView,
cellForRowAt indexPath: IndexPath
) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(
withIdentifier: «ChecklistItem»,
for: indexPath)
let label = cell.viewWithTag(1000) as! UILabel
if indexPath.row == 0 {
label.text = row0text
} else if indexPath.row == 1 {
label.text = row1text
} else if indexPath.row == 2 {
label.text = row2text
} else if indexPath.row == 3 {
label.text = row3text
} else if indexPath.row == 4 {
label.text = row4text
}
return cell
}`
BOS Запустите приложение. Он по-прежнему показывает те же пять строк, что и изначально.
Что вы здесь сделали? Для каждой строки вы добавили константу с текстом для этой строки. Вместе эти пять констант и составляют вашу модель данных. (Вы могли бы использовать переменные вместо констант, но поскольку значения не изменятся для этого конкретного примера, лучше использовать константы.)
В tableView(_:cellForRowAt:)
вы смотрите на indexPath.row
то, чтобы выяснить, какую строку отображать, и помещаете текст из соответствующей константы в ячейку.
Обрабатывать галочки
Теперь давайте исправим логику переключения галочек. Вы больше не хотите переключать флажок на ячейке, но на уровне строки (или данных). Для этого вы добавляете пять новых переменных экземпляра, чтобы отслеживать “проверенное” состояние каждой из строк. (На этот раз значения должны быть переменными, а не константами, так как вы будете изменять проверенное/непроверенное состояние для каждой строки.) Эти новые переменные также являются частью вашей модели данных.
BOS Добавьте следующие переменные экземпляра:
var row0checked = false var row1checked = false var row2checked = false var row3checked = false var row4checked = false
Эти переменные имеют тип данных Bool
. Вы уже видели типы данных Int
(целые числа), Float
(десятичные/дробные числа) и String
(текст) раньше. Bool
Переменная может содержать только два возможных значения: true
или false
.
Bool
это сокращение от “булева”, в честь англичанина Джорджа Буля, который давным-давно изобрел своего рода логику, лежащую в основе всех современных вычислений. Тот факт, что компьютеры говорят единицами и нулями, во многом обязан ему.
Вы используете Bool
переменные, чтобы запомнить, является ли что-то истинным (1) или нет (0). Как правило, имена булевых переменных часто начинаются с глагола “is” или “has”, как в isHungry
or hasIceCream
.
Переменная экземпляраrow0checked``true
-это если в первой строке установлена галочка, а false
в другой-нет. Точно так же row1checked
отражает, есть ли во второй строке галочка или нет, и так далее.
Примечание: Как компилятор узнает, что тип этих переменных таков
Bool
? Вы нигде этого не указывали.Помните вывод типа из вашего кода в Яблочко? Из того , что вы сказали
var row0checked = false
, компилятор делает вывод, что вы намеревались сделать это aBool
, так какfalse
это допустимо только дляBool
значений.
Метод делегата, обрабатывающий нажатия на ячейки таблицы, теперь будет использовать эти новые переменные экземпляра, чтобы определить, нужно ли включать или выключать флажок для строки.
Код tableView(_:didSelectRowAt:)
должен быть примерно следующим. Не вносите эти изменения прямо сейчас! Просто попытайтесь понять, что происходит в первую очередь.
override func tableView( _ tableView: UITableView, didSelectRowAt indexPath: IndexPath ) { if let cell = tableView.cellForRow(at: indexPath) { if indexPath.row == 0 { row0checked = !row0checked if row0checked { cell.accessoryType = .checkmark } else { cell.accessoryType = .none } } else if indexPath.row == 1 { row1checked = !row1checked if row1checked { cell.accessoryType = .checkmark } else { cell.accessoryType = .none } } else if indexPath.row == 2 { row2checked = !row2checked if row2checked { cell.accessoryType = .checkmark } else { cell.accessoryType = .none } } else if indexPath.row == 3 { row3checked = !row3checked if row2checked { cell.accessoryType = .checkmark } else { cell.accessoryType = .none } } else if indexPath.row == 4 { row4checked = !row4checked if row4checked { cell.accessoryType = .checkmark } else { cell.accessoryType = .none } } } tableView.deselectRow(at: indexPath, animated: true) }
Должно быть ясно, что код ищет indexPath.row
строку, которая была прослушана, а затем выполняет некоторую логику с соответствующей переменной экземпляра “row checked”. Но есть и кое-что новое, чего вы, возможно, раньше не видели.
Давайте рассмотрим первое if indexPath.row
утверждение подробнее:
if indexPath.row == 0 { row0checked = !row0checked if row0checked { cell.accessoryType = .checkmark } else { cell.accessoryType = .none } } . . .
Если indexPath.row
равно 0, то пользователь нажимает на самую первую строку и соответствующая переменная экземпляра равна row0checked
.
Чтобы перевернуть это логическое значение, выполните следующие действия:
row0checked = !row0checked
!
Символ — это логический оператор not. Есть несколько других логических операторов, которые работают со Bool
значениями, такими как and и or, с которыми вы скоро столкнетесь.
Что !
делает, так это просто: он переворачивает смысл значения. Если row0checked
есть true
, то !
делает это false
. И наоборот, !false
есть true
.
Думайте о !
“не”: не » да «- это «нет», а » нет «- это «да». Да?
Как только у вас есть новое значение row0checked
, вы можете использовать его, чтобы показать или скрыть галочку:
if row0checked { cell.accessoryType = .checkmark } else { cell.accessoryType = .none }
Та же логика используется и для остальных четырех строк.
На самом деле другие строки используют точно такую же логику. Единственное, что отличается между каждым из этих блоков кода, — это имя переменной экземпляра “проверенная строка”.
Переключение логических значений
Переключение Bool
значений из true
в false
(или наоборот) является настолько распространенным действием, что Swift добавила метод, позволяющий сделать это легко, не беспокоясь о том, каково значение Bool
переменной. Этот метод вызываетсяtoggle
, и вы просто вызываете toggle
метод для Bool
переменной, чтобы переключить ее значение на противоположное значение.
Итак, давайте немного улучшим вышеприведенный код двумя способами:
- Используйте этот
toggle
метод для переворачивания логических значений. - Поскольку код выглядит так похоже от одного
if
оператора к другому, упростите его еще немного.
BOS Заменить текущую tableView(_:didSelectRowAt:)
реализацию следующей::
`override func tableView(
_ tableView: UITableView,
didSelectRowAt indexPath: IndexPath
) {
if let cell = tableView.cellForRow(at: indexPath) {
var isChecked = false
if indexPath.row == 0 {
row0checked.toggle()
isChecked = row0checked
} else if indexPath.row == 1 {
row1checked.toggle()
isChecked = row1checked
} else if indexPath.row == 2 {
row2checked.toggle()
isChecked = row2checked
} else if indexPath.row == 3 {
row3checked.toggle()
isChecked = row3checked
} else if indexPath.row == 4 {
row4checked.toggle()
isChecked = row4checked
}
if isChecked {
cell.accessoryType = .checkmark
} else {
cell.accessoryType = .none
}
}
tableView.deselectRow(at: indexPath, animated: true)
}`
Разве это не намного короче предыдущей итерации (которую вы не должны были вводить)?
Обратите внимание, как логика, устанавливающая галочку в ячейке, переместилась в нижнюю часть метода. Сейчас есть только одно место, где это происходит.
Чтобы сделать это возможным, вы сохраняете значение переменной экземпляра “строка проверена” в isChecked
локальной переменной. Эта временная переменная используется только для того, чтобы запомнить, нужна ли выделенной строке галочка или нет.
Используя локальную переменную, вы смогли удалить много дублированного кода, и это хорошо. Вы взяли логику, которая была общей для всех строк, и переместили ее из if
операторов в одно место.
Примечание: дублирование кода значительно затрудняет чтение программ. Хуже того, это приводит к тонким ошибкам, которые вызывают труднодоступные ошибки. Всегда будьте в курсе возможностей удаления дубликатов кода!
Упражнение: В предыдущей, более длинной версии этого метода действительно была ошибка – вы ее заметили? Вот что происходит, когда вы используете copy-paste для создания дубликата кода, как я сделал, когда писал этот метод.
BOS Запустите приложение и наблюдайте… что он все еще работает не очень хорошо. Сначала вам нужно нажать пару раз подряд, чтобы галочка действительно исчезла.
Что здесь не так? Все просто: когда вы объявляете rowXchecked
переменные, вы устанавливаете им значения false
.
So row0checked
и другие указывают, что в их строке нет галочки, но таблица все равно рисует ее. Это потому, что вы включили аксессуар checkmark в ячейке прототипа.
Другими словами,модель данных (переменные “проверенная строка”) и представления (галочки внутри ячеек) не синхронизированы.
Есть несколько способов исправить это: вы можете установить Bool
переменные равными для true
начала или удалить галочку из ячейки прототипа в раскадровке.
Ни то, ни другое не является надежным решением. Здесь происходит не столько то, что вы неправильно инициализировали значения “проверенной строки” или неправильно спроектировали ячейку прототипа, сколько то, что вы не установили accessoryType
свойству ячейки правильное значение tableView(_:cellForRowAt:)
.
Когда вас просят создать новую ячейку, вы всегда должны настроить все ее свойства. Вызов to tableView.dequeueReusableCell(withIdentifier:)
может возвращать ячейку, которая ранее использовалась для строки с галочкой. Если новая строка не должна иметь галочки, то в этот момент вы должны удалить ее из ячейки (и наоборот). Давайте это исправим.
BOS Добавьте следующий метод в ChecklistViewController.swift. (Если вам интересно, куда добавить код, вероятно, лучше всего добавить его до или после отмеченных разделов для делегатов табличного представления. Не то чтобы должность имела значение, но чисто с организационной точки зрения.):
`func configureCheckmark(
for cell: UITableViewCell,
at indexPath: IndexPath
) {
var isChecked = false
if indexPath.row == 0 {
isChecked = row0checked
} else if indexPath.row == 1 {
isChecked = row1checked
} else if indexPath.row == 2 {
isChecked = row2checked
} else if indexPath.row == 3 {
isChecked = row3checked
} else if indexPath.row == 4 {
isChecked = row4checked
}
if isChecked {
cell.accessoryType = .checkmark
} else {
cell.accessoryType = .none
}
}`
Этот новый метод просматривает ячейку для определенной строки, указанной как обычно indexPath
, и делает галочку видимой , если есть соответствующая переменная “строка проверена”true
, или скрывает галочку, если есть переменная false
.
Эта логика должна выглядеть очень знакомой! Единственное отличие от before заключается в том, что здесь вы не переключаете состояние переменной “строка проверена”. Вы только читаете его, а затем устанавливаете принадлежность ячейки.
Вы вызовете этот метод tableView(_:cellForRowAt:)
непосредственно перед возвращением ячейки.
BOS Измените tableView(_:cellForRowAt:)
на следующее (напомним, что . . .
это означает, что существующий код в этом месте не меняется):
`override func tableView(
_ tableView: UITableView,
cellForRowAt indexPath: IndexPath
) -> UITableViewCell {
. . .
configureCheckmark(for: cell, at: indexPath)
return cell
}`
BOS Запустите приложение еще раз.
Теперь приложение работает просто отлично. Изначально все строки не отмечены галочками. Нажатие на строку проверяет ее, повторное нажатие снимает флажок. Строки и ячейки теперь всегда синхронизированы. Этот код гарантирует, что каждая ячейка всегда будет иметь значение, соответствующее ее базовой строке данных.

Внешние и внутренние имена параметров
Новый configureCheckmark
метод имеет два параметра, for
и at
. Поэтому его полное название таково configureCheckmark(for:at:)
.
for
и at
являются внешними именами этих параметров.
Добавление коротких предлогов, таких как “at”, “with” или “for”, очень распространено в Swift. Это заставляет название метода звучать как правильная английская фраза: “configure checkmark for this cell at that index-path”. Разве это не просто слетает с твоего языка?
Когда вы вызываете метод, вы всегда должны включать эти внешние имена параметров:
configureCheckmark(for: someCell, at: someIndexPath)
Здесь someCell
это переменная, которая ссылается на UITableViewCell
объект. Аналогично, someIndexPath
это переменная типа IndexPath
.
Вы не можете написать следующее:
configureCheckmark(someCell, someIndexPath)
Это не будет компилироваться. В приложении нет configureCheckmark
метода, который не принимает только имена параметров configureCheckmark(for:at:)
. for
И at
являются неотъемлемой частью имени метода!
Внутри метода вы используете внутренние метки cell
и indexPath
ссылки на параметры.
`func configureCheckmark(
for cell: UITableViewCell,
at indexPath: IndexPath
) {
if indexPath.row == 0 {
. . .
}
cell.accessoryType = .checkmark
. . .
}`
Ты не можешь писать if at.row == 0
или for.accessoryType = .checkmark
. Это тоже звучит немного странно, не так ли?
Это разделение между внешними и внутренними метками уникально для Swift и Objective-C и требует некоторого привыкания, если вы знакомы с другими языками.
Это соглашение об именах в первую очередь существует для того, чтобы Swift мог общаться со старым кодом Objective-C, и это хорошо, поскольку большинство фреймворков iOS все еще написаны на Objective-C.

Упростите код
В любом случае, почему он был configureCheckmark(for:at:)
создан как собственный метод? Ну, потому что вы можете использовать его для упрощения tableView(_:didSelectRowAt:)
.
Обратите внимание, насколько похожи эти два метода в настоящее время. Это еще один случай дублирования кода, от которого вы можете избавиться!
Вы можете упростить didSelectRowAt
задачу, позволив configureCheckmark(for:at:)
сделать часть работы.
Заменить tableView(_:didSelectRowAt:)
следующим:
override func tableView( _ tableView: UITableView, didSelectRowAt indexPath: IndexPath ) { if let cell = tableView.cellForRow(at: indexPath) { if indexPath.row == 0 { row0checked.toggle() } else if indexPath.row == 1 { row1checked.toggle() } else if indexPath.row == 2 { row2checked.toggle() } else if indexPath.row == 3 { row3checked.toggle() } else if indexPath.row == 4 { row4checked.toggle() } configureCheckmark(for: cell, at: indexPath) } tableView.deselectRow(at: indexPath, animated: true) }
Этот метод больше не устанавливает и не удаляет галочку из ячейки, а только переключает “проверенное” состояние в модели данных, а затем вызывает configureCheckmark(for:at:)
обновление представления.
BOS Запустите приложение еще раз, и оно все равно должно работать.
BOS Измените объявления переменных экземпляра на следующие и запустите приложение снова.:
var row0checked = false var row1checked = true var row2checked = true var row3checked = false var row4checked = true
Теперь строки 1, 2 и 4 (вторая, третья и пятая строки) изначально имеют галочку, а остальные-нет.

Модель данных и ячейки табличного представления теперь всегда синхронизированы
Массивы
Подход, который мы использовали выше, чтобы запомнить, какие строки проверяются или нет, работает просто отлично… когда есть пять строк данных.
Но что делать, если у вас есть 100 строк, и все они должны быть уникальными? Должны ли вы добавить еще 95 переменных “текст строки” и “проверенная строка” в контроллер представления, а также столько дополнительных if
операторов? Надеюсь, что нет!
Есть лучший способ: массивы.
Массив — это упорядоченный список объектов. Если вы думаете о переменной как о контейнере одного значения (или одного объекта), то массив-это контейнер для нескольких объектов.

Массивы-это упорядоченные списки, содержащие несколько объектов
Конечно, сам массив также является объектом (именованнымArray
), который вы можете поместить в переменную. А поскольку массивы являются объектами, массивы могут содержать другие массивы.

Массивы могут также включать в себя другие массивы
Объекты внутри массива индексируются числами, как обычно, начиная с 0. Чтобы запросить у массива первый объект, вы пишете array[0]
. Второй объект-at array[1]
и так далее.
Массив упорядочен, что означает, что порядок содержащихся в нем объектов имеет значение. Объект с индексом 0 всегда стоит перед объектом с индексом 1.
Примечание: Массив — это объект коллекции. Есть несколько других объектов коллекции, и все они организуют свои объекты по — разному.
Dictionary
например , содержит пары ключ-значение, точно так же, как настоящий словарь содержит список слов и описание каждого из этих слов. Некоторые из этих типов коллекций будут использоваться в последующих главах.
Организация массива очень похожа на строки таблицы — оба они представляют собой списки объектов в определенном порядке, — поэтому имеет смысл поместить строки вашей модели данных в массив.
Массивы хранят по одному объекту на индекс, но ваши строки в настоящее время состоят из двух отдельных фрагментов данных: текста и проверенного состояния. Было бы проще, если бы вы сделали один объект для каждой строки, потому что тогда номер строки из таблицы просто становится индексом в массиве.
Вторая итерация
Давайте объединим текст и состояние галочки в новый собственный объект!
Объект
BOS Выберите группу Контрольные списки в навигаторе проектов и щелкните правой кнопкой мыши. Выберите Новый файл… из всплывающего меню:

Добавление нового файла в проект
В разделе Источник выберите Файл Swift:

Выбор шаблона файла Swift
Нажмите кнопку Далее, чтобы продолжить. Сохраните новый файл как ChecklistItem (на самом деле вам не нужно добавлять расширение файла .swift, так как он будет автоматически добавлен для вас).

Сохранение нового файла Swift
Нажмите кнопку Создать, чтобы добавить новый файл в проект.
BOS Добавьте следующее в новый файл ChecklistItem.swift под import
строкой:
class ChecklistItem { var text = "" var checked = false }
То, что вы видите здесь, — это абсолютный минимальный объем кода, необходимый для создания нового объекта. class
Ключевое слово называет объект и две строки с var
добавлением к нему элементов данных (переменных экземпляра).
text
Свойство будет хранить описание элемента контрольного списка (текст, который появится в метке ячейки табличного представления), и checked
свойство определяет, будет ли ячейка отмечена галочкой или нет.
Примечание: Возможно, вам интересно, в чем разница между терминами property и instance variable — мы использовали оба термина для обозначения элементов данных объекта. Вы будете рады услышать, что эти два термина взаимозаменяемы.
В терминологии Swift свойство — это переменная или константа, используемая в контексте объекта. Именно это и есть переменная экземпляра.
(В Objective-C свойства и переменные экземпляра тесно связаны, но не совсем одно и то же. В Swift они одинаковы.)
На данный момент это все для ChecklistItem.swift. В ChecklistItem
настоящее время объект служит только для объединения переменных text
и checked
переменных в один объект. Позже вы сделаете с ним больше.
Использование объекта
Прежде чем пытаться использовать массив, замените String``Bool
переменные экземпляра and в контроллере представления этими новыми ChecklistItem
объектами, чтобы увидеть, как будет работать этот подход.
BOS В ChecklistViewController.swiftудалите старые свойства (как let
значения, так и var
значения) и замените их ChecklistItem
объектами:
class ChecklistViewController: UITableViewController { var row0item = ChecklistItem() var row1item = ChecklistItem() var row2item = ChecklistItem() var row3item = ChecklistItem() var row4item = ChecklistItem()
Они заменяют row0text``row0checked
переменные экземпляра, и т. Д.
Хотя подожди минутку … У нас были объявления переменных с типом или с явными значениями, такими как пустая строка или число, но что это такое? Эти переменные назначаются с помощью того, что выглядит как метод!
И вы правы насчет метода — это специальный метод, который все классы называют методом инициализатора. В данном случае метод инициализатора создает новый экземпляр данного объекта ChecklistItem
. Это создает пустой экземпляр ChecklistItem
со значениями по умолчанию, которые вы определили при добавлении реализации класса, — пустую строку (””) for text
и false
for checked
.
Вместо вышесказанного вы могли бы использовать так называемую аннотацию типа, чтобы просто указать типrow0Item
, подобный этому:
var row0item: ChecklistItem
Если вы это сделаете, row0item
у вас еще не будет значения, это будет просто пустой контейнер для ChecklistItem
объекта. И вам все равно придется создать ChecklistItem
экземпляр позже в вашем коде (например, in viewDidLoad
).
Так же, как мы сделали код сейчас, мы немедленно инициализируем переменные выше пустым экземпляром ChecklistItem
и позволяем Swift-выводу типа выполнять работу, позволяя компилятору определить тип переменных. Удобно, правда?
Просто чтобы немного прояснить вышесказанное, тип данных похож на фирменное наименование автомобиля. Просто произнеся вслух слова “Porsche 911”, вы волшебным образом не получите новый автомобиль — на самом деле вам нужно пойти к дилеру, чтобы купить его.
Скобки()
, стоящие за именем типа, подобны походу в дилерский центр объекта, чтобы купить объект этого типа. Скобки говорят фабрике объектов Swift: “Постройте мне объект этого типаChecklistItem
”.
Важно помнить, что простое объявление о том, что у вас есть переменная, не делает автоматически соответствующий объект для вас. Переменная-это просто контейнер для объекта. Вам все равно придется создать экземпляр объекта и поместить его в контейнер. Переменная-это коробка, а объект-это вещь внутри коробки.
Поэтому до тех пор , пока вы не закажете фактический ChecklistItem
объект с фабрики и не поместите его в row0item
переменную, она будет пуста. А пустые переменные-это большое нет-нет в Swift.
Исправление существующего кода
Поскольку некоторые методы в контроллере представления все еще ссылаются на старые переменные, Xcode будет выдавать несколько ошибок на этом этапе. Прежде чем вы сможете снова запустить приложение, вам необходимо исправить эти ошибки. Итак, давайте сделаем это сейчас.
Примечание: Обычно я рекомендую вам вводить код из этой книги вручную (вместо копирования-вставки), потому что это дает вам лучшее представление о том, что вы делаете, но в следующих случаях проще просто скопировать-вставить из PDF или использовать кнопку копирования в нашем приложении. онлайн-ридер.
К сожалению, копирование из PDF-файла иногда добавляет странные или невидимые символы, которые путают Xcode. Лучше всего сначала вставить скопированный текст в обычный текстовый редактор, такой как TextMate, а затем скопировать/вставить из текстового редактора в Xcode.
Конечно, если вы читаете печатное издание этой книги, копирование и вставка из нее не сработает, но вы все равно можете использовать copy-paste, чтобы сэкономить немного усилий. Внесите изменения в одну строку, а затем скопируйте эту строку, чтобы создать другие строки. Copy-paste-лучший друг программиста, но не забудьте обновить вставленные строки, чтобы использовать правильные имена переменных!
BOS In tableView(_:cellForRowAt:)
, замените if
операторы следующими:
if indexPath.row == 0 { label.text = row0item.text } else if indexPath.row == 1 { label.text = row1item.text } else if indexPath.row == 2 { label.text = row2item.text } else if indexPath.row == 3 { label.text = row3item.text } else if indexPath.row == 4 { label.text = row4item.text }
BOS In tableView(_:didSelectRowAt:)
, снова измените блок if
операторов на:
if indexPath.row == 0 { row0item.checked.toggle() } else if indexPath.row == 1 { row1item.checked.toggle() } else if indexPath.row == 2 { row2item.checked.toggle() } else if indexPath.row == 3 { row3item.checked.toggle() } else if indexPath.row == 4 { row4item.checked.toggle() }
И, наконец , inconfigureCheckmark(for:at:)
, измените if
блок на:
if indexPath.row == 0 { isChecked = row0item.checked } else if indexPath.row == 1 { isChecked = row1item.checked } else if indexPath.row == 2 { isChecked = row2item.checked } else if indexPath.row == 3 { isChecked = row3item.checked } else if indexPath.row == 4 { isChecked = row4item.checked }
В принципе, все вышеперечисленные изменения делают одну вещь — вместо использования отдельных row0text``row0checked
переменных and теперь вы используете row0item.text
and row0item.checked
. Это позаботится обо всех ошибках, и вы даже сможете создать и запустить приложение. Но если вы это сделаете, то заметите, что у вас пустой стол. Попробуйте нажать на первые несколько строк. Вы заметите, что для них появляются галочки, которые включаются и выключаются. Все любопытнее и любопытнее … Так что же пошло не так?
Настройка объектов
Помните, как я сказал, что новые row0item
переменные etc. инициализируются пустыми экземплярами ChecklistItem
? Это означает, что текст для каждой переменной пуст. Вам все равно нужно настроить значения для этих новых переменных!
Изменить viewDidLoad
в ChecklistViewController.swift следующим образом::
`override func viewDidLoad() {
super.viewDidLoad()
// Add the following lines
row0item.text = «Walk the dog»
row1item.text = «Brush my teeth»
row1item.checked = true
row2item.text = «Learn iOS development»
row2item.checked = true
row3item.text = «Soccer practice»
row4item.text = «Eat ice cream»
row4item.checked = true
}`
Этот код просто устанавливает каждую из новых ChecklistItem
переменных, которые вы создали. Если вам интересно, почему некоторые переменные имеют строку для установки checked
свойства, а некоторые нет, помните, что вы инициализируете checked``false
ее в реализации ChecklistItem
класса. Это значение по умолчанию применяется к новому объекту при его создании. Таким образом, хотя вы все еще можете добавить строку для установки checked``false
, в этом нет необходимости, так checked
как свойство уже установлено false
.
Приведенный выше код по существу делает то же самое, что и раньше, за исключением того, что на этот раз text``checked
переменные and не являются отдельными переменными экземпляра контроллера представления, а являются свойствами ChecklistItem
объекта.
BOS Запустите приложение, чтобы убедиться, что теперь все работает.
Помещение свойств text
and checked
в их собственный ChecklistItem
объект уже улучшило код, но он все еще немного громоздок.
Использование массивов
При нынешнем подходе вам нужно сохранить ChecklistItem
переменную экземпляра для каждой строки. Это не идеально, особенно если вам нужно больше, чем просто несколько строк. Время ввести этот массив в игру!
В ChecklistViewController.swiftудалите все переменные экземпляра и замените их одной переменной массива с именемitems
:
class ChecklistViewController: UITableViewController { var items = [ChecklistItem]()
Вместо пяти различных переменных экземпляра, по одной для каждой строки, теперь у вас есть только одна переменная для массива.
Это похоже на то, как вы объявили предыдущие переменные, но на этот раз вокруг них квадратные скобки ChecklistItem
. Эти квадратные скобки указывают на то, что переменная будет представлять собой массив, содержащий ChecklistItem
объекты. А скобки в конце ()
просто указывают на то, что вы создаете экземпляр этого массива — он создаст пустой массив без элементов в массиве.
viewDidLoad
Изменить следующим образом:
`override func viewDidLoad() {
super.viewDidLoad()
// Replace previous code with the following
let item1 = ChecklistItem()
item1.text = «Walk the dog»
items.append(item1)
let item2 = ChecklistItem()
item2.text = «Brush my teeth»
item2.checked = true
items.append(item2)
let item3 = ChecklistItem()
item3.text = «Learn iOS development»
item3.checked = true
items.append(item3)
let item4 = ChecklistItem()
item4.text = «Soccer practice»
items.append(item4)
let item5 = ChecklistItem()
item5.text = «Eat ice cream»
items.append(item5)
}`
Это не так уж отличается от предыдущего, за исключением того, что теперь вам нужно сначала создать — или создать экземпляр — каждого ChecklistItem
объекта и добавить каждый экземпляр в массив. После завершения приведенного выше кода items
массив содержит пять ChecklistItem
объектов. Это ваша новая модель данных.
Упростите код — еще раз
Теперь у вас есть все ваши строки в items
массиве.
Вы можете еще раз упростить источник данных табличного представления и делегировать методы.
BOS Измените эти методы.:
`override func tableView(
_ tableView: UITableView,
cellForRowAt indexPath: IndexPath
) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(
withIdentifier: «ChecklistItem»,
for: indexPath)
let item = items[indexPath.row] // Add this
let label = cell.viewWithTag(1000) as! UILabel
// Replace everything after the above line with the following
label.text = item.text
configureCheckmark(for: cell, at: indexPath)
return cell
}`
`override func tableView( _ tableView: UITableView, didSelectRowAt indexPath: IndexPath ) { if let cell = tableView.cellForRow(at: indexPath) { // Replace everything inside this
if` condition
// with the following
let item = items[indexPath.row]
item.checked.toggle()
configureCheckmark(for: cell, at: indexPath)
}
tableView.deselectRow(at: indexPath, animated: true)
}«
`func configureCheckmark(
for cell: UITableViewCell,
at indexPath: IndexPath
) {
// Replace full method implementation
let item = items[indexPath.row]
if item.checked {
cell.accessoryType = .checkmark
} else {
cell.accessoryType = .none
}
}`
Это намного проще, чем то, что у вас было раньше! Теперь каждый метод имеет длину всего в несколько строк.
В каждом методе вы делаете:
let item = items[indexPath.row]
При этом массив запрашивает ChecklistItem
объект с индексом, соответствующим номеру строки. Как только у вас есть этот объект, вы можете просто посмотреть на его text``checked
свойства и сделать все, что вам нужно.
Если бы пользователь добавил в этот список 100 пунктов, ни один из этих кодов не нуждался бы в изменении. Он одинаково хорошо работает как с пятью предметами, так и с сотней (или тысячей).
Говоря о количестве элементов, теперь вы можете изменить numberOfRowsInSection
его, чтобы возвращать фактическое количество элементов в массиве, а не жестко закодированное число.
BOS Измените tableView(_:numberOfRowsInSection:)
метод на:
override func tableView( _ tableView: UITableView, numberOfRowsInSection section: Int ) -> Int { return items.count }
Мало того, что код стал намного короче и проще для чтения, теперь он может обрабатывать произвольное количество строк. Вот в чем сила массивов!
BOS Запустите приложение и убедитесь в этом сами. Он все равно должен работать точно так же, как и раньше, но внутренняя структура кода намного лучше.
Упражнение: Добавьте в таблицу еще несколько строк. Вам нужно только измениться
viewDidLoad
, чтобы это сработало.
Очистите код
Есть еще несколько вещей, которые вы можете сделать, чтобы улучшить исходный код.
Заменить configureCheckmark(for:at:)
на этот:
func configureCheckmark( for cell: UITableViewCell, with item: ChecklistItem ) { if item.checked { cell.accessoryType = .checkmark } else { cell.accessoryType = .none } }
Теперь вместо индексного пути вы напрямую передаете ChecklistItem
объект методу.
Обратите внимание, что теперь полное имя метода становитсяconfigureCheckmark(for:with:)
, и именно так вы будете вызывать его из других мест в приложении.
Почему вы изменили этот метод? Ранее он получил индексный путь, а затем сделал следующее, чтобы найти соответствующийChecklistItem
:
let item = items[indexPath.row]
Но в обоих cellForRowAt``didSelectRowAt
случаях вы уже это делаете. Таким образом, проще передать этот ChecklistItem
объект напрямуюconfigureCheckmark
, а не заставлять его выполнять одну и ту же работу дважды. Все, что упрощает код, хорошо.
BOS Также добавьте этот новый метод:
func configureText( for cell: UITableViewCell, with item: ChecklistItem ) { let label = cell.viewWithTag(1000) as! UILabel label.text = item.text }
Это устанавливает текст элемента контрольного списка на метке ячейки. Раньше вы делали это, cellForRowAt
но яснее всего поместить это в свой собственный метод.
BOS Обновите tableView(_:cellForRowAt:)
так, чтобы он вызывал эти новые методы:
`override func tableView(
_ tableView: UITableView,
cellForRowAt indexPath: IndexPath
) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(
withIdentifier: «ChecklistItem»,
for: indexPath)
let item = items[indexPath.row]
configureText(for: cell, with: item)
configureCheckmark(for: cell, with: item)
return cell
}`
BOS Также обновлениеtableView(_:didSelectRowAt:)
:
override func tableView( _ tableView: UITableView, didSelectRowAt indexPath: IndexPath ) { if let cell = tableView.cellForRow(at: indexPath) { let item = items[indexPath.row] item.checked.toggle() configureCheckmark(for: cell, with: item) } tableView.deselectRow(at: indexPath, animated: true) }
BOS Запустите приложение. Он все равно должен работать точно так же, как и раньше, но код намного лучше. Теперь у вас могут быть списки с тысячами дел для тех, кто особенно трудолюбив.
Убери этот беспорядок!
Так какой же смысл вносить все эти изменения, если приложение по-прежнему работает точно так же? Во-первых, код намного чище, и это помогает избежать ошибок. Используя массив, вы также сделали код более гибким. Табличное представление теперь может обрабатывать любое количество строк.
Вы обнаружите, что когда вы программируете, вы постоянно перестраиваете свой код, чтобы сделать его лучше. Невозможно сделать все на 100% идеально с самого начала.
Поэтому вы пишете код до тех пор, пока он не станет грязным, а затем очищаете его. Через некоторое время он снова превращается в большой беспорядок, и вы снова его убираете. Процесс очистки кода называется рефакторингом, и это цикл, который никогда не заканчивается.
Есть много программистов, которые никогда не реорганизуют свой код. В результате получается то, что мы называем “спагетти-кодом”, и поддерживать его в рабочем состоянии-ужасный беспорядок.
Если вы не просматривали свой код в течение нескольких месяцев, но вам нужно добавить новую функцию или исправить ошибку, вам может потребоваться некоторое время, чтобы прочитать его, чтобы снова понять, как все сочетается. Эта задача становится намного сложнее, когда у вас есть спагетти-код.
Таким образом, в ваших же интересах написать как можно более чистый код.
Если вы хотите проверить свою работу, вы можете найти файлы проекта для текущей версии приложения в папке 10-The-data-model в папке исходного кода.