Safely unwrap optional values in SwiftUI bindings

Sponsored
RevenueCat logo
RevenueCat Paywalls

Add paywalls to your iOS app's in one line of code! With RevenueCat Paywalls you can remotely configure and edit your entire paywall view without waiting on App Review.

This week I came across a situation where I had to pass the member of an optional struct held as a @State property to a child view as a Binding.

Sounds simple, right? Well, the problem was that the child view, which I did not have control over, was expecting a Binding with a non-optional value:

ContenView.swift
import SwiftUI

struct Version: Identifiable {
    let id: String
    var name: String
}

@Observable
final class ViewModel {
    var version: Version?
}

struct ContentView: View {
    @State private var viewModel = ViewModel()
    
    var body: some View {
        VStack {
            TextField("Version Name", text: $viewModel.version?.name)
        }
        .padding()
    }
}

When I tried to compile the code above, I got an error saying that I could not use optional chaining to access the non-optional viewModel.version:

Unwrapping optional values in a SwiftUI Binding

This made sense, as while the property version is optional, the Binding we are accessing through the $ prefix is not. It is a non-optional Binding with a wrapped value of type String?.

At this point I had two options: either change the version property to be non-optional by providing default values for all of its properties or find a way to safely unwrap the optional value inside the Binding.

I decided to go with the latter as it would scale better and keeping the optionality made sense in the context of the application to reflect the user’s selection.

I did a bit of research and, thanks to this amazing Stack Overflow answer, I was pointed to an initializer of Binding I was not aware of: init?(_ base: Binding<Value?>). In a nutshell, what this initializer does is unwrap the optional value the Binding is holding and instead provide an optional Binding with a non-optional value.

Let’s now modify the code above to use this initializer:

ContentView.swift
import SwiftUI

struct ContentView: View {
    @State private var viewModel = ViewModel()
    
    var body: some View {
        VStack {
            if let unwrapped = Binding($viewModel.version) {
                TextField("Version Name", text: unwrapped.name)
            }
        }
        .padding()
    }
}

Make sure you safely unwrap the optional binding as shown above, as if its wrapped value becomes nil during the view’s lifecycle, the Binding will also become nil.