На этом этапе курса вы, вероятно, начинаете чувствовать себя более уверенно в том, как работают приложения и как создавать основные функции приложений. Вы видели, что даже относительно простые приложения зависят от множества файлов, структур и классов. Представьте, как код приложения среднего размера может охватывать сотни файлов в вашем проекте.
В этом уроке вы узнаете, как организовать файлы, структуры и классы в шаблон проектирования под названием Model-View-Controller, или MVC. MVC поможет вам структурировать файлы в вашем приложении, а также взаимодействия и отношения между различными типами и экземплярами.
MVC — это непростая тема. Этот урок поможет вам начать, но вы будете продолжать изучать и укреплять понимание концепций MVC в течение долгого времени. Вы узнаете тонкости реализации правильных шаблонов MVC в вашем приложении из примеров кода, создания собственных приложений, а также от наставников и преподавателей.
Помните, что никогда не существует одного правильного ответа на любое архитектурное решение. Существуют лучшие практики, но стиль и личные предпочтения будут играть роль в том, как вы решите организовать проекты вашего приложения и модели отношений.
Что вы изучите:
- Как использовать паттерн Model-View-Controller (MVC) для проектирования объектов и их взаимосвязей в вашем приложении.
- Один из распространенных способов организации файлов в ваших проектах Xcode.
Словарь:
- абстракция (abstraction) — процесс выделения важных характеристик объекта и игнорирования несущественных деталей.
- архитектура (architecture) — структура и организация компонентов системы.
- контроллер (controller) — компонент в MVC, который управляет потоком данных между моделью и представлением.
- модель (model) — компонент в MVC, который управляет данными и бизнес-логикой приложения.
- Model-View-Controller — паттерн проектирования, который разделяет приложение на три компонента: модель, представление и контроллер.
- представление (view) — компонент в MVC, который отвечает за отображение данных пользователю.
Связанные ресурсы:
- О разработке приложений с UIKit
- Отображение и управление представлениями с помощью контроллера представлений
Теперь, когда вы начали создавать более сложные проекты, вы уже многое знаете о том, как различные классы и структуры работают вместе. И вы, вероятно, заметили, что начинают появляться некоторые шаблоны.
Например, вы знаете, что можно использовать методы AppDelegate
и SceneDelegate
для обработки различных событий в жизненном цикле приложения. Вы знаете, что можно редактировать файл Main.storyboard
в Interface Builder, чтобы определять представления и создавать пользовательские интерфейсы. Вы работали с файлами контроллеров представлений, которые определяют, как сцена должна реагировать, когда пользователь нажимает кнопку или переходит между экранами. И вы видели, как определенные классы представляют данные, которые ваше приложение должно отображать.
На данный момент курса вы следовали конкретным инструкциям, которые говорили вам, какие классы или структуры создать и какие свойства или функции добавить к ним. Но до этого кто-то должен был решить, как все части будут работать вместе. Как бы вы решили, какие новые классы или структуры создавать? Как узнать, какие свойства должны быть? Или какие объекты должны вызывать функции на других объектах?
Прежде чем пытаться понять, как работают взаимодействия, следует понять, за что отвечает каждый тип объекта. Читая описания ниже, обратитесь к диаграмме и рассмотрите взаимодействия между типами объектов.
Модели (Models)
Объект модели группирует данные, необходимые для представления элементов или концепций. Эти элементы или концепции могут быть уникальными для вашего приложения, например, персонаж в игре или задача в списке дел, или они могут представлять объекты реального мира, такие как человек и его контактная информация в адресной книге, или товар, который нужно приобрести в магазине.
Объекты модели могут быть связаны с другими объектами модели. Например, объект модели для автомобиля может иметь свойство водителя, или у персонажа игры может быть массив предметов инвентаря.
В большинстве случаев вы будете создавать объекты модели, определяя структуры или классы в своем проекте. Обычно вы будете определять структуры или классы в новых Swift-файлах.
Объекты модели состоят из свойств, которые представляют атрибуты типа, и иногда имеют методы для обновления и изменения своих собственных свойств.
Взаимодействие
Объекты модели обычно создаются в ответ на какое-либо взаимодействие пользователя с представлением или элементом управления. Сообщение о создании объекта модели передается от представления через объект контроллера, который чаще всего является подклассом контроллера представления. Тем не менее, объекты модели не должны иметь прямой связи с представлениями, как это показано на предыдущей диаграмме.
Представления (Views)
Вы уже узнали, что представления отвечают за визуальные аспекты пользовательского интерфейса. Объект представления знает, как отобразить себя на экране и может реагировать на действия пользователя. Основная цель объектов представления — отображать данные о моделях приложения и позволять пользователю редактировать эти данные.
Представления могут быть переиспользованы или перенастроены для отображения различных экземпляров данных модели. Например, объект представления в приложении "Контакты" может использоваться для отображения информации о любом контакте, а представление в приложении "Почта" — для показа любого сообщения.
Представления (Views) часто имеют функцию обновления update
или настройки configure
, которая принимает объект модели в качестве параметра; представление использует объект модели для обновления своего состояния, чтобы соответствовать данным, которые оно должно отображать. Например, представьте ячейку таблицы, отображающую фотографию игрока вместе с текущим счетом игрока. Представление может иметь метод update(_:)
, который назначает изображение игрока для отображения и текущий счет игрока для текстовой метки.
При создании iOS-приложения вы обычно определяете слой представления в Interface Builder. Затем представления могут быть связаны или использоваться в классе контроллера представления в виде аутлетов или действий.
Взаимодействие
Хотя представления обычно используются для отображения данных о моделях приложения, они никогда не должны владеть объектом модели в качестве свойства или напрямую изменять объект модели. Вместо этого представление может отправить сообщение — например, о том, что пользователь совершил действие — контроллеру представления, и контроллер обновит объект модели.
Контроллеры
Объект контроллера действует как посредник между представлениями и объектами модели. Например, когда пользователь взаимодействует с представлением, представление отправляет сообщение контроллеру представления, и контроллер представления может обновить объект модели. Или, наоборот, когда объект модели создается или обновляется, контроллер представления может попросить своё представление перерисовать или обновить себя с новыми данными.
Фактически, контроллер представления является самым распространенным типом контроллера для начинающих разработчиков iOS. Как вы уже узнали, контроллер представления управляет представлением вместе с его дочерними представлениями и обычно отображает информацию об одном или нескольких объектах модели. Объект модели или коллекция объектов модели обычно определяются как свойство в контроллере представления. Когда пользователь взаимодействует с представлением или элементом управления, запускается действие или блок кода на контроллере представления, который затем может обновить объект модели.
Существуют также два других типа контроллеров: контроллеры модели и вспомогательные контроллеры.
Контроллеры модели
Подобно тому, как контроллер представления управляет представлением, контроллер модели помогает управлять объектом модели или коллекцией объектов модели. Существует три распространенные причины, по которым вы можете захотеть создать контроллер модели:
- Несколько объектов или сцен нуждаются в доступе к данным модели.
- Логика добавления, изменения или удаления данных модели является сложной.
- Вы хотите, чтобы код в ваших контроллерах представления был сосредоточен на управлении представлениями.
Например, представьте, что вы работаете над простым приложением "Заметки", которое синхронизирует заметки пользователя с сервером. В приложении есть две сцены: представление списка, которое отображает все заметки в виде таблицы, и представление деталей, которое позволяет пользователю создавать новую заметку или читать и редактировать уже существующую заметку.
Если бы вы использовали только контроллеры представления, то контроллер представления списка взял бы на себя основную часть работы в приложении — не только управление представлением списка, но и управление всеми данными модели, включая получение данных заметок с сервера, добавление новых заметок, замену измененных заметок, удаление заметок и сохранение всех изменений на сервере.
Вместо этого вы можете решить абстрагировать, или перенести, весь код, который управляет заметками, в отдельный контроллер модели, называемый 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
Этот материал может быть защищен авторским правом.