Разработка приложений для iOS

Создание приложения для iOS с помощью SwiftUI и объединение с использованием MVVM и протоколов

В этом руководстве мы реализуем взаимодействие между представлениями и будем работать с данными в вашем демонстрационном приложении с помощью новых фреймворков swiftUI и Combine, выпущенных Apple в 2019 году. используйте шаблон MVVM и парадигму протокольно-ориентированного программирования.

В этом руководстве мы используем последнюю версию Xcode (11.3.1) и macOS Catalina (10.15.4) на момент написания.

SwiftUI и Combine были представлены на WWDC 2019 компанией Apple. Эти две платформы меняют ваш способ создания приложений для iOS, делая его более быстрым и удобным, помогая создавать тестируемый и читаемый код. SwiftUI предоставляет декларативный способ создания интерфейсов, ускоряющих разработку, а Combine дополняет SwiftUI (или, конечно, работает отдельно от него) и привносит функциональное реактивное программирование (FRP) в проекты iOS и помогает больше сосредоточиться на логике и разделяет код на отдельные parts упрощает использование MVVM или других парадигм в приложениях iOS.

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

Нашим структурным шаблоном проектирования будет MVVM, шаблон, который разделяет код на три отдельные части: Модель, Представление и Модель представления . Мы увидим, насколько удобно его использовать в приложениях для iOS, использующих SwiftUI и Combine.

В этом руководстве мы будем использовать приложение, разработанное в руководстве Начало работы с SwiftUI и объединение с использованием MVVM и протоколов для iOS, поэтому я настоятельно рекомендую пройти его. В противном случае, если вы не хотите этого делать, вы можете просто загрузить полный код здесь.

Что мы узнаем

К концу этого урока мы узнаем:

  • Как взаимодействовать между представлениями и представлять модальное представление в родительском представлении;
  • Как работают переменные @State и @Published и как они обновляют пользовательский интерфейс приложений iOS;
  • Как SwiftUI View взаимодействует с ViewModel с помощью Combine и как ViewModel взаимодействует с Data Manager;
  • Как создать издателей, отправляющих уведомления (с примером того, как настроить представления при появлении клавиатуры).

Мы продолжим создавать простое приложение со списком дел, которое использует SwiftUI и Combine и работает на MVVM и протоколах. Итак, приступим.

Ссылка на обновленную версию видео на YouTube: https://www.youtube.com/watch?v=NJOOVl6VUB0&list=PLNzBC8jVdb__Wf-ojLhTP7FGj7wFhPc7N&index=2

Начиная

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

Итак, в качестве начального приложения у нас есть приложение со списком дел с 4 основными файлами:

  • Todo.swift - это модель наших текущих задач;
  • DataManager.swift, который хранит и обновляет данные в приложении;
  • TodoListView.swift, содержащий главное представление нашего приложения;
  • TodoListViewModel.swift, который содержит viewModel, который работает с TodoListView.

Если у вас нет опыта работы с SwiftUI и Combine или вы не понимаете какой-либо части этого проекта, пожалуйста, ознакомьтесь с учебником, в котором мы создали это начальное приложение с нуля. Это поможет вам легче понять следующие шаги.

Если мы создадим приложение, мы увидим, что оно не содержит никакой информации, и мы не можем добавить новый Todo. Даже если мы выберем TodoListView и посмотрим на Canvas, мы увидим только список Todos из нашего MockDataManager, и все. Очевидно, нам нужно реализовать интерфейс и логику добавления новых Todos. Давайте сделаем это.

Добавить новый Todo

Во-первых, нам нужно создать протокол для viewModel, который необходим для представления, где мы сможем добавить новый элемент Todo и сам viewModel, как мы это сделали для TodoListView.

NewTodoViewModel

Создайте новый файл Swift с именем NewTodoViewModel.swift. Создайте протокол в файле следующим образом:

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

Давайте создадим NewTodoViewModel и реализуем протокол:

Мы создаем новый класс, инициализируем его с помощью DataManager и реализуем метод addNewTodo (title: String).

Но это не выглядит чистым. Мне не нравится, что нам нужно создавать экземпляр Todo в методе. Почему бы просто не передать title в метод DataManager? Давайте немного реорганизуем наш DataManager. В этом случае нам нужно обновить DataManagerProtocol, а затем обновить реализацию в классах DataManager и MockDataManager:

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

Вы также можете заметить, что в DataManager мы вызываем инициализатор Todo только с одним параметром (Todo (title: String)), потому что id и isCompleted имеют значения по умолчанию. Это делает наш код еще проще.

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

NewTodoView

Создайте новый файл SwiftUI View, назовите его NewTodoView и нажмите Создать. Убедитесь, что холст отображает представление (если холст не открыт, используйте Cmd + Option + Return, чтобы отобразить его, нажмите Возобновите в правом верхнем углу, если вы не видите вид).

Представление будет содержать только три подпредставления: кнопки TextField, Отмена и Добавить. Добавим их в представление:

Вот что мы сделали:

  • Мы добавили viewModel;
  • Мы создали переменную @State title для хранения текста из текстового поля;
  • Мы создали VStack и поместили TextField между двумя Spacer (). Spacer () - это расширяемое пространство, которое расширяется настолько, насколько это возможно, поэтому, если у нас есть представление между двумя из них, представление будет в середине супервизора. TextField имеет два параметра: первый - это текст-заполнитель, а второй - это строковая переменная для хранения текста, введенного пользователем. Мы используем префикс $ для доступа к привязке к переменной состояния;
  • Мы добавили HStack под нижний разделитель (), чтобы кнопки «Отмена» и «Добавить» отображались горизонтально в ряд. Кнопки в основном представляют собой представления Текст и пока ничего не делают.

Что ж, сейчас это выглядит не очень хорошо, так почему бы нам не добавить отступы и не сделать кнопки шире?

Мы добавим модификатор .padding () после закрывающей скобки VStack, чтобы создать отступ вокруг дисплея.

Кроме того, мы добавим к нашим кнопкам два модификатора: .padding (.vertical, 16.0), чтобы сделать кнопки больше по вертикали, и .frame (minWidth: 0, maxWidth: .infinity). , чтобы максимально развернуть обе кнопки по горизонтали и, следовательно, сделать их одинаково широкими:

Так лучше!

Теперь мы добавим действия к нашим кнопкам:

  1. Мы создаем переменную @Environment presentationMode, в которой хранится состояние представления, мы будем использовать ее, чтобы программно отклонить наше представление.
  2. Мы вызываем self.presentationMode.wrappedValue.dismiss () для действия кнопки Отмена, чтобы закрыть представление: мы получаем доступ к обернутому значению переменной и используем метод dismiss (), чтобы закрыть представление.
  3. В действии кнопки Добавить мы проверяем, пуста ли переменная title , и если это не так, мы вызываем метод в viewModel и отклоняем вид.

Помните об использовании переменной presentationMode. Вот как мы обычно закрываем представление в SwiftUI.

Отличная работа! Теперь пора реализовать открытие окна, иначе как мы туда доберемся?

Вернемся к нашему основному виду. В навигации по проекту на левой панели Xcode выберите TodoListView.swift. Отсюда нам нужно открыть NewTodoView, поэтому для этого мы добавим кнопку навигации с правой стороны панели навигации.

  • Сначала создайте переменную @State с именем isShowingAddNew, которая равна false. Этот флаг будет отображать представление, если представлен лист NewTodoView (для TodoListView это лист в SwiftUI);
  • Затем создайте вычисляемую переменную addNewButton типа some View над телом. Переменная будет содержать только кнопку, которая переключает isShowingAddNew на действие и содержит системное изображение плюс;
  • Передайте переменную addNewButton в модификатор navigationBarItems внутри NavigationView, чтобы кнопка отображалась на панели навигации;
  • Наконец, в теле добавьте модификатор .sheet к. NavigationView, который отображается, если isShowingAddNew имеет значение true (он получает значение Binding, поэтому мы передадим переменную с помощью $ для доступа к привязке к переменной состояния). Он представит NewTodoView и обновит Задачи при закрытии.

Теперь ваш TodoListView должен выглядеть так:

Теперь вы можете нажать Предварительный просмотр в правом нижнем углу окна Холст. Подождите, пока он загрузится, а затем попробуйте нажать кнопку Добавить. Будет представлен NewTodoView. Видите ли, вы можете использовать Live Preview, чтобы проверить свои представления, не создавая и не запуская все приложение.

Но в любом случае давайте создадим и запустим приложение. Когда он запустится, нажмите кнопку Добавить в правом верхнем углу. Теперь попробуйте нажать на текстовое поле и начать что-нибудь вводить. Вы видите, что клавиатура скрывает кнопки (если вы используете симулятор, ваша клавиатура может не отображаться, когда вы фокусируетесь на текстовом поле, в этом случае попробуйте Cmd + K показать это).

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

KeyboardResponder

Создайте новый файл Swift с именем KeyboardResponder.swift. Сначала, как обычно, мы создадим KeyboardResponderProtocol. Как вы, возможно, знаете, в UIKit мы использовали уведомления keyboardWillShow и keyboardWillHide, чтобы создать правильную анимацию для наших представлений. Для этого мы получали значения keyboardHeight и animationDuration. Итак, наш протокол будет содержать только две переменные: currentHeight и duration.

После этого мы создадим класс KeyboardResponder, который соответствует KeyboardResponderProtocol и ObservableObject (на этот раз мы не создаем расширение, потому что у нас есть только переменные, а переменные не могут храниться в расширениях (думаю, на этот раз это не имеет большого значения).

Наконец, наш класс будет выглядеть так:

  • Мы создаем две переменные, чтобы соответствовать протоколу, только одна из них - @Published, потому что мы знаем, что эти переменные будут обновлены одновременно, нам не нужно принудительно обновлять представления дважды;
  • Мы создаем переменную cancellableBag для хранения нашего набора AnyCancellable, поэтому мы не будем стирать издателей и убедиться, что мы будем получать уведомления до тех пор, пока не появится экземпляр KeyboardResponder существует;
  • В методе инициализатора мы создаем две переменные, в которых хранятся издатели для keyboardWillShow и keyboardWillHide;
  • Затем мы объединяем наши уведомления с помощью Publishers.Merge, поскольку наши действия с этими уведомлениями одинаковы, и нам не нужно дублировать код;
  • Мы получаем уведомления в основном потоке, так как собираемся обновить пользовательский интерфейс об этих событиях;
  • Мы вызываем раковину, чтобы прикрепить подписчика к издателю, и вызываем keyboardNotification при закрытии;
  • Наконец, мы сохраняем подписчика в переменной cancellableBag, пока существует экземпляр KeyboardResponder.

Метод keyboardNotification почти аналогичен методу, который мы используем в UIKit для обновления UIView, когда клавиатура будет отображена или скрыта. Но мы в основном помещаем продолжительность анимации и высоту клавиатуры в переменные, которые мы создали ранее.

Это все, что нам нужно сделать в классе KeyboardResponder. Теперь нам нужно использовать этот класс в нашем представлении. Выберите NewTodoView.swift в Навигаторе проекта.

Создайте клавиатуру переменной @ObservedObject для нового класса. Чтобы переместить все подвиды в представлении вверх при появлении клавиатуры, нам нужно обновить нижний отступ. Для этого нам нужно добавить модификатор .padding прямо под существующим, но с параметром Edge, равным .bottom, а Параметр length должен быть keyboard.currentHeight. При этом нижний отступ в представлении всегда будет равен высоте клавиатуры (когда клавиатура скрыта, отступ будет равен нулю).

Поскольку мы добавили модификатор нижнего отступа под .padding (), он переопределяет предыдущий модификатор, и эти два модификатора работают правильно.

И в качестве последнего шага добавьте модификатор .animation прямо под тем, который мы только что добавили. Установите анимацию как .easeOut с помощью keyboard.duration. Теперь отступы обновляются с анимацией, и это выглядит хорошо. Вы можете собрать приложение и проверить, как оно работает.

Единственное, что меня сейчас беспокоит, это то, что кнопка Добавить выглядит активной, даже если в TextField нет текста. Почему бы нам не изменить кнопку, когда пользователь что-то вводит?

  • Сначала мы создаем новую переменную с именем isAddButtonDisabled, которая возвращает title.isEmpty. Это может показаться ненужным, но делает код более читабельным;
  • Мы создаем переменную addButtonColor, которая возвращает либо .gray, либо .blue в зависимости от isAddButtonDisabled;
  • Теперь, когда у нас есть эта переменная, мы заменяем оператор if в действии кнопки Добавить;
  • Мы добавляем модификатор .foregroundColor (.black) к Тексту («Добавить»), так как мы хотим использовать синий цвет в качестве фона;
  • Наконец, мы добавляем два модификатора к кнопке Добавить: .background (addButtonColor) и .disabled (isAddButtonDisabled), чтобы обновить цвет кнопки и сделать он включен / отключен.

Полный код должен быть следующим:

Отличная работа! Теперь вы можете создать свое приложение и посмотреть, как меняется кнопка, когда вы вводите что-то в текстовое поле. И если вы наберете заголовок и нажмете кнопку Добавить, вы заметите новую задачу в главном окне! Потрясающие. Теперь у нашего приложения наконец-то есть логика, которая действительно работает.

Если вы перезапустите приложение, вы потеряете все данные, потому что мы не реализовали постоянное хранилище. Мы сделаем это в другом уроке.

Что дальше

Поздравляю! Вы сделали это! Теперь вы разработали приложение, в котором используются SwiftUI и Combine, вы реализовали шаблон MVVM и использовали Протоколы. чтобы сделать ваш код чистым, читаемым и тестируемым. В следующей части мы добавим галочки к строкам, реализуем маркировку Todos как завершенных, реализуем отображение / скрытие завершенных Todos и некоторые другие функции, которые улучшат наше приложение и помогут разработать swiftUI и Комбинируйте знания.

Полный код приложения доступен здесь.

Следующее руководство доступно здесь.

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

  1. Начало работы со SwiftUI и объединение использования MVVM и протоколов для iOS.
  2. Создание приложения для iOS с помощью SwiftUI и объединение с использованием MVVM и протоколов. (это руководство)
  3. Создание приложения для iOS с помощью SwiftUI, Combine, MVVM и протоколов [Часть 3].
  4. Как создать свой собственный фреймворк для вашего приложения iOS.
  5. Использование основных данных в вашем приложении SwiftUI с Combine, MVVM и протоколами.