Mapping SwiftUI State to View Binding Types


A simple example of using a SwiftUI view with a Binding would be to have an @State property and pass its binding to the view.


struct ContentView: View {
    @State var payWithCard = false
    
    var body: some View {
        Toggle(isOn: $payWithCard) {
            Text("Pay with a credit card?")
        }
    }
}


But often we have a model that doesn't exactly match the binding that a view expects. In that case we would want to modify the value of the binding before we assign it to the state and modify the state to match the binding type that the view accepts.


Lets say we would like to represent our state as an enum, so that only one value can be true at a given moment.


enum PaymentMethod {
    case creditCard
    case bankTransfer
    case cheque
    case noSelection
}

struct ContentView: View {

    @State var paymentMethod: PaymentMethod = .noSelection

    var body: some View {...}
}


To be able to use Toggle that accepts a Binding<Bool> we would need to declare a custom binding that will serve as a middleman between the Toggle's isOn binding and our state. We need to declare that binding inside the body of our view, because it needs to be able to access the stored property @State.


struct ContentView: View {

    @State var paymentMethod: PaymentMethod = .noSelection
    
    var body: some View {
        
        let creditCardBinding = Binding<Bool>(
            get: {
                switch self.paymentMethod {
                case .creditCard: return true
                default: return false
                }
            },
            set: { value in
                self.paymentMethod = value ? .creditCard : .noSelection
            }
        )
        
        return VStack(spacing: 10) {
            Text("How would you like to pay?")
            
            Toggle(isOn: creditCardBinding) {
                Text("Credit Card")
            }
        }
    }
}


Don't forget to add an explicit return before your outer view, as we added another statement to the body property, the implicit return is no longer possible.


Now we can add more toggles and each of them will have its own binding. We don't want it to be possible to select more than one payment method at a time, so we can control the selection with our custom bindings.


struct ContentView: View {

    @State var paymentMethod: PaymentMethod = .noSelection
    
    var body: some View {
        
        let creditCardBinding = Binding<Bool>(
            get: {
                switch self.paymentMethod {
                case .creditCard: return true
                default: return false
                }
            },
            set: { value in
                self.paymentMethod = value ? .creditCard : .noSelection
            }
        )
        
        let bankTransferBinding = Binding<Bool>(
            get: {
                switch self.paymentMethod {
                case .bankTransfer: return true
                default: return false
                }
            },
            set: { value in
                self.paymentMethod = value ? .bankTransfer : .noSelection
            }
        )
        
        let chequeBinding = Binding<Bool>(
            get: {
                switch self.paymentMethod {
                case .cheque: return true
                default: return false
                }
            },
            set: { value in
                self.paymentMethod = value ? .cheque : .noSelection
            }
        )
        
        
        return VStack(spacing: 10) {
            Spacer()
            
            Text("How would you like to pay?")
            
            Spacer()
            
            Toggle(isOn: creditCardBinding) {
                Text("Credit Card")
            }
            
            Toggle(isOn: bankTransferBinding) {
                Text("Bank Transfer")
            }
            
            Toggle(isOn: chequeBinding) {
                Text("Cheque")
            }
            
            Spacer()
        }
        .padding()
    }
}


Here is how our example will work for the user.



The code for this article is available on GitHub.