Navegando entre visualizações usando @EnvironmentObject no SwiftUI

A tradução do artigo foi preparada às vésperas do lançamento do curso avançado "iOS Developer" .




Olá e bem-vindo ao nosso tutorial! Nesta série, falamos sobre como navegar entre visualizações no SwiftUI (sem usar uma visualização de navegação!). Embora essa ideia possa parecer trivial, mas entendendo-a um pouco mais profundamente, podemos aprender muito sobre os conceitos de fluxo de dados usados ​​no SwiftUI.

Na parte anterior, aprendemos como implementar isso usando @ObservableObject. Nesta parte, veremos como fazer o mesmo, mas com mais eficiência usando o @EnvironmentObject. Também vamos adicionar uma pequena animação de transição.

Aqui está o que vamos alcançar:


O que nós temos


Então, acabamos de descobrir como navegar entre diferentes visualizações usando o ObservableObject. Em poucas palavras, criamos um ViewRouter e o associamos à Mother View e à Content View. Em seguida, simplesmente manipulamos a propriedade CurrentPage ViewRouter clicando nos botões Exibição de conteúdo. Depois disso, o MotherView é atualizado para exibir a Visualização de Conteúdo correspondente!

Mas existe uma segunda maneira, mais eficiente, de obter essa funcionalidade: use @EnvironmentObject!

Dica: você pode baixar os desenvolvimentos mais recentes aqui (esta é a pasta "NavigateInSwiftUIComplete") : GitHub

Por que o uso ObservableObjectnão é a melhor solução


Você provavelmente está se perguntando: por que devemos implementar isso de outra maneira, quando já temos uma solução funcional? Bem, se você olhar para a lógica da hierarquia de nosso aplicativo, ela ficará clara para você. Nosso MotherView é a visualização raiz que inicializa a instância do ViewRouter. No MotherView, também inicializamos o ContentViewA e o ContentViewB, passando a instância ViewRouter como um BindableObject.

Como você pode ver, devemos seguir uma hierarquia estrita que passa um ObservableObject inicializado para todas as subvisões. Agora, isso não é tão importante, mas imagine uma aplicação mais complexa com muitas visualizações. Sempre precisamos acompanhar a transmissão da visão raiz observável inicializada para todas as sub-visões e todas as sub-visões de sub-visões, etc., o que pode se tornar uma tarefa bastante tediosa.



Resumindo: o uso de limpeza ObservableObjectpode ser problemático quando se trata de hierarquias de aplicativos mais complexas.

Em vez disso, poderíamos inicializar o ViewRouter quando o aplicativo iniciar, para que todas as visualizações possam ser conectadas diretamente a essa instância, ou melhor, assisti-la, independentemente da hierarquia do aplicativo. Nesse caso, a instância do ViewRouter parecerá uma nuvem que sobrevoa o código do nosso aplicativo, ao qual todas as visualizações acessam automaticamente sem se preocupar com a cadeia de inicialização correta na hierarquia da visualização.
Este é apenas o trabalho EnvironmentObject!

O que é EnvironmentObject?


EnvironmentObjectÉ um modelo de dados que, após a inicialização, pode trocar dados com todas as representações do seu aplicativo. O que é especialmente bom é o que é EnvironmentObjectcriado pelo fornecimento ObservableObject, para que possamos usar o nosso ViewRouterpara criar EnvironmentObject!

Assim que declarou a nossa ViewRoutercomo EnvironmentObject, todas as representações podem ser anexado a ele da mesma forma que os normais ObservableObject, mas sem a necessidade de uma cadeia de inicializações para baixo na hierarquia aplicação!

Como já mencionado, ele EnvironmentObjectjá deve ser inicializado na primeira vez em que é acessado. Como a nossa MotherView, como visualização raiz, examinará a propriedade CurrentPage ViewRouter, precisamos inicializá-la EnvironmentObjectquando o aplicativo for iniciado. Então podemos mudar automaticamente currentPageEnvironmentObjectde ContentView, que então requer MotherViewnova renderização.



Implementação ViewRoutercomoEnvironmentObject


Então, vamos atualizar o código do nosso aplicativo!

Primeiro, altere o invólucro da propriedade viewRouterdentro MotherViewde de @ObservableObjectpara @EnvironmentObject.

import SwiftUI

struct MotherView : View {
@EnvironmentObject var viewRouter: ViewRouter
var body: some View {
//...
    }
}

A propriedade está viewRouterolhando agora ViewRouter-EnvironmentObject. Portanto, precisamos fornecer à nossa estrutura MotherView_Previewsa instância apropriada:

#if DEBUG
struct MotherView_Previews : PreviewProvider {
    static var previews: some View {
        MotherView().environmentObject(ViewRouter())
    }
}
#endif

Como mencionado acima - ao iniciar nosso aplicativo, ele deve ser imediatamente fornecido com uma instância ViewRouterem qualidade EnvironmentObject, já que MotherViewagora se refere à representação raiz EnvironmentObject. Portanto, atualize a função de cena dentro do arquivo da SceneDelegage.swiftseguinte maneira:

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()
        }
    }

Ótimo, agora quando você inicia o aplicativo, o SwiftUI cria uma instância ViewRouterem qualidade EnvironmentObject, à qual todas as visualizações do nosso aplicativo agora podem ser anexadas.

Em seguida, vamos atualizar o nosso ContentViewA. Altere sua viewRouterpropriedade para EnvironmentObjecte também atualize a estrutura 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

Dica: Novamente, a estrutura ContentViewsA_Previewspossui sua própria instância ViewRouter, mas ContentViewAestá associada à instância criada quando o aplicativo foi iniciado!

Vamos repetir isso para 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

Como viewRouternossas propriedades ContentViewagora estão diretamente relacionadas / observam a instância inicial ViewRoutercomo EnvironmentObject, não precisamos mais inicializá-las na nossa MotherView. Então, vamos atualizar nosso MotherView:

struct MotherView : View {
    
    @EnvironmentObject var viewRouter: ViewRouter
    
    var body: some View { 
        VStack {
            if viewRouter.currentPage == "page1" {
                ContentViewA()
            } else if viewRouter.currentPage == "page2" {
                ContentViewB()
            }
        }
    }
}

Isso é ótimo: não precisamos mais inicializar ViewRouterdentro da nossa MotherViewe passar sua instância para o ContentView, que pode ser muito eficiente, especialmente para hierarquias mais complexas.

Ótimo, vamos executar nosso aplicativo e ver como ele funciona.


Ótimo, ainda podemos nos mover entre nossos pontos de vista!

Adicionando animações de transição


Como um bônus, vamos ver como adicionar uma animação de transição ao alternar de "página1" para "página2".

No SwiftUI, isso é bastante simples.
Veja o willChangemétodo que chamamos de arquivo ViewRouter.swiftquando CurrentPageatualizado. Como você já sabe, isso faz com que um vínculo MotherViewexiba novamente seu corpo, mostrando outro ContentView, o que significa mudar para outro ContentView. Podemos adicionar animação simplesmente envolvendo o método willChangeem uma função withAnimation:

var currentPage: String = "page1" {
        didSet {
            withAnimation() {
                willChange.send(self)
            }
        }
    }

Agora podemos adicionar uma animação de transição ao exibir outra Content View.
“WithAnimation (_: _ :) - retorna o resultado de recalcular o corpo da exibição com a animação fornecida”
Apple
Queremos fornecer ao nosso aplicativo uma transição pop-up ao navegar de ContentViewApara ContentViewB. Para fazer isso, vá para o arquivo MotherView.swifte adicione o modificador de transição quando chamado ContentViewB. Você pode escolher um dos vários tipos de transição predefinidos ou até criar o seu próprio (mas este é um tópico para outro artigo). Para adicionar uma transição pop-up, selecionamos um tipo .scale.

var body: some View {
        VStack {
            if viewRouter.currentPage == "page1" {
                ContentViewA()
            } else if viewRouter.currentPage == "page2" {
                ContentViewB()
                    .transition(.scale)
            }
        }
    }

Para ver como ele funciona, execute seu aplicativo em um simulador regular:


Legal, adicionamos uma boa animação de transição ao nosso aplicativo em apenas algumas linhas de código!

Você pode baixar todo o código fonte aqui !

Conclusão


Isso é tudo! Aprendemos por que é melhor usar EnvironmentObjectpara navegar entre visualizações no SwiftUI e como implementá-lo. Também aprendemos como adicionar animações de transição à navegação. Se você quiser saber mais, siga-nos no Instagram e assine a nossa newsletter para não perder atualizações, tutoriais e dicas sobre o SwiftUI e muito mais!



Lição gratuita: “Acelerando aplicativos iOS com instrumentos”



All Articles