Урок 1.3 Model-View-Controller

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

В этом уроке вы узнаете, как организовать файлы, структуры и классы в шаблон проектирования под названием Model-View-Controller, или MVC. MVC поможет вам структурировать файлы в вашем приложении, а также взаимодействия и отношения между различными типами и экземплярами.

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

Помните, что никогда не существует одного правильного ответа на любое архитектурное решение. Существуют лучшие практики, но стиль и личные предпочтения будут играть роль в том, как вы решите организовать проекты вашего приложения и модели отношений.


Что вы изучите:

  • Как использовать паттерн Model-View-Controller (MVC) для проектирования объектов и их взаимосвязей в вашем приложении.
  • Один из распространенных способов организации файлов в ваших проектах Xcode.

Словарь:

  • абстракция (abstraction) — процесс выделения важных характеристик объекта и игнорирования несущественных деталей.
  • архитектура (architecture) — структура и организация компонентов системы.
  • контроллер (controller) — компонент в MVC, который управляет потоком данных между моделью и представлением.
  • модель (model) — компонент в MVC, который управляет данными и бизнес-логикой приложения.
  • Model-View-Controller — паттерн проектирования, который разделяет приложение на три компонента: модель, представление и контроллер.
  • представление (view) — компонент в MVC, который отвечает за отображение данных пользователю.

Связанные ресурсы:

Теперь, когда вы начали создавать более сложные проекты, вы уже многое знаете о том, как различные классы и структуры работают вместе. И вы, вероятно, заметили, что начинают появляться некоторые шаблоны.

Например, вы знаете, что можно использовать методы AppDelegate и SceneDelegate для обработки различных событий в жизненном цикле приложения. Вы знаете, что можно редактировать файл Main.storyboard в Interface Builder, чтобы определять представления и создавать пользовательские интерфейсы. Вы работали с файлами контроллеров представлений, которые определяют, как сцена должна реагировать, когда пользователь нажимает кнопку или переходит между экранами. И вы видели, как определенные классы представляют данные, которые ваше приложение должно отображать.

На данный момент курса вы следовали конкретным инструкциям, которые говорили вам, какие классы или структуры создать и какие свойства или функции добавить к ним. Но до этого кто-то должен был решить, как все части будут работать вместе. Как бы вы решили, какие новые классы или структуры создавать? Как узнать, какие свойства должны быть? Или какие объекты должны вызывать функции на других объектах?

Это отличные вопросы. И, задавая их, вы переходите от просто выполнения инструкций к программированию, где необходимо принимать решения.

Десятилетиями разработчики программного обеспечения изучали и практиковали различные способы ответа на эти вопросы. По мере того как разработка программного обеспечения развивалась, появились несколько общепринятых шаблонов проектирования. Один из наиболее распространенных шаблонов — это Model-View-Controller, или MVC. MVC присваивает объектам одну из трех ролей — модель, представление или контроллер — и помогает определить, как объекты взаимодействуют друг с другом.

Диаграмма ниже предоставляет обзор того, как эти три типа объектов работают по отношению друг к другу. Каждый слой или тип имеет определенные роли и рекомендации по взаимодействию с другими слоями.

Прежде чем пытаться понять, как работают взаимодействия, следует понять, за что отвечает каждый тип объекта. Читая описания ниже, обратитесь к диаграмме и рассмотрите взаимодействия между типами объектов.

Модели (Models)

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

Объекты модели могут быть связаны с другими объектами модели. Например, объект модели для автомобиля может иметь свойство водителя, или у персонажа игры может быть массив предметов инвентаря.

В большинстве случаев вы будете создавать объекты модели, определяя структуры или классы в своем проекте. Обычно вы будете определять структуры или классы в новых Swift-файлах.

Объекты модели состоят из свойств, которые представляют атрибуты типа, и иногда имеют методы для обновления и изменения своих собственных свойств.

Взаимодействие

Объекты модели обычно создаются в ответ на какое-либо взаимодействие пользователя с представлением или элементом управления. Сообщение о создании объекта модели передается от представления через объект контроллера, который чаще всего является подклассом контроллера представления. Тем не менее, объекты модели не должны иметь прямой связи с представлениями, как это показано на предыдущей диаграмме.

Представления (Views)

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

Представления могут быть переиспользованы или перенастроены для отображения различных экземпляров данных модели. Например, объект представления в приложении "Контакты" может использоваться для отображения информации о любом контакте, а представление в приложении "Почта" — для показа любого сообщения.

Представления (Views) часто имеют функцию обновления update или настройки configure, которая принимает объект модели в качестве параметра; представление использует объект модели для обновления своего состояния, чтобы соответствовать данным, которые оно должно отображать. Например, представьте ячейку таблицы, отображающую фотографию игрока вместе с текущим счетом игрока. Представление может иметь метод update(_:), который назначает изображение игрока для отображения и текущий счет игрока для текстовой метки.

При создании iOS-приложения вы обычно определяете слой представления в Interface Builder. Затем представления могут быть связаны или использоваться в классе контроллера представления в виде аутлетов или действий.

Взаимодействие

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

Контроллеры

Объект контроллера действует как посредник между представлениями и объектами модели. Например, когда пользователь взаимодействует с представлением, представление отправляет сообщение контроллеру представления, и контроллер представления может обновить объект модели. Или, наоборот, когда объект модели создается или обновляется, контроллер представления может попросить своё представление перерисовать или обновить себя с новыми данными.

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

Существуют также два других типа контроллеров: контроллеры модели и вспомогательные контроллеры.

Контроллеры модели

Подобно тому, как контроллер представления управляет представлением, контроллер модели помогает управлять объектом модели или коллекцией объектов модели. Существует три распространенные причины, по которым вы можете захотеть создать контроллер модели:

  1. Несколько объектов или сцен нуждаются в доступе к данным модели.
  2. Логика добавления, изменения или удаления данных модели является сложной.
  3. Вы хотите, чтобы код в ваших контроллерах представления был сосредоточен на управлении представлениями.

Например, представьте, что вы работаете над простым приложением "Заметки", которое синхронизирует заметки пользователя с сервером. В приложении есть две сцены: представление списка, которое отображает все заметки в виде таблицы, и представление деталей, которое позволяет пользователю создавать новую заметку или читать и редактировать уже существующую заметку.

Если бы вы использовали только контроллеры представления, то контроллер представления списка взял бы на себя основную часть работы в приложении — не только управление представлением списка, но и управление всеми данными модели, включая получение данных заметок с сервера, добавление новых заметок, замену измененных заметок, удаление заметок и сохранение всех изменений на сервере.

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

Использование контроллеров модели не является обязательным; на самом деле, это нечасто практикуется в небольших проектах. Однако абстрагирование становится крайне важным при работе над более крупными проектами или при работе с другими разработчиками, так как это делает ваш код проще, более читаемым и легким в поддержке.

Вспомогательные контроллеры

Вспомогательные контроллеры полезны в тех случаях, когда вы хотите объединить связанные данные или функциональность, чтобы другие объекты в вашем приложении могли к ним обращаться. Обычным примером вспомогательного контроллера является NetworkController, который управляет всеми сетевыми запросами в данном приложении.

Контроллеры должны выполнять очень конкретную функцию, не завися при этом от других контроллеров для выполнения своей работы. Например, NoteListViewController может отображать список заметок, а NoteDetailViewController — отображать детали конкретной заметки и реагировать на ввод пользователя для изменения или создания заметки.

Взаимодействие

Помимо посредничества в общении между моделями и представлениями, объекты контроллеров могут также взаимодействовать или работать напрямую с другими объектами контроллеров. Рассмотрим приведенные выше примеры. Начальный контроллер представления приложения для заметок (NoteListViewController) может быть ответственен за отображение списка заметок, поэтому он обращается к свойству notes в контроллере модели заметок (NoteController). Контроллер модели заметок должен проверить, есть ли новые заметки, поэтому он дает команду сетевому контроллеру (NetworkController) проверить веб-сервис на наличие новых данных. Если NetworkController находит новые заметки, он загружает данные и отправляет их обратно в NoteController, который затем обновляет свое свойство notes и отправляет обратный вызов в NoteListViewController о том, что у него есть новые данные, что позволяет NoteListViewController обновить свое представление списка заметок.

Пример

Представьте, что вам поручено создать приложение для отслеживания фотографий блюд, которые пользователь съел, с возможностью добавления заметок и оценки каждого блюда. Какие объекты модели вам понадобятся? Какие представления? Какие контроллеры?

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

Модель

Если приложение будет отслеживать блюда, логично создать объект модели под названием Meal, который будет содержать данные о каждом блюде. Каждое блюдо должно иметь имя, а также — согласно вашему описанию функционала — фотографию, заметки и оценку.

Затем вам нужно будет подумать о том, как ваши объекты модели будут отображаться в приложении, и ваши решения повлияют на свойства, которые необходимо сохранить в объектах модели. Например, если вы хотите убедиться, что список блюд отображается в порядке их добавления, возможно, вам нужно будет включить временную метку на каждом объекте Meal.

Ваш объект модели может быть объявлен как структура или класс с пятью свойствами:

struct Meal {
    var name: String
    var photo: UIImage
    var notes: String
    var rating: Int
    let timestamp: Date
}

Представление

Можно представить, что приложению потребуется как минимум два представления или сцены. Точно так же, как в примере с заметками, одна сцена будет отображать список всех блюд, которые отслеживаются приложением, а другая сцена будет отображать детали каждого блюда.

Сцена списка блюд может отображать список в таблице, где имя и фотография каждого блюда отображаются в отдельной ячейке. Сцена деталей блюда может затем отображать имя блюда в строке навигации, фотографию в элементе image view, заметки о блюде в текстовом поле и текущую оценку в segmented control.

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

В большинстве случаев вы будете использовать storyboard для определения ваших представлений в Interface Builder. Вы назначите каждому представлению класс контроллера представления, в котором сможете добавить действия или привязки (outlets), чтобы программно реагировать на взаимодействие с пользователем и обновлять представление.

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

Контроллер

Как минимум, вам понадобится контроллер для списка блюд и контроллер для деталей блюда. Для списка блюд вам следует использовать UITableViewController, который отображает данные в таблице, а для контроллера деталей блюда — UIViewController. Некоторая часть настройки таблицы, которая будет описана далее, может показаться вам незнакомой, но не беспокойтесь — вы изучите UITableViewController и настройку таблиц в одном из будущих уроков.

Контроллер таблицы списка блюд

class MealListTableViewController: UITableViewController {...}

Контроллер MealListTableViewController должен иметь свойство, которое хранит коллекцию блюд для отображения. Свойства, которые вы добавляете в подкласс контроллера таблицы, — в данном случае MealListTableViewController — должны отражать все, что делает его отличным от обычного UITableViewController.

class MealListTableViewController: UITableViewController {
 
    var meals: [Meal] = []
}

Поскольку коллекция блюд хранится в MealListTableViewController, контроллер должен обрабатывать добавление и удаление блюд из коллекции. Один из подходов — напрямую модифицировать массив, но вы также можете определить функции, которые добавляют или удаляют блюда из массива.

Когда пользователь завершает работу с приложением, он ожидает, что все данные о блюдах, которые он только что ввел или изменил, будут сохранены. В этом примере MealListTableViewController является посредником между представлением и моделью данных блюд, поэтому он должен взять на себя ответственность за сохранение данных на диск, а также за загрузку данных при запуске приложения и перед отображением представления.

class MealListTableViewController: UITableViewController {
 
    var meals: [Meal] = []
 
    func saveMeals() {...}
 
    func loadMeals() {...}
}

Контроллер представления также должен реагировать на любые действия пользователя. Какие события может обрабатывать представление списка?

Обычно в представлениях списков пользователю разрешается выбирать элемент, чтобы отобразить более подробную информацию о нём. Вы можете добавить эту функциональность, добавив переход (segue) от сцены представления списка к сцене детального просмотра в storyboard. Также вам потребуется добавить и реализовать функцию prepare, которая передаст выбранное блюдо в сцену детального просмотра.

Когда всё будет сделано, ваш контроллер представления, вероятно, будет выглядеть примерно так:

class MealListTableViewController: UITableViewController {
 
    var meals: [Meal] = []
 
    override func viewDidLoad() {
        super.viewDidLoad()
        // Load the meals and set up the table view
    }
 
    // Required table view methods
 
    override func tableView(_ tableView: UITableView,
       numberOfRowsInSection section: Int) -> Int {...}
 
    override func tableView(_ tableView: UITableView,
       cellForRowAt indexPath: IndexPath) -> UITableViewCell {...}
 
    // Navigation methods
 
    @IBSegueAction func showMealDetails(_ coder: NSCoder) ->
       MealDetailViewController?
        // Initialize MealDetailViewController with selected Meal
           and return
    }
 
    @IBAction func unwindToMealList(sender: UIStoryboardSegue) {
        // Capture the new or updated meal from the
           MealDetailViewController and save it to the meals
           property
    }
 
    // Persistence methods
 
    func saveMeals() {
        // Save the meals model data to the disk
    }
 
    func loadMeals() {
        // Load meals data from the disk and assign it to the
           meals property
    }
}

После того как вы спланировали необходимые функции для контроллера представления списка, подумайте о контроллере представления деталей. Как эти два контроллера будут взаимодействовать друг с другом?

Контроллер отображения деталей блюда (Meal Detail View Controller)

class MealDetailViewController: UIViewController {...}

MealDetailViewController будет отображать детали конкретного блюда. Вы можете использовать Interface Builder для настройки представлений, которые будут отображать название, изображение, заметки и оценку блюда, а затем создать привязки (outlets) к MealDetailViewController. Ваш контроллер представления также должен иметь свойство для хранения данных о блюде, которое будет отображаться.

Предоставление кастомного инициализатора и использование @IBSegueAction для отображения контроллера деталей из MealListTableViewController позволяет гарантировать, что модель будет предоставлена, так что вам не придется беспокоиться о необходимости обработки опциональных значений.

class MealDetailViewController: UIViewController {
 
    var meal: Meal
 
    @IBOutlet var nameTextField: UITextField!
    @IBOutlet var photoImageView: UIImageView!
    @IBOutlet var ratingControl: RatingControl!
    @IBOutlet var saveButton: UIBarButtonItem!
 
    init?(coder: NSCoder, meal: Meal) {
        self.meal = meal
        super.init(coder: coder)
    }
 
    override func viewDidLoad() {
        super.viewDidLoad()
        // Update view components using the Meal model
    }
}

MealDetailViewController должен управлять обновлением отображаемого представления с информацией о модельном объекте (блюде), которое он должен отображать. Часто это можно сделать прямо в методе viewDidLoad(), но вы можете захотеть вынести этот процесс в отдельный метод, особенно если модель может изменяться. Контроллер представления деталей может также иметь дополнительные методы, позволяющие пользователю добавить фото или изменить другие свойства блюда. В таких сценариях ему потребуется способ определить, отображает ли он существующее блюдо или создаёт новое.

Учитывая все задачи, которые выполняет контроллер представления деталей, его окончательный вид может выглядеть следующим образом:

class MealDetailViewController: UIViewController, UIImagePickerControllerDelegate {
 
@IBOutlet var nameTextField: UITextField!
@IBOutlet var photoImageView: UIImageView!
@IBOutlet var ratingControl: RatingControl!
@IBOutlet var saveButton: UIBarButtonItem!
 
var meal: Meal
 
init?(coder: NSCoder, meal: Meal) {
    self.meal = meal
    super.init(coder: coder)
}
 
override func viewDidLoad() {
    super.viewDidLoad()
    // Update view components using the Meal model
}
 
// Navigation methods
 
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
    // Update the meal property that will be accessed by the
       MealListTableViewController to update the list of meals
}
 
@IBAction func cancel(_ sender: UIBarButtonItem) {
    // Dismiss the view without saving the meal
}

Напоминание

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

Когда разработчики размышляют о реализации MVC, у каждого из них есть свой уникальный стиль и шаблоны. Так как вы проходите этот курс, вы, скорее всего, будете следовать шаблонам, используемым здесь. Но по мере того, как вы будете расти и набираться опыта, у вас появятся собственные предпочтения относительно того, как организовывать объекты в ваших приложениях.

Вы также сформируете своё собственное мнение о преимуществах и рекомендациях, связанных с паттерном проектирования MVC. Для дальнейшего руководства изучайте примеры кода и предыдущие проекты. И не бойтесь обратиться к кому-то с большим опытом за наставничеством в вопросах лучшего подхода к архитектуре приложения.

Организация проекта

Проекты в Xcode быстро становятся перегруженными. Даже небольшие приложения могут содержать десятки или сотни файлов, определяющих различные представления, сториборды, структуры, классы, протоколы и контроллеры. Написание навигабельного кода — важная часть создания любой программы. Если не быть осторожным, ваш проект может быстро стать неорганизованным, что затруднит продуктивную работу в дальнейшем.

Первый шаг в организации вашего проекта — создание чётких, описательных имён файлов. Например, ProductListTableViewController.swift намного более информативно, чем MainVC.swift. Описательные имена для классов, структур, протоколов, свойств и функций также помогут вам оставаться продуктивными. Некоторые разработчики беспокоятся, что им придётся снова и снова писать длинные имена, но функция автодополнения в Xcode упрощает этот процесс, так что короткие имена не дают никакого преимущества.

Ещё одной хорошей практикой является создание отдельных файлов для каждого из ваших определений типов, таких как классы, структуры, протоколы и перечисления. Например, создайте отдельные файлы Car.swift, Driver.swift, RaceTrack.swift и Garage.swift, вместо одного объединённого файла Model.swift.

Ещё один совет: пишите код так, как будто его будут читать совершенно посторонние люди. Возможно, вам кажется, что это важно только при работе с другими людьми, но это также относится и к коду, который будете видеть только вы. Когда через шесть месяцев вам понадобится пересмотреть свой код, вы оцените описательные имена, чёткие сигнатуры функций и подробные комментарии.

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

Многие разработчики создают группы для следующих элементов:

  • Контроллеры представлений (View controllers)
  • Представления (Views)
  • Модели (Models)
  • Контроллеры моделей (Model controllers)
  • Другие контроллеры (Other controllers)
  • Протоколы (Protocols)
  • Расширения (Extensions)
  • Ресурсы (Resources)
    • Сториборды (Storyboards)
    • Фреймворки (Frameworks)

Пока что вы можете оставить ваши файлы AppDelegate, SceneDelegate и Main на верхнем уровне и использовать более короткий список групп  1  :

  • Models (Модели)
  • Other controllers (Другие контроллеры)
  • Storyboards (Сториборды)
  • View controllers (Контроллеры представлений)
  • Views (Представления)

Стоит отметить, что способ организации вашего проекта в Xcode не всегда меняет способ, которым файлы сохраняются в каталогах проекта; в зависимости от того, как создаются группы, это может повлиять только на то, как файлы отображаются непосредственно в Xcode. В Xcode это обозначается двумя разными иконками папок. Группа, которая используется для логической организации информации в вашем проекте, но не представляет собой папку, которую вы увидите в Finder, обозначается затемненным нижним левым углом иконки папки.

При создании групп в навигаторе Xcode вы увидите два варианта: «New Group» и «New Group without Folder». Последний не создает папку на диске — это просто визуальная организация внутри Xcode.

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

Лабораторная работа — Любимые атлеты

Цель

В этой лабораторной работе вам предстоит спланировать и создать приложение, используя правильный дизайн MVC. Ваше приложение будет состоять из двух экранов для отображения любимых атлетов пользователя. Оно позволит пользователю добавлять новых атлетов в список и редактировать существующие записи.

В папке с ресурсами для студентов откройте стартовый проект под названием «FavoriteAthlete». Приложение уже настроено с использованием таблицы для отображения списка атлетов и формы для сбора информации об отдельном атлете.

Шаг 1

Составьте план

  • На основе описания приложения подумайте над следующими вопросами и запишите свои ответы в краткий план:
    • Какие модельные объекты вам понадобятся для этого приложения?
    • Какие свойства понадобятся для модельных объектов?
    • Кроме контроллера представления, который уже предоставлен в стартовом проекте, какие еще контроллеры вам понадобятся?
    • Какие свойства и функции понадобятся контроллерам?
  • Вы составили план дальнейших действий? Ваш дизайн может немного отличаться от шагов в этой лабораторной работе, и это нормально. В проектировании приложений не существует единственно правильного способа делать вещи. Но для целей этого проекта вы будете использовать следующую модель и контроллеры:
    • Модель Athlete будет хранить информацию о спортсмене через свойства, такие как имя, возраст, лига и команда.
    • AthleteTableViewController будет обрабатывать представление, связанное с отображением списка любимых спортсменов пользователя.
    • AthleteFormViewController будет обрабатывать представления и ввод данных пользователем, связанные с вводом и редактированием информации о спортсмене.

Шаг 2

Изучите стартовый проект

  • Посмотрите на контроллеры представлений в сториборде. Вы увидите контроллер представления таблицы, который имеет два сегвея к форме. Один сегвей имеет идентификатор AddAthlete и начинается с кнопки "Add" (Добавить). Другой сегвей имеет идентификатор EditAthlete и начинается с ячейки таблицы.
  • Форма имеет кнопку "Save" (Сохранить), которую вы будете использовать для вызова обратного сегвея, чтобы вернуть пользователя к представлению таблицы.
  • Подкласс контроллера представления таблицы уже был создан и установлен как AthleteTableViewController. В данный момент вы увидите ошибку в этом файле. Это связано с тем, что он ссылается на тип Athlete, который еще не был создан.

Шаг 3

Создание модели

  • Создайте новый файл Swift с именем "Athlete".
  • В этом файле создайте структуру Athlete, которая будет иметь следующие свойства: name, age, league и team.
  • Добавьте вычисляемое свойство description типа String, которое будет использовать эти четыре свойства для возврата фразы, описывающей спортсмена, как в следующем примере:
var description: String {
    return "\(name) is \(age) years old and plays for the \
    (team) in the \(league)."
}

Шаг 4

Создание и реализация подкласса View Controller

  • Создайте новый файл класса Cocoa Touch, который наследуется от UIViewController, и назовите его "AthleteFormViewController". В интерфейсе сториборда используйте инспектор Identity, чтобы установить класс сцены формы на ваш новый подкласс.
  • Добавьте переменное свойство athlete типа Athlete? в ваш класс AthleteFormViewController. Почему эта переменная является опциональной? Она будет равна nil, когда пользователь перейдет на экран для создания нового спортсмена, и будет иметь значение, когда пользователь перейдет на экран для редактирования информации о спортсмене.
  • Создайте пользовательский инициализатор с сигнатурой init?(coder: NSCoder, athlete: Athlete?). Присвойте self.athlete значение вашей переменной экземпляра и вызовите реализацию super. Устраните ошибку компиляции, которую это вызовет, используя предложение Xcode по исправлению.
  • Создайте метод updateView() и вызовите его в методе viewDidLoad().
  • Добавьте аутлеты из сториборда ко всем текстовым полям в классе AthleteFormViewController. В том же файле добавьте действие для кнопки "Сохранить".
  • В методе updateView() разверните свойство athlete и проверьте, содержит ли оно объект типа Athlete. Если это так, используйте объект для обновления текстовых полей с информацией о спортсмене.
  • Создайте @IBAction для кнопки "Сохранить".
  • Внутри этого действия создайте объект Athlete, развернув текст из текстовых полей и передав его в инициализатор Athlete, следующим образом:
guard let name = nameTextField.text,
    let ageString = ageTextField.text,
    let age = Int(ageString),
    let league = leagueTextField.text,
    let team = teamTextField.text else {return}
 
    athlete = Athlete(name: name, age: age, league: league,
    team: team)

Шаг 5

Завершение реализации AthleteTableViewController

  • В начальном проекте для этой лабораторной работы большая часть класса AthleteTableViewController уже реализована. Позже вы научитесь самостоятельно настраивать таблицу. На данный момент вам просто нужно создать функциональность для информирования следующего контроллера представления о том, какой спортсмен был выбран, и для получения информации о добавленном или отредактированном спортсмене.
  • Создайте @IBSegueAction для сегвея AddAthlete, перетащив Control-drag от стрелки сегвея между двумя контроллерами в AthleteTableViewController. Назовите его addAthlete без аргументов. Сделайте то же самое для сегвея EditAthlete, назвав действие editAthlete и установив Arguments в Sender.
  • В методе addAthlete создайте экземпляр и верните новый экземпляр AthleteFormViewController.
  • Когда вызывается метод editAthlete, параметр sender будет представлять собой ячейку, на которую нажали. Поскольку вы еще не изучили таблицы, следующий шаг может показаться сложным, но попробуйте следовать объяснению. Ваши объекты Athlete хранятся в массиве athletes в контроллере представления таблицы. Индекс каждого спортсмена в массиве соответствует индексу ячейки таблицы, отображающей этого спортсмена. UITableView предоставляет метод для поиска IndexPath ячейки, который можно использовать для поиска соответствующего спортсмена в вашем массиве athletes.
  • В методе editAthlete разверните sender и приведите его к UITableViewCell, затем используйте метод indexPath(for:) из UITableView, чтобы получить IndexPath для ячейки. Метод indexPath возвращает опциональное значение, поэтому вы можете включить этот вызов в свою логику разворачивания. Как только у вас будет indexPath, вы можете получить доступ к его свойству row, которое соответствует индексу спортсмена в вашем массиве athletes. Ваша реализация может выглядеть следующим образом:
let athleteToEdit: Athlete?
if let cell = sender as? UITableViewCell,
   let indexPath = tableView.indexPath(for: cell) {
    athleteToEdit = athletes[indexPath.row]
} else {
    athleteToEdit = nil
}
 
return AthleteFormViewController(coder: coder, athlete:
   athleteToEdit)
  • Далее вам нужно будет настроить контроллер представления таблицы для получения объекта Athlete из AthleteFormViewController в случае, если был добавлен новый спортсмен или отредактирован существующий. Вы сделаете это с помощью "unwind segue". В AthleteTableViewController добавьте метод @IBAction, который принимает параметр типа UIStoryboardSegue. Внутри тела этого метода вам сначала нужно будет привести исходный контроллер представления сегвея к AthleteFormViewController и развернуть его свойство athlete с помощью оператора guard.
  • Какой indexPath, если такой был, был выбран до перехода к AthleteFormViewController? Вы можете использовать свойство indexPathForSelectedRow у представления таблицы в сочетании с условным связыванием, чтобы узнать путь. Если возвращенный indexPath успешно разворачивается, то возвращаемый объект Athlete — это отредактированный спортсмен, а не новый. Вам нужно будет использовать indexPath, чтобы заменить существующего спортсмена в массиве athletes. Если indexPath не удалось развернуть, то возвращаемый объект Athlete — это новый спортсмен, в этом случае вы можете просто добавить нового спортсмена в конец массива athletes. Вот как это все работает:
guard
    let athleteFormViewController =
       segue.source as? AthleteFormViewController,
    let athlete = athleteFormViewController.athlete
else {
    return
}
 
if let selectedIndexPath = tableView.indexPathForSelectedRow {
    athletes[selectedIndexPath.row] = athlete
} else {
    athletes.append(athlete)
}

Шаг 6

Выполните Unwind Segue в Storyboard

  • Наконец, вам нужно создать unwind segue. В storyboard, удерживая Control, перетащите мышью от сцены формы спортсмена в панели структуры документа (Document Outline) к Exit контроллера представления, затем выберите ваш unwind segue. Присвойте этому segue имя, выбрав его в панели структуры документа и добавив идентификатор в инспекторе атрибутов (Attributes Inspector).
  • Теперь вам нужно указать segue, когда он должен выполняться в AthleteFormViewController. Сделайте это, вызвав метод performSegue(withIdentifier:sender:) в конце метода @IBAction кнопки "Сохранить", передав в него идентификатор unwind segue и self в качестве отправителя (sender).
  • Теперь запустите приложение и проверьте, можете ли вы добавлять и редактировать своих любимых спортсменов.

Поздравляем! Вы спланировали и создали простое приложение, используя принципы MVC. Обязательно сохраните вашу работу в папке проекта.

 

 

 

 

 


Отрывок из книги
Develop in Swift Data Collections
Apple Education
https://itunes.apple.com/WebObjects/MZStore.woa/wa/viewBook?id=1581183203
Этот материал может быть защищен авторским правом.

Information

Apple, the Apple logo, Apple Books, Apple TV, Apple Watch, Cocoa, Cocoa Touch, Finder, Handoff, HealthKit, iPad, iPad Pro, iPhone, iPod touch, Keynote, Mac, macOS, Numbers, Objective-C, Pages, Photo Booth, Safari, Siri, Spotlight, Swift, tvOS, watchOS, and Xcode are trademarks of Apple Inc., registered in the U.S. and other countries. App Store and iBooks Store are service marks of Apple Inc., registered in the U.S. and other countries. ​
The Bluetooth® word mark and logos are registered trademarks owned by Bluetooth SIG, Inc. and any use of such marks by Apple is under license. ​
IOS is a trademark or registered trademark of Cisco in the U.S. and other countries and is used under license. ​
Other product and company names mentioned herein may be trademarks of their respective companies.