To provide some context, Im writing an order tracking section of our app, which reloads the order status from the server every so-often. The UI on-screen is developed in SwiftUI. I require an optional image on screen that changes as the order progresses through the statuses.
When I try the following everything works...
My viewModel is an ObservableObject:
internal class MyAccountOrderViewModel: ObservableObject {
This has a published property:
@Published internal var graphicURL: URL = Bundle.main.url(forResource: "tracking_STAGEONE", withExtension: "gif")!
In SwiftUI use the property as follows:
GIFViewer(imageURL: $viewModel.graphicURL)
My issue is that the graphicURL
property has a potentially incorrect placeholder value, and my requirements were that it was optional. Changing the published property to: @Published internal var graphicURL: URL?
causes an issue for my GIFViewer which rightly does not accept an optional URL:
Cannot convert value of type 'Binding<URL?>' to expected argument type 'Binding<URL>'
Attempting the obvious unwrapping of graphicURL
produces this error:
Cannot force unwrap value of non-optional type 'Binding<URL?>'
What is the right way to make this work? I don't want to have to put a value in the property, and check if the property equals placeholder value (Ie treat that as if it was nil), or assume the property is always non-nil and unsafely force unwrap it somehow.
Below is an extension of Binding
you can use to convert a type like Binding<Int?>
to Binding<Int>?
. In your case, it would be URL
instead of Int
, but this extension is generic so will work with any Binding
:
extension Binding {
func optionalBinding<T>() -> Binding<T>? where T? == Value {
if let wrappedValue = wrappedValue {
return Binding<T>(
get: { wrappedValue },
set: { self.wrappedValue = $0 }
)
} else {
return nil
}
}
}
With example view:
struct ContentView: View {
@StateObject private var model = MyModel()
var body: some View {
VStack(spacing: 30) {
Button("Toggle if nil") {
if model.counter == nil {
model.counter = 0
} else {
model.counter = nil
}
}
if let binding = $model.counter.optionalBinding() {
Stepper(String(binding.wrappedValue), value: binding)
} else {
Text("Counter is nil")
}
}
}
}
class MyModel: ObservableObject {
@Published var counter: Int?
}
Result: