在 SwiftUI 中将绑定传递给导航堆栈中每个视图的最佳方法是啥

Posted

技术标签:

【中文标题】在 SwiftUI 中将绑定传递给导航堆栈中每个视图的最佳方法是啥【英文标题】:What is the best way to pass binding to each view in navigation stack in SwiftUI在 SwiftUI 中将绑定传递给导航堆栈中每个视图的最佳方法是什么 【发布时间】:2021-03-24 13:20:30 【问题描述】:

我正在使用 .fullScreenCover 从一个视图转换到另一个视图。呈现的视图有一个导航堆栈,将由几个视图组成。我希望这些视图中的每一个都能够使用 .fullScreenCover 中使用的绑定 isActive 属性来关闭整个堆栈。 我是否需要将此属性绑定到堆栈中的每个视图,或者是否有更简单的方法来执行此操作。我想将此属性添加到导航堆栈的 viewModel 中,但没有成功。这是我想要做的:

struct FirstView: View 
    
    @State var isActive = false
    
    var body: some View 
        Text("Press to present Navigation Stack")
            .onTapGesture 
                isActive = true
            
            .fullScreenCover(isPresented: $isActive, content: 
                SecondView(isActive: $isActive)
            )
    
    

struct SecondView: View 
    
    @StateObject var viewModel: ViewModel = ViewModel()
    
    @Binding var isActive: Bool
    
    @State var showThirdViewIsActive: Bool = false
    
    init(isActive: Binding<Bool>) 
        self._isActive = isActive
        // somehow get the isActive to my ViewModel but no luck
    
    
    var body: some View 
        NavigationView 
            Button(action: 
                $showThirdViewIsActive = true
            , label: 
                Text("Show Third View")
            )
            Button(action: 
                viewModel.dismiss()
            , label: 
                Text("Dismiss")
            )
            NavigationLink(
                destination: ThirdView(viewModel: viewModel, isActive: $isActive),
                isActive: $showThirdViewIsActive) 
        
    


struct ThirdView: View 
    
    @ObservableObject var viewModel: ViewModel
    
    @Binding var isActive: Bool
    
    init(viewModel: ViewModel, isActive: Binding<Bool>) 
        self.viewModel = viewModel
        self._isActive = isActive
        // somehow get the isActive to my ViewModel but no luck
    
    
    var body: some View 
        Button(action: 
            viewModel.dismiss()
        , label: 
            Text("dismiss")
        )
    


class ViewModel: ObservableObject 
    
    @Binding var isActive: Bool
    
    func dismiss() 
        self.isActive = false
    

当然,我可以通过将 isActive 设置为 false 来关闭 SecondView,但我是故意在 ViewModel 中这样做的,因为它不仅适用于这种简单的情况。它可能会在异步调用访问服务器后关闭。

我也刚刚展示了两个视图,但第二个视图中会有 NavigationView,而 NavigationLink 会指向另一个视图,依此类推。它们都将共享相同的 ViewModel。 FirstView 不共享该 ViewModel。

如果可以避免的话,我不想将 isActive 传递给每个视图。我希望能够以某种方式将 isActive 从 SecondView 传递给 viewModel,或者让它成为任何视图都可以访问的某种环境变量。

不确定在我猜测的这种常见情况下的最佳做法是什么。

【问题讨论】:

绑定不是在深层视图层次结构中传递控制的可靠方法,请改用基于 ObservableObject 的视图模型。 是的,但问题是如何将关闭的 isActive 变量放入可观察的 viewModel 【参考方案1】:

我不知道你的项目,我认为你在做什么是理所当然的。

如果你想被模型关闭,模型必须在第一个视图中实现。

struct FirstView: View 
    
    @StateObject var viewModel: ViewModel = ViewModel()
           
    var body: some View 
        Text("Press to present Navigation Stack")
            .onTapGesture 
                viewModel.isActive = true
            
            .fullScreenCover(isPresented: $viewModel.isActive, content: 
                SecondView(viewModel: viewModel)
            )
    
    

struct SecondView: View 
    
    @StateObject var viewModel: ViewModel
    
    @State var showThirdViewIsActive: Bool = false
    

    var body: some View 
        NavigationView 
            VStack 
                Button(action: 
                    showThirdViewIsActive = true
                , label: 
                    Text("Show Third View")
                )
                Button(action: 
                    viewModel.dismiss()
                , label: 
                    Text("Dismiss")
                )
                NavigationLink(
                    destination: ThirdView(viewModel: viewModel),
                    isActive: $showThirdViewIsActive) 
            
            
        
    


struct ThirdView: View 
    
    @StateObject var viewModel: ViewModel

    var body: some View 
        Button(action: 
            viewModel.dismiss()
        , label: 
            Text("dismiss")
        )
    


class ViewModel: ObservableObject 
    
    @Published var isActive: Bool = false
    
    func dismiss() 
        self.isActive = false
    



struct SwiftUIViewTest_Previews: PreviewProvider 
    static var previews: some View 
            FirstView()
    

更新 仅使用@Binding

struct FirstView: View 
     
     @State var isActive: Bool = false
        
     
     var body: some View 
         Text("Press to present Navigation Stack")
             .onTapGesture 
                 isActive = true
             
             .fullScreenCover(isPresented: $isActive, content: 
                 NavigationView 
                    SecondView(isActive: $isActive)
                 
             )
             .onAppear() 
                 isActive = false
             
     
     
 
 struct SecondView: View 
     
     
     @Binding var isActive: Bool
     
     @State var showThirdViewIsActive: Bool = false
     
     var body: some View 
         VStack 
             Button(action: 
                 showThirdViewIsActive = true
             , label: 
                 Text("Show Third View")
             )
             Button(action: 
                 isActive = false
             , label: 
                 Text("Dismiss")
             )
             NavigationLink(
                destination: ThirdView(isActive: $isActive),
                 isActive: $showThirdViewIsActive) 
         
     
 

 struct ThirdView: View 
     
    @Binding var isActive: Bool
     
     var body: some View 
         Button(action: 
             isActive = false
         , label: 
             Text("dismiss")
         )
     
 

【讨论】:

如果我使用这种方法,尽管仅与第二个视图等相关的 ViewModel 会在我不想要的第二个视图被关闭时持续存在。 您可以使用两个模型,一个在第一个视图中移动到另一个视图中,另一个在第二个视图中与您的所有员工一起使用。否则,如果您使用的是 ios 14,则可以使用 @AppStorage @alionthego 通过绑定检查更新

以上是关于在 SwiftUI 中将绑定传递给导航堆栈中每个视图的最佳方法是啥的主要内容,如果未能解决你的问题,请参考以下文章

SwiftUI 导航链接和@State 在“onAppear()”中有问题

SwiftUI 将数组的元素作为绑定传递给子视图

SwiftUI 为啥不能在视图之间传递发布者?

检查后在 SwiftUI 中将可选绑定设置为 nil 时出现异常

SwiftUI:从工作表导航到新视图

SwiftUI - 在导航堆栈中弹回不会取消分配视图