Reputation: 9773
I am transitioning from one view to another using .fullScreenCover. The presented view has a navigation stack and will consist of several views. I want each one of those views to be able to dismiss the entire stack using the binded isActive property used in the .fullScreenCover.
Do I need to bind this property to each view in the stack or is there a simpler way to do this. I thought of adding this property to the viewModel of the navigation stack but had no luck getting it there. Here is what I am trying to do:
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
}
}
Of course I could just dismiss the SecondView by setting isActive to false but I am doing it in the ViewModel on purpose because it will not just be for this simple case. It may dismiss after an asynchronous call to access a server.
Also I have just shown two views but there will be NavigationView in the second view and NavigationLink to another view and so on. They will all share the same ViewModel. The FirstView does not share that ViewModel.
I don't want to have to pass the isActive to each of those views if I can avoid it. I want to either be able to pass the isActive somehow to the viewModel from the SecondView, or to have it be some kind of environment variable that can be accessed by any of the views.
Not sure what is the best practice in what I am guessing is this common scenario.
Upvotes: 1
Views: 1916
Reputation: 2840
I don't know your project, I take for granted what you are doing.
If you want to dismiss by the model, the model has to be implemented inside the first view.
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()
}
}
UPDATE using only @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")
})
}
}
Upvotes: 1