0

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 :)

LeftiBus
  • 11
  • 5

2 Answers2

1

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

     //...
}
Joakim Danielson
  • 43,251
  • 5
  • 22
  • 52
  • Thanks for quick response. Unfortunately this doesn't work since this creates a publisher and the isActive argument expects a binding -> Error: Cannot convert value of type 'Published.Publisher' to expected argument type 'Binding' – LeftiBus Feb 12 '22 at 22:07
  • yes, and based on @joakim's answer you can just do: `NavigationLink("Click Link", isActive: $myVM.isProjectValid, destination: NextView())` – ChrisR Feb 13 '22 at 09:19
  • Aha - this was the obvious detail I was missing :). was putting the $ at the wrong place. IN fact I believe that I do not even need the didSet and can instead put the computed property on the project class and use $myVM.project.isProjectValid. Thanks to @JoakimDanielson and ChrisR – LeftiBus Feb 13 '22 at 13:37
0

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())`
shufflingb
  • 1,847
  • 16
  • 21
  • The description of the problem is accurate but constant bindings are designed for previews – malhal Feb 13 '22 at 10:59
  • @malhal Apple's docs are here https://developer.apple.com/documentation/swiftui/binding/constant(_:) The only place the docs mention Previews is in the Discusion section, where they say "Use this method to create a binding to a value that cannot change. This can be useful when using a PreviewProvider to see how a view represents different values." Not sure what constant bindings were designed for; but afaik there is nothing official saying it it bad practice to use outside of previews. – shufflingb Feb 13 '22 at 16:41