Перевод статьи подготовлен в преддверии старта продвинутого курса «Разработчик iOS».
![](https://habrastorage.org/r/w1560/webt/dy/nh/af/dynhaf1v_qvyw2xcskvtvn3aesa.png)
Здравствуйте и добро пожаловать на наш туториал! В этой серии мы говорим о том, как перемещаться между представлениями в SwiftUI (без использования навигационного представления!). Хоть эта идея и может казаться тривиальной, но, разобравшись в ней поглубже, мы сможем многое узнать о концепциях потоков данных, используемых в SwiftUI.
В предыдущей части мы узнали, как реализовать это, используя
Вот чего мы собираемся достичь:
Итак, мы как раз выяснили, как перемещаться между различными представлениями, используя ObservableObject. В двух словах, мы создали ViewRouter и связали с ним Mother View и наши Content View. Далее мы просто манипулируем свойством CurrentPage ViewRouter, при нажатии на кнопки Content View. После этого MotherView обновляется, отображая соответствующий Content View!
Но есть и второй, более эффективный способ достижения этой функциональности: использование
Подсказка: Вы можете скачать актуальные наработки здесь (это папка «NavigateInSwiftUIComplete»):GitHub
Почему использование
Вы, вероятно, задаетесь вопросом: почему мы должны реализовывать это каким-либо другим способом, когда у нас уже есть рабочее решение? Ну, если взглянете на логику иерархии нашего приложения, то вам станет ясно. Наш MotherView является корневым представлением, которое инициализирует инстанс ViewRouter. В MotherView мы также инициализируем ContentViewA и ContentViewB, передавая им как BindableObject инстанс ViewRouter.
Как видите, мы должны следовать строгой иерархии, которая передает инициализированный ObservableObject по нисходящей всем подпредставлениям. Сейчас это не так важно, но представьте себе более сложное приложение с большим количеством представлений. Мы всегда должны следить за передачей инициализированного Observable корневого представления всем подпредставлениям и всем подпредставлениям подпредставлений и т. д., что в конечном итоге может стать довольно муторным занятием.
![](https://habrastorage.org/r/w780q1/webt/ij/ps/on/ijpson_0gvwhatcpzyosh2eacaq.jpeg)
Подытожим: использование чистого
Вместо этого мы могли бы инициализировать ViewRouter при запуске приложения таким образом, чтобы все представления могли быть напрямую связаны с этим инстансом, или, лучше сказать, наблюдали за ним, безотносительно к иерархии приложения. В этом случае инстанс ViewRouter будет похож на облако, которое пролетает над кодом нашего приложения, к которому все представления автоматически получают доступ, не заботясь о правильной цепочке инициализации вниз по иерархии представления.
Это работа как раз для
Что такое
Как только мы объявили наш
Как уже говорилось,
![](https://habrastorage.org/r/w780q1/webt/ew/fr/9l/ewfr9lhz0xqhodfe5obumjykzoe.jpeg)
Реализация
Итак, давайте обновим код нашего приложения!
Сначала изменим обертку свойства
Свойство
Как уже говорилось выше — при запуске нашего приложения, оно должно быть немедленно снабжено инстансом
Отлично, теперь при запуске приложения SwiftUI создает инстанс
Далее давайте обновим наш
Подсказка: Опять, структура
Давайте повторим это для
Поскольку свойства
Это просто замечательно: нам больше не нужно инициализировать
Отлично, давайте запустим наше приложение и посмотрим, как оно работает.
Отлично, мы все еще можем перемещаться между нашими представлениями!
В качестве бонуса давайте рассмотрим, как добавить анимацию перехода при переходе от «page1» к «page2».
В SwiftUI сделать это достаточно просто.
Посмотрите на
Теперь мы можем добавить анимацию перехода при отображении другого
Чтобы увидеть, как это работает, запустите ваше приложение в обычном симуляторе:
Круто, мы добавили в наше приложение хорошую анимацию перехода всего за несколько строк кода!
Вы можете скачать весь исходный код здесь!
Вот и все! Мы узнали, почему лучше использовать
Бесплатный урок: «Ускорение iOS-приложений с помощью instruments»
![](https://habrastorage.org/webt/dy/nh/af/dynhaf1v_qvyw2xcskvtvn3aesa.png)
Здравствуйте и добро пожаловать на наш туториал! В этой серии мы говорим о том, как перемещаться между представлениями в SwiftUI (без использования навигационного представления!). Хоть эта идея и может казаться тривиальной, но, разобравшись в ней поглубже, мы сможем многое узнать о концепциях потоков данных, используемых в SwiftUI.
В предыдущей части мы узнали, как реализовать это, используя
@ObservableObject
. В этой части мы рассмотрим, как сделать то же самое, но более эффективно, используя @EnvironmentObject. Мы также собираемся добавить небольшую анимацию перехода.Вот чего мы собираемся достичь:
Что мы имеем
Итак, мы как раз выяснили, как перемещаться между различными представлениями, используя ObservableObject. В двух словах, мы создали ViewRouter и связали с ним Mother View и наши Content View. Далее мы просто манипулируем свойством CurrentPage ViewRouter, при нажатии на кнопки Content View. После этого MotherView обновляется, отображая соответствующий Content View!
Но есть и второй, более эффективный способ достижения этой функциональности: использование
@EnvironmentObject
!Подсказка: Вы можете скачать актуальные наработки здесь (это папка «NavigateInSwiftUIComplete»):GitHub
Почему использование ObservableObject
— не лучшее решение
Вы, вероятно, задаетесь вопросом: почему мы должны реализовывать это каким-либо другим способом, когда у нас уже есть рабочее решение? Ну, если взглянете на логику иерархии нашего приложения, то вам станет ясно. Наш MotherView является корневым представлением, которое инициализирует инстанс ViewRouter. В MotherView мы также инициализируем ContentViewA и ContentViewB, передавая им как BindableObject инстанс ViewRouter.
Как видите, мы должны следовать строгой иерархии, которая передает инициализированный ObservableObject по нисходящей всем подпредставлениям. Сейчас это не так важно, но представьте себе более сложное приложение с большим количеством представлений. Мы всегда должны следить за передачей инициализированного Observable корневого представления всем подпредставлениям и всем подпредставлениям подпредставлений и т. д., что в конечном итоге может стать довольно муторным занятием.
![](https://habrastorage.org/webt/ij/ps/on/ijpson_0gvwhatcpzyosh2eacaq.jpeg)
Подытожим: использование чистого
ObservableObject
может стать проблематичным, когда речь идет о более сложных иерархиях приложений.Вместо этого мы могли бы инициализировать ViewRouter при запуске приложения таким образом, чтобы все представления могли быть напрямую связаны с этим инстансом, или, лучше сказать, наблюдали за ним, безотносительно к иерархии приложения. В этом случае инстанс ViewRouter будет похож на облако, которое пролетает над кодом нашего приложения, к которому все представления автоматически получают доступ, не заботясь о правильной цепочке инициализации вниз по иерархии представления.
Это работа как раз для
EnvironmentObject
!Что такое EnvironmentObject
?
EnvironmentObject
— это модель данных, которая после инициализации может обмениваться данными со всеми представлениями вашего приложения. Что особенно хорошо, так это то, что EnvironmentObject
создается путем предоставления ObservableObject
, поэтому мы можем использовать наш ViewRouter
для создания EnvironmentObject
!Как только мы объявили наш
ViewRouter
как EnvironmentObject
, все представления могут быть привязаны к нему так же, как и к обычным ObservableObject
, но без необходимости в цепочке инициализаций вниз по иерархии приложения!Как уже говорилось,
EnvironmentObject
должен быть уже инициализирован при первом обращении к нему. Так как наш MotherView
, как корневое представление, будет смотреть на свойство CurrentPage
ViewRouter
, мы должны инициализировать EnvironmentObject
при запуске приложения. Затем мы можем автоматически изменить currentPage EnvironmentObject
из ContentView
, который затем вызывает MotherView
для повторного рендеринга.![](https://habrastorage.org/webt/ew/fr/9l/ewfr9lhz0xqhodfe5obumjykzoe.jpeg)
Реализация ViewRouter
как EnvironmentObject
Итак, давайте обновим код нашего приложения!
Сначала изменим обертку свойства
viewRouter
внутри MotherView
с @ObservableObject
на @EnvironmentObject
.import SwiftUI
struct MotherView : View {
@EnvironmentObject var viewRouter: ViewRouter
var body: some View {
//...
}
}
Свойство
viewRouter
теперь ищет ViewRouter-EnvironmentObject
. Таким образом, нам нужно предоставить нашей структуре MotherView_Previews
соответствующий инстанс:#if DEBUG
struct MotherView_Previews : PreviewProvider {
static var previews: some View {
MotherView().environmentObject(ViewRouter())
}
}
#endif
Как уже говорилось выше — при запуске нашего приложения, оно должно быть немедленно снабжено инстансом
ViewRouter
в качестве EnvironmentObject
, поскольку MotherView
в качестве корневого представления теперь ссылается на EnvironmentObject
. Поэтому обновите функцию сцены внутри файла SceneDelegage.swift
следующим образом:func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
if let windowScene = scene as? UIWindowScene {
let window = UIWindow(windowScene: windowScene)
window.rootViewController = UIHostingController(rootView: MotherView().environmentObject(ViewRouter()))
self.window = window
window.makeKeyAndVisible()
}
}
Отлично, теперь при запуске приложения SwiftUI создает инстанс
ViewRouter
в качестве EnvironmentObject
, к которому теперь могут быть привязаны все представления нашего приложения.Далее давайте обновим наш
ContentViewA
. Измените его viewRouter
свойство на EnvironmentObject
, а также обновите структуру ContentViewA_Previews
.import SwiftUI
struct ContentViewA : View {
@EnvironmentObject var viewRouter: ViewRouter
var body: some View {
//...
}
}
#if DEBUG
struct ContentViewA_Previews : PreviewProvider {
static var previews: some View {
ContentViewA().environmentObject(ViewRouter())
}
}
#endif
Подсказка: Опять, структура
ContentViewsA_Previews
имеет собственный инстанс ViewRouter
, но ContentViewA
связан с инстансом созданным при запуске приложения!Давайте повторим это для
ContentViewB
:import SwiftUI
struct ContentViewB : View {
@EnvironmentObject var viewRouter: ViewRouter
var body: some View {
//...
}
}
#if DEBUG
struct ContentViewB_Previews : PreviewProvider {
static var previews: some View {
ContentViewB().environmentObject(ViewRouter())
}
}
#endif
Поскольку свойства
viewRouter
наших ContentView
теперь непосредственно связаны с/наблюдают первоначальный инстанс ViewRouter
как EnvironmentObject
, нам больше не нужно инициализировать их в нашей MotherView
. Итак, давайте обновим наш MotherView
:struct MotherView : View {
@EnvironmentObject var viewRouter: ViewRouter
var body: some View {
VStack {
if viewRouter.currentPage == "page1" {
ContentViewA()
} else if viewRouter.currentPage == "page2" {
ContentViewB()
}
}
}
}
Это просто замечательно: нам больше не нужно инициализировать
ViewRouter
внутри нашего MotherView
и передавать его инстанс вниз в ContentView, что может быть очень эффективным, особенно для более сложных иерархий.Отлично, давайте запустим наше приложение и посмотрим, как оно работает.
Отлично, мы все еще можем перемещаться между нашими представлениями!
Добавление анимации перехода
В качестве бонуса давайте рассмотрим, как добавить анимацию перехода при переходе от «page1» к «page2».
В SwiftUI сделать это достаточно просто.
Посмотрите на
willChange
метод, который мы вызываем в файл ViewRouter.swift
когда CurrentPage
обновляется. Как вы уже знаете, это вызывает привязку MotherView
к повторному отображению своего тела, в конечном итоге показывая другой ContentView
, что означает переход к другому ContentView
. Мы можем добавить анимацию, просто обернув метод willChange
в функцию withAnimation
: var currentPage: String = "page1" {
didSet {
withAnimation() {
willChange.send(self)
}
}
}
Теперь мы можем добавить анимацию перехода при отображении другого
Content View
.“withAnimation(_:_:) — возвращает результат повторного вычисления тела представления с предоставленной анимацией”Мы хотим снабдить наше приложение «всплывающим» переходом при навигации от
Apple
ContentViewA
к ContentViewB
. Для этого перейдите в файл MotherView.swift
и добавьте модификатор перехода при вызове ContentViewB
. Вы можете выбрать один из нескольких предустановленных типов переходов или даже создать собственный (но это тема для другой статьи). Для добавления «всплывающего» перехода мы выбираем тип .scale
.var body: some View {
VStack {
if viewRouter.currentPage == "page1" {
ContentViewA()
} else if viewRouter.currentPage == "page2" {
ContentViewB()
.transition(.scale)
}
}
}
Чтобы увидеть, как это работает, запустите ваше приложение в обычном симуляторе:
Круто, мы добавили в наше приложение хорошую анимацию перехода всего за несколько строк кода!
Вы можете скачать весь исходный код здесь!
Заключение
Вот и все! Мы узнали, почему лучше использовать
EnvironmentObject
для навигации между представлениями в SwiftUI, и как это реализовать. Мы также узнали, как добавить в навигацию анимацию перехода. Если вы хотите узнать больше, следите за нами в Instagram и подпишитесь на нашу рассылку, чтобы не пропустить обновления, туториалы и советы по SwiftUI и многому другому!Бесплатный урок: «Ускорение iOS-приложений с помощью instruments»