Автор: Мария Пасхавер

В первой части этой серии вы узнали, как настроить интерфейс приложения списка дел с таблицей для хранения данных и кнопкой добавления.

В части 2 у вас есть тестовые данные для отображения в этой таблице.

В части 3 вы дали пользователю возможность добавлять новые задачи с помощью кнопки Добавить. Однако вы также обнаружили несколько проблем:

  • Вы должны иметь возможность отмечать задачи как выполненные.
  • Вы должны иметь возможность сохранять задачи.
  • Вы не хотите иметь возможность добавлять пустые/пустые ячейки дел.

Если вы не читали предыдущие части этой серии или хотите начать с моего кода заново, клонируйте проект с настроенным интерфейсом и базовыми функциями из моего репозитория GitHub здесь. Я также оставил комментарии в своем коде, чтобы объяснить, какие строки что делают.

В противном случае давайте начнем с проблемы номер один.

Шаг 1. Сделайте задачи доступными для выполнения

До сих пор мы хранили наши задачи в виде строк внутри массива строк toDos в ViewController.swift, классе, который управляет состояниями и поведением контроллера табличного представления в Main.storyboard.

Однако это проблематично, поскольку сами по себе строки не дают нам никакой информации о том, была ли задача выполнена или нет. Другими словами, у нас есть описание/слова задачи, но нигде нельзя пометить ее как выполненную. Как нам решить эту проблему?

Создав класс ToDo.

Помните, что классы — это способы представления объектов реального мира. Задача — это объект. У него есть свойства: описание и маркер, указывающий, завершено оно или нет. Этот маркер будет так называемым логическим значением, значением, которое всегда либо истинно, либо ложно.

Чтобы создать этот класс ToDo, щелкните правой кнопкой мыши на левой боковой панели и выберите «Новый файл». Сделайте его файлом Swift и назовите его ToDo. Такое форматирование UppercaseClassName является стандартным синтаксисом для имен классов в Swift.

Внутри ToDo.swift мы собираемся объявить класс ToDo и дать ему два свойства: String с именем description и Bool (это ключевое слово Swift для логического значения) с именем isComplete. На данный момент мы сделаем каждое описание пустой строкой, а каждый isComplete установим в false, так как вы логически не можете создать задачу для чего-то, что вы уже сделали.

Итак, теперь Swift знает, что у класса ToDo есть свойства. Но как нам создать экземпляр объекта и сообщить ему, чему будет равно его конкретное свойство описания? В конце концов, каждый объект ToDo будет иметь свое описание.

Это делается через функцию инициализатора, и выглядит это так:

init(_ desc: String) {
self.description = desc
}

Это означает, что для заданной строки с именем desc необходимо установить конкретный экземпляр свойства описания этого объекта ToDo в desc. Нет необходимости инициализировать isComplete с каким-либо другим значением, кроме false, поскольку опять же, вы никогда не сможете запустить задачу, которая уже завершена, по крайней мере, в этом приложении.

Теперь вернемся к ViewController.swift. Нам нужно изменить тип нашего массива toDos. Ранее он содержал строки. Теперь он содержит объекты ToDo. Нам нужно это показать.

Для создания объекта используется формат

var имя_объекта = имя_класса (свойство1, свойство2 и т. д.)

Поэтому, когда мы создаем новую задачу, она будет соответствовать формату

var newToDo = ToDo(someString)

где someString может равняться чему угодно, что передал пользователь или что мы придумали, и т. д.

Итак, мы собираемся заменить

var toDos = ["Study Calculus", "Make lunch", "Do laundry"]

с

var toDos = [ToDo("Study Calculus"), ToDo("Make lunch"), ToDo("Do laundry")]

Теперь toDos заполнен объектами ToDo; это массив объектов ToDo, каждый из которых имеет собственное описание и свойства isComplete. Потрясающий!

Там, где мы создаем alertAction, нам нужно изменить некоторые вещи. Мы не можем добавить newToDo к toDos. В конце концов, toDos теперь представляет собой массив ToDo, а не массив String.

Давайте переименуем строковую переменную newToDo в newToDoDescription, чтобы было понятнее, с чем мы имеем дело.

Далее давайте создадим объект newToDo и инициализируем его свойством description объекта newToDoDescription.

Наконец, мы добавим этот новыйToDo в массив toDos.

Вот как теперь выглядит создание alertAction:

let alertAction = UIAlertAction(title: "Done", style: .default, handler: { (action) in
     let newToDoDescription = alert.textFields![0].text!
     let newToDo = ToDo(newToDoDescription)
     self.toDos.append(newToDo)
     self.tableView.reloadData()
})

Большой. Теперь нам нужно исправить нашу функцию cellForRowAt, которая отображает то, что хранится в toDos в tableView ViewController. Помните, как мы присваивали textToDisplay значение String в toDos в indexPath.row? Ну, теперь есть ошибка, потому что textToDisplay в настоящее время содержит объект ToDo, и вы не можете установить текст содержимого ячейки на что-то, что не является строкой.

К счастью, это имеет простое решение. Мы уже узнали, что можем получить доступ к свойствам объектов с помощью записи через точку, поэтому мы просто заменим

let textToDisplay = self.toDos[indexPath.row]

с

let textToDisplay = self.toDos[indexPath.row].description

И если мы запустим наше приложение сейчас, оно все равно будет работать. Мы успешно перешли от использования строк к объектам ToDo, и теперь у нас есть возможность помечать задачи как выполненные. Давайте разберемся, как мы можем это сделать.

Шаг 2. Пометка задач как выполненных

Как мы пометим задачу как завершенную? Допустим, когда пользователь нажимает на ячейку/строку, содержащую эту задачу. Это кажется разумным, верно? Как проверить, коснулся ли пользователь ячейки? К счастью, в UITableViewControllers Swift для этого есть встроенная функция. Его

override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)

Объявим это:

Что нам нужно делать, когда мы нажимаем на ячейку/строку?

  1. Отмените выбор этой ячейки.
  2. Получите соответствующий ToDo из нашего массива toDos.
  3. Измените свойство isComplete этой задачи на противоположное тому, что было раньше (поскольку пользователь может пометить задачу как выполненную случайно, мы хотим дать ему возможность отменить ее).

Перезагрузите tableView ViewController в этой строке, чтобы обновить его новыми данными.

Затем, вернувшись в нашу функцию cellForRowAt, при загрузке ячейки в определенной строке нам нужно будет:

  1. Получите соответствующий ToDo из нашего массива toDos.
  2. Проверьте его свойство isComplete.
  3. На основе его свойства isComplete добавьте что-то вроде галочки в ячейку, чтобы пометить ее как видимую для пользователя.

Итак, давайте сделаем это.

Чтобы отменить выбор ячейки в IndexPath в tableView ViewController, просто вызовите встроенную функцию deselectRow tableView, например:

self.tableView.deselectRow(at: indexPath, animated: false)

Теперь мы собираемся получить то, что нам нужно. Напомним, что эта функция didSelectRowAt предоставляет нам IndexPath с именем indexPath, у которого есть свойство строки, указывающее на ячейку, которая была нажата. Это тот же номер строки, который, если его использовать для индексации в массиве toDos, даст вам правильный объект ToDo, описание которого использовалось для возврата этой ячейки в cellForRowAt.

Таким образом, мы получаем требуемую задачу, набрав это:

let tappedToDo = self.toDos[indexPath.row]

И чтобы изменить его свойство isComplete, мы набираем это:

tappedToDo.isComplete = !tappedToDo.isComplete

! означает «нет». Он переворачивает логическое значение isComplete, которое в настоящее время содержит tappedToDo. В этом есть смысл — логическое значение может быть только истинным или ложным, поэтому !true ложно, а !false истинно. Если isComplete равно true, для него будет установлено значение false, а если isComplete равно false, то для него будет установлено значение true. Хороший.

Теперь мы перезагружаем данные в этом IndexPath с анимацией по умолчанию, набрав

self.tableView.reloadRows(at: [indexPath], with: .automatic)

Теперь наша функция выглядит так, и это хорошо:

В cellForRowAt нам нужно найти способ показать, что задача, представленная ячейкой в ​​IndexPath indexPath, завершена. Во-первых, мы получаем это задание, поэтому заменяем строку

let textToDisplay = self.toDos[indexPath.row].description

с

let toDo = self.toDos[indexPath.row]

Теперь установите content.text в свойство описания задачи вместо уже несуществующего textToDisplay:

content.text = toDo.description

Хороший. Теперь мы можем поставить ячейке галочку, если объект ToDo, который она представляет, завершен, или не ставить галочку, если задача не завершена. Мы можем сделать это, установив для свойства accessType ячейки галочку, только если свойство toDo isComplete имеет значение true.

Сделаем оператор if:

if (toDo.isComplete == true) {
     cell.accessoryType = UITableViewCell.AccessoryType.checkmark
} else {
     cell.accessoryType = UITableViewCell.AccessoryType.none
}

Первая строка, как и ожидалось, обращается к свойству isComplete toDo и проверяет, равно ли оно (с помощью оператора ==) значению true. Если это так, он устанавливает в качестве аксессуара ячейки встроенную галочку Swift для объектов UITableViewCell. Если нет, он устанавливает для ячейки accessType равным нулю. Это внешний вид ячейки по умолчанию.

Примечание. Вы также можете записать их как .checkmark и .none, точно так же, как мы сделали выше с сокращением .automatic, но я хотел показать вам, откуда взялись эти виды значений: Свойства свойств в классах.

Теперь, если мы запустим наше приложение и нажмем на некоторые ячейки, мы увидим, что этот код работает. Попробуй это!

Хороший!

Подведение итогов

Итак, из этой статьи вы узнали:

  • Как представить задачи в виде объектов ToDo с описанием и свойством isComplete и исправить существующие функции для работы с этими изменениями
  • Как использовать свойство isComplete объекта ToDo для переключения галочки в соответствующей ячейке в tableView ViewController

Отличная работа. Если вы думаете, что ваш код запутан или вы допустили ошибку где-то по пути, я вас понял. Сделайте клон моего репозитория GitHub здесь.

Мы все? Ахах нет. Нам все еще нужно сделать наши to-dos сохраняемыми. Это тема пятой части этой серии.

Устали читать? Чувак, представь, сколько времени я потратил на написание этого. Давай, давайте вместе.