【问题标题】:Pushing multiple navigation links from a parent view in SwiftUI从 SwiftUI 中的父视图推送多个导航链接
【发布时间】:2021-01-27 10:08:08
【问题描述】:

我想实现一个向导,用户必须通过多个屏幕才能完成注册过程。

在 SwiftUI 中,最简单的方法是让每个视图在完成后将下一个视图推送到导航堆栈上,但这会将视图之间的整个导航编码在视图本身中,我想避免它。

我想做的是让父视图显示导航视图,然后在该导航视图上推送不同的步骤。

我已经有一些看起来像这样的工作:

struct AddVehicleView: View {
    @ObservedObject var viewModel: AddVehicleViewModel

    var body: some View {
        NavigationView {
            switch viewModel.state {
            case .description:
                AddDescriptionView(addDescriptionViewModel: AddVehicleDescriptionViewModel(), addVehicleViewModel: viewModel)
            case .users:
                AddUsersView(viewModel: AddUsersViewModel(viewModel.vehicle), addVehicleViewModel: viewModel)
            }
        }
    }
}

这很好用。在第一步中,AddVehicleViewModel 使用必要的信息更新,AddVehicleView 被重新评估,switch case 跳转到下一个选项,并显示下一个视图以完成向导。

然而,这个问题是没有导航堆栈动画。视图只是被替换。如何将其更改为推送视图的系统,而不在 AddDescriptionView 对象内实现推送?

我是否应该编写包装视图,在这些视图之上进行导航堆栈处理,并摆脱开关盒?

【问题讨论】:

  • 不确定我是否理解你,但可能你只需要带有标签/选择构造函数的 NavigationLink。
  • 我想做上面的 switch case,但它应该推送视图而不是替换它们
  • 这是否回答了您的问题stackoverflow.com/a/59692268/12299030
  • 不,因为这需要一次实例化所有视图。如果第一个视图尚未完成生成一些要传递给它的数据,我将无法实例化第二个视图。使用 VStack 并包含所有视图将运行构造函数。
  • @Hariprasad 我的结论是 SwiftUI 在处理导航方面很糟糕。我将它用作布局工具。我编写了一个通用的 UIViewController 子类,它使用 UIHostingController 来显示 SwiftUI 视图,我使用的每个视图都是该子类。每个视图都定义了自己的导航协议。子类实现该协议,子类被我的 ViewModel 弱引用,我通过 ViewModel 将所有导航任务委托给该 UIViewController。因此视图布局和控件由 SwiftUI 处理,但呈现/推送/弹出是通过良好的旧可靠 UIKit 完成的。

标签: swiftui


【解决方案1】:

好的,所以如果你想从视图 a 转到 b,你应该在 NavigationView 中而不是在 NavigationView 之后的视图中实现它,这样你就不会破坏动画。为什么?好问题,我真的不知道。如果可能,我将 NavigationView 始终保留在 WindowGroup 下的 App 结构中。

回到正题。基本上,您的步骤和 NavigationView 之间应该有一个中间视图。此视图 (StepperView) 将包含您的步骤的导航逻辑。这样您就可以保持动画完好无损。

import SwiftUI

class AddVehicleViewModel: ObservableObject {
    
    enum StateType {
        case description
        case users1
        case users2
    }
    
    
    @Published var state: StateType? = nil
    
}

struct AddDescriptionView: View {

    @ObservedObject var viewModel: AddVehicleViewModel
    
    @State var text: String = ""
    var body: some View {
        GeometryReader {proxy in
            VStack {
                TextField("test", text: self.$text).background(RoundedRectangle(cornerRadius: 10).fill(Color.white).frame(width: 150, height: 40)).padding()
                Button("1") {
                    viewModel.state = .users1
                }
            }.frame(width: proxy.size.width, height: proxy.size.height, alignment: .center).background(Color.orange)
        }
    }
}

struct AddUsersView: View {

    @ObservedObject var viewModel: AddVehicleViewModel
    
    
    var body: some View {
        GeometryReader {proxy in
            ZStack {
                Button("2") {
                    viewModel.state = .users2
                }
            }.frame(width: proxy.size.width, height: proxy.size.height, alignment: /*@START_MENU_TOKEN@*/.center/*@END_MENU_TOKEN@*/).background(Color.orange)
        }
    }
}

struct AddUsersView2: View {

    @ObservedObject var viewModel: AddVehicleViewModel
    
    
    var body: some View {
        GeometryReader {proxy in
            ZStack {
                Button("3") {
                    viewModel.state = .description
                }
            }.frame(width: proxy.size.width, height: proxy.size.height, alignment: /*@START_MENU_TOKEN@*/.center/*@END_MENU_TOKEN@*/).background(Color.orange)
        }
    }
}

struct StepperView: View {
    
    @ObservedObject var viewModel: AddVehicleViewModel = AddVehicleViewModel()
    
    var body: some View {
        VStack {
            NavigationLink(
                destination: AddDescriptionView(viewModel: viewModel),
                isActive: .constant(viewModel.state == .description),
                label: {EmptyView()})
            if viewModel.state == .users1 {
                NavigationLink(
                    destination: AddUsersView(viewModel: viewModel),
                    isActive: .constant(true),
                    label: {EmptyView()})
            }
            if viewModel.state == .users2 {
                NavigationLink(
                    destination: AddUsersView2(viewModel: viewModel),
                    isActive: .constant(true),
                    label: {EmptyView()})
            }
        }.onAppear {
            viewModel.state = .description
        }
    }
}

class BackBarButtonItem: UIBarButtonItem {
    @available(iOS 14.0, *)
    override var menu: UIMenu? {
        set {
            // Don't set the menu here
            // super.menu = menu
        }
        get {
            return super.menu
        }
    }
}

struct AddVehicleView: View {
    @ObservedObject var viewModel: AddVehicleViewModel = AddVehicleViewModel()
    
    var body: some View {
        NavigationView {
            NavigationLink(
                destination: StepperView(),
                isActive: .constant(true),
                label: {EmptyView()})
        }
    }
}

【讨论】:

  • 这个解决方案有同样的问题。由于您使用的是 VStack,所有这些 AddUserView/AddDescriptionView 结构都会同时实例化,这是应该避免的,因为除非第一个视图完成,否则无法构建第二和第三个视图。
  • 你可以在 vstack 中添加 if 语句,我已经更新了代码。但我认为它们同时被实例化并不重要。因为视图 ​​a 中可观察视图模型的变化也应该在视图 b 中可见
猜你喜欢
  • 1970-01-01
  • 2020-04-09
  • 2020-05-28
  • 2022-01-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多