An example with a view model created only when doing into a specific form hierarchy. First level use navigationDestination, then navigationLink and 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
}
}
}