Have the following situation. I have a view model that is an observable object with a computed property of type Bool. I want to be able to enable/disable a navigation link based on the computed property, but I need a binding to do so. Here a simplified example:
struct Project {
var name: String
var duration: Int
}
class MyViewModel: Observable Object {
@Published var project: Project
var isProjectValid: Bool {
return project.name != "" && project.duration > 0
}
}
struct MyView: View {
@EnvironmentObject var myVM: MyViewModel
var body: some View {
...
NavigationLink("Click Link", isActive: ?????, destination: NextView())
...
}
}
Since isActive expects a binding I am not able to access computed property such as myVM.isProjectValid. Tried also with the computed property in project class, still same problem.
Also considered creating custom binding related to the computed property, but not quite sure if/how to go about it.
First question I have posted, so if I am missing some details please be kind :)
CodePudding user response:
Make it a @Published
property and update it when project
is changed
class MyViewModel: ObservableObject {
@Published var project: Project {
didSet {
isProjectValid = project.name != "" && project.duration > 0
}
}
@Published var isProjectValid: Bool
//...
}
CodePudding user response:
@Binding
is by definition a two-way connection
https://developer.apple.com/documentation/swiftui/binding
When the NavigationLink
is dismissed the Binding
will automatically try to make that variable false
.
That being said you can easily make it a Binding
by adding a {get set}
the code below will work in your project.
var isProjectValid: Bool {
get{
project.name != "" && project.duration > 0
}
set{
//Something has to happen here to make the `get` condition `false`
if !newValue{
project.duration = -1
}
}
}
But the issue is with the current setup of setting the duration
to -1
that is likely not something you want to have done.
So you need to provide another variable that can hold the Binding
that decides if the NavigationLink
isActive
or not, if you don't your variable will be out of sync.
Here is full working code I had to make some tweaks to get it to compile and run.
import SwiftUI
struct Project {
var name: String
var duration: Int
}
class MyViewModel: ObservableObject {
@Published var project: Project = Project(name: "name", duration: -1)
var isProjectValid: Bool {
get{
project.name != "" && project.duration > 0
}
set{
//Something has to happen here to make the `get` condition `false`
if !newValue{
project.duration = -1
}
}
}
}
struct ComputedBindingView: View {
@StateObject var myVM: MyViewModel = .init()
var body: some View {
NavigationView{
VStack{
Button("toggle", action: {
if myVM.isProjectValid{
myVM.project.duration = -1
}else{
myVM.project.duration = 1
}
})
NavigationLink("Click Link", isActive: $myVM.isProjectValid, destination: {Text("NextView()")})
}
}
}
}
struct ComputedBindingView_Previews: PreviewProvider {
static var previews: some View {
ComputedBindingView()
}
}
CodePudding user response:
The use of the computed property suggests some type of design where the user is not supposed to trigger the NavigationLink
directly. But instead the NavigationLink
is expected to be triggered programatically as a side-effect of some other mechanism elsewhere in the code. Such as might be done at the completion of a form or similar process by the user.
Not 100% if this is what's being aimed for, but if it is, then one option would be to pass a constant Binding to the NavigationLink
, e.g.
NavigationLink("Click Link", isActive: .constant(myVM.isProjectValid), destination: NextView())`