Разработка приложений для 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). , чтобы максимально развернуть обе кнопки по горизонтали и, следовательно, сделать их одинаково широкими:
Так лучше!
Теперь мы добавим действия к нашим кнопкам:
- Мы создаем переменную @Environment presentationMode, в которой хранится состояние представления, мы будем использовать ее, чтобы программно отклонить наше представление.
- Мы вызываем self.presentationMode.wrappedValue.dismiss () для действия кнопки Отмена, чтобы закрыть представление: мы получаем доступ к обернутому значению переменной и используем метод dismiss (), чтобы закрыть представление.
- В действии кнопки Добавить мы проверяем, пуста ли переменная 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 и Комбинируйте знания.
Полный код приложения доступен здесь.
Следующее руководство доступно здесь.
Это руководство является второй частью серии по созданию приложения со списком дел. Чтобы проверить остальные части, воспользуйтесь следующими ссылками:
- Начало работы со SwiftUI и объединение использования MVVM и протоколов для iOS.
- Создание приложения для iOS с помощью SwiftUI и объединение с использованием MVVM и протоколов. (это руководство)
- Создание приложения для iOS с помощью SwiftUI, Combine, MVVM и протоколов [Часть 3].
- Как создать свой собственный фреймворк для вашего приложения iOS.
- Использование основных данных в вашем приложении SwiftUI с Combine, MVVM и протоколами.