Как стать автором
Обновить
1302.6
OTUS
Цифровые навыки от ведущих экспертов

Навигация между представлениями с помощью @EnvironmentObject в SwiftUI

Время на прочтение6 мин
Количество просмотров5K
Автор оригинала: BLCKBIRDS
Перевод статьи подготовлен в преддверии старта продвинутого курса «Разработчик iOS».




Здравствуйте и добро пожаловать на наш туториал! В этой серии мы говорим о том, как перемещаться между представлениями в 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 корневого представления всем подпредставлениям и всем подпредставлениям подпредставлений и т. д., что в конечном итоге может стать довольно муторным занятием.



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

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

Что такое EnvironmentObject?


EnvironmentObject — это модель данных, которая после инициализации может обмениваться данными со всеми представлениями вашего приложения. Что особенно хорошо, так это то, что EnvironmentObject создается путем предоставления ObservableObject, поэтому мы можем использовать наш ViewRouter для создания EnvironmentObject!

Как только мы объявили наш ViewRouter как EnvironmentObject, все представления могут быть привязаны к нему так же, как и к обычным ObservableObject, но без необходимости в цепочке инициализаций вниз по иерархии приложения!

Как уже говорилось, EnvironmentObject должен быть уже инициализирован при первом обращении к нему. Так как наш MotherView, как корневое представление, будет смотреть на свойство CurrentPage ViewRouter, мы должны инициализировать EnvironmentObject при запуске приложения. Затем мы можем автоматически изменить currentPage EnvironmentObject из ContentView, который затем вызывает MotherView для повторного рендеринга.



Реализация 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»


Теги:
Хабы:
Всего голосов 7: ↑5 и ↓2+5
Комментарии0

Публикации

Информация

Сайт
otus.ru
Дата регистрации
Дата основания
Численность
101–200 человек
Местоположение
Россия
Представитель
OTUS