ios 仅跨NavigationStack中的特定视图共享ViewModel

nzkunb0c  于 2023-07-01  发布在  iOS
关注(0)|答案(1)|浏览(133)

我有一个NavigationStack,里面有三个窗体,每个窗体都有几个屏幕。如何只跨屏幕共享表单的ViewModel,而不必全局创建示例(在ContentView内部)?目前,无论是否使用,都会创建每个ViewModel。这是我的代码

struct ContentView: View {

    @StateObject private var pathStore = PathStore()

    @StateObject var formOneVM = FormOneViewModel()
    @StateObject var formTwoVM = FormTwoViewModel()

    var body: some View {

        NavigationStack(path: $pathStore.path) {

            HomeView()
            .navigationDestination(FormOneRoutes.self){ route in
                switch route{
                    case .screenOne:
                        FormOneScreenOne()
                    case .screenTwo:
                        FormOneScreenTwo()
            }
            .navigationDestination(FormTwoRoutes.self){ route in
                switch route{
                    case .screenOne:
                        FormTwoScreenOne()
                    case .screenTwo:
                        FormTwoScreenTwo()
        }
    }
    .environmentObject(formOneVM)
    .environmentObject(formTwoVM)
}

我试过将每个表单放在自己的NavigationStack中,但嵌套的NavigationStack似乎不适合我。如果你有嵌套的NavigationStack工作或有其他替代方案,请分享。

yhuiod9q

yhuiod9q1#

一个视图模型的例子,只有在进入一个特定的表单层次结构时才创建。首先使用navigationDestination,然后使用navigationLink和binding:

class PathStore: ObservableObject {
    @Published var path: NavigationPath = NavigationPath()
    func gotoToTop() {
        path.removeLast()
    }
}

class FormOneViewModel: ObservableObject {
    @Published var oneDone = false
    @Published var twoDone = false
}

class FormTwoViewModel: ObservableObject {
    // view one entries
    @Published var entry11 = false
    @Published var entry12 = false
    
    // view two entries
    @Published var entry21 = false
    @Published var entry22 = false
}

enum FormOneRoutes: Hashable {
    case screenOne
    case screenTwo

}

enum FormTwoRoutes: Hashable {
    case screenOne
    case screenTwo
}

struct ContentView: View {
    
    @StateObject private var pathStore = PathStore()
    
    var body: some View {
        
        NavigationStack(path: $pathStore.path) {
            HomeView()
            // here just to start first view of first model
                .navigationDestination(for: FormOneRoutes.self){ route in
                    FormOneScreenOne()
                }
            // here just to start first view of second model
                .navigationDestination(for: FormTwoRoutes.self){ route in
                    FormTwoScreenOne()
                }
        }
        .environmentObject(pathStore)
    }
}

struct HomeView: View {
    var body: some View {
        VStack {
            NavigationLink(value: FormOneRoutes.screenOne) {
                Text("Form one")
            }

            NavigationLink(value: FormTwoRoutes.screenOne) {
                Text("Form Two")
            }

        }
    }
}

struct FormOneScreenOne: View {
    // model only exists while in first form hierachy
    @StateObject var formVM = FormOneViewModel()
    var body: some View {
        VStack {
            Text("FormOneScreenOne \(formVM.oneDone ? "1" : "-") \(formVM.twoDone ? "2" : "-")")
            Toggle("One", isOn: $formVM.oneDone)
            NavigationLink {
                FormOneScreenTwo(formVM: formVM)
            } label: {
                Text("-> Form one screen two")
            }
        }
    }
}

struct FormOneScreenTwo: View {
    @ObservedObject var formVM: FormOneViewModel
    @EnvironmentObject var pathStore: PathStore
    var body: some View {
        VStack {
            Text("FormOneScreenTwo \(formVM.oneDone ? "1" : "-") \(formVM.twoDone ? "2" : "-")")
            Toggle("Two", isOn: $formVM.twoDone)
            Button {
                pathStore.gotoToTop()
            } label: {
                Text("Done")
            }
        }
    }
}

enum YesNo {
    case yes
    case no
    case undefined
}

struct FormTwoScreenOne: View {
    // model only exists while in second form hierachy
    @StateObject var formVM = FormTwoViewModel()
    @State var yesNo1: YesNo = .undefined
    @State var yesNo2: YesNo = .undefined
    var allDefined: Bool {
        yesNo1 != .undefined && yesNo2 != .undefined
    }
    @State var allEntriesDone: Bool = false
    var body: some View {
        VStack {
            Text("FormTwoScreenOne \(yesNo1 == .undefined ? "--" : (formVM.entry11 ? "+1" : "-1")) \(yesNo2 == .undefined ? "--" : ( formVM.entry12 ? "+2" : "-2"))")
            Picker("Choose entry 1", selection: $yesNo1) {
                Text("Yes").tag(YesNo.yes)
                Text("No").tag(YesNo.no)
            }
            Picker("Choose entry 2", selection: $yesNo2) {
                Text("Yes").tag(YesNo.yes)
                Text("No").tag(YesNo.no)
            }
        }
        .onChange(of: yesNo1) { newValue in
            set(value: newValue, for: $formVM.entry11)
        }
        .onChange(of: yesNo2) { newValue in
            set(value: newValue, for: $formVM.entry12)
        }
        .navigationDestination(isPresented: $allEntriesDone) {
            FormTwoScreenTwo(formVM: formVM)
        }
    }
    
    func set(value: YesNo, for bind: Binding<Bool>) {
        switch value {
            case .yes:
                bind.wrappedValue = true
            case .no:
                bind.wrappedValue = false
            case .undefined:
                break
        }
        allEntriesDone = allDefined
    }
}

struct FormTwoScreenTwo: View {
    @EnvironmentObject var pathStore: PathStore
    @ObservedObject var formVM: FormTwoViewModel
    @State var yesNo1: YesNo = .undefined
    @State var yesNo2: YesNo = .undefined
    var allDefined: Bool {
        yesNo1 != .undefined && yesNo2 != .undefined
    }
    var body: some View {
        VStack {
            Text("FormTwoScreenTwo \(yesNo1 == .undefined ? "--" : (formVM.entry21 ? "+1" : "-1")) \(yesNo2 == .undefined ? "--" : ( formVM.entry22 ? "+2" : "-2"))")
            Picker("Choose entry 1", selection: $yesNo1) {
                Text("Yes").tag(YesNo.yes)
                Text("No").tag(YesNo.no)
            }
            Picker("Choose entry 2", selection: $yesNo2) {
                Text("Yes").tag(YesNo.yes)
                Text("No").tag(YesNo.no)
            }
            if allDefined {
                Text("Everything entered")
            }
            Button {
                pathStore.gotoToTop()
            } label: {
                Text("Exit")
            }
        }
        .onChange(of: yesNo1) { newValue in
            set(value: newValue, for: $formVM.entry21)
        }
        .onChange(of: yesNo2) { newValue in
            set(value: newValue, for: $formVM.entry22)
        }
    }
    
    func set(value: YesNo, for bind: Binding<Bool>) {
        switch value {
            case .yes:
                bind.wrappedValue = true
            case .no:
                bind.wrappedValue = false
            case .undefined:
                break
        }
    }
}

相关问题