在SwiftUI中使用@EnvironmentObject在视图之间导航

本文的翻译是在高级课程“ iOS Developer”启动前夕进行的




您好,欢迎来到我们的教程!在本系列中,我们讨论如何在SwiftUI中的视图之间进行导航(不使用导航视图!)。尽管这个想法看似微不足道,但是通过更深入地了解它,我们可以学到很多关于SwiftUI中使用的数据流概​​念的知识。

在上一部分中,我们学习了如何使用来实现这一点@ObservableObject在这一部分中,我们将研究如何使用@EnvironmentObject进行相同的操作,但是效率更高。我们还将添加一些过渡动画。

这是我们要实现的目标:


我们有什么


因此,我们只是想出了如何使用ObservableObject在不同视图之间导航。简而言之,我们创建了一个ViewRouter并将其与Mother View和Content View关联。接下来,我们只需单击“内容视图”按钮来操作CurrentPage ViewRouter属性。之后,MotherView将更新为显示相应的内容视图!

但是还有第二种更有效的方法来实现此功能:use @EnvironmentObject

提示:您可以在此处下载最新动态(这是“ NavigateInSwiftUIComplete”文件夹)GitHub

为什么使用ObservableObject不是最佳解决方案


您可能想知道:当我们已经有了一个可行的解决方案时,为什么还要以其他方式实现这一目标呢?好吧,如果您查看我们应用程序层次结构的逻辑,这对您将变得很清楚。我们的MotherView是用于初始化ViewRouter实例的根视图。在MotherView中,我们还初始化ContentViewA和ContentViewB,并将ViewRouter实例作为BindableObject传递给它们。

如您所见,我们必须遵循严格的层次结构,该结构将已初始化的ObservableObject下游传递给所有子视图。现在,这并不是那么重要,但是可以想象一个具有很多视图的更复杂的应用程序。我们始终需要跟踪已初始化的Observable根视图到所有子视图和子视图的所有子视图等的传输,这最终可能是一个相当繁琐的任务。



总结一下:ObservableObject在涉及更复杂的应用程序层次结构时,使用clean 可能会出现问题。

取而代之的是,我们可以在应用程序启动时初始化ViewRouter,以便所有视图都可以直接连接到该实例,或者更确切地说,可以监视它,而不管应用程序的层次结构如何。在这种情况下,ViewRouter实例将看起来像云一样,飞越我们应用程序的代码,所有视图都将自动访问该云,而无需担心视图层次结构下正确的初始化链。
这只是工作EnvironmentObject

什么EnvironmentObject


EnvironmentObject是一个数据模型,初始化后可以与应用程序的所有表示交换数据。EnvironmentObject通过提供创造的东西尤其有益ObservableObject,因此我们可以用我们ViewRouter的创造EnvironmentObject

只要将我们的声明ViewRouter为as EnvironmentObject,所有视图就可以附加到普通视图,也可以附加到普通视图ObservableObject,但无需在应用程序层次结构中进行一系列初始化!

如前所述,它EnvironmentObject应该在第一次访问时已经初始化。由于我们的MotherView作为根视图将查看该属性CurrentPage ViewRouter,因此我们必须EnvironmentObject在应用程序启动时对其进行初始化。然后我们可以自动更改currentPageEnvironmentObjectContentView,然后需要MotherView重新渲染。



实施ViewRouterEnvironmentObject


因此,让我们更新应用程序的代码!

首先,将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将创建一个ViewRouterquality 实例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具有自己的instance 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现在与初始实例直接相关,因此我们不再需要在初始实例ViewRouterEnvironmentObject对其进行初始化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.swiftCurrentPageMotherViewContentViewContentViewwillChangewithAnimation

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

现在,我们可以在显示另一个动画时添加一个过渡动画Content View
“ WithAnimation(_:_ :)-返回使用提供的动画重新计算视图主体的结果”
Apple
从导航ContentViewA时,我们希望为我们的应用程序提供一个弹出式过渡ContentViewB为此,请转到文件,MotherView.swift并在调用时添加transition修饰符ContentViewB您可以选择几种预定义的过渡类型之一,甚至可以创建自己的过渡类型(但这是另一篇文章的主题)。要添加弹出式过渡,我们选择一个type .scale

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

要查看其工作原理,请在常规模拟器中运行您的应用程序:


太酷了,我们仅用几行代码就为我们的应用程序添加了一个不错的过渡动画!

您可以在此处下载所有源代码

结论


就这样!我们了解了为什么最好EnvironmentObject在SwiftUI中的视图之间导航以及如何实现它。我们还学习了如何将过渡动画添加到导航中。如果您想了解更多信息,请在Instagram上关注我们并订阅我们的新闻通讯,以免错过SwiftUI的更新,教程和技巧以及更多内容!



免费课程:“使用乐器加速iOS应用”



All Articles