skip to Main Content

According to this article it should be simple to create custom Binding with different type for binding and for display. This is my not working example:

import SwiftUI

struct BindingButton: View {
    @Binding var title: String
    var onTap: () -> Void
    var body: some View {
        Button {
            onTap()
        } label: {
            Text(title)
        }
    }
}

struct CustomBinding: View {
    @State var date: Date
    @State var int: Int
    
    lazy var descriptiveDate = Binding(
        get: { //Escaping closure captures mutating 'self' parameter
            return self.date.description
        },
        set: { _ in }
    )
    
    lazy var descriptiveInt = Binding(
        get: { //Escaping closure captures mutating 'self' parameter
            return String(self.int)
        },
        set: { _ in }
    )
    
    var body: some View {
        VStack {
            // Cannot find '$descriptiveDate' in scope
            BindingButton(title: $descriptiveDate) {
                date = Date()
            }
            // Cannot find '$descriptiveInt' in scope
            BindingButton(title: $descriptiveInt) {
                int += 2
            }
        }
    }
}

I would like to bind Date and Int, but use string for display. Is it possible witohut Double Binding? I mean I would like to avoid to create first Binding for Date and Int, then .onChange update another Binding for String and String. Do I have to do it like this, or maybe there is more elegant way?

2

Answers


  1. Binding is by definition a two-way connection. lazy implies that the code only runs once. Based on this and the empty set your descriptiveDate and descriptiveInt don’t need to be Binding just a get for a String

    struct CustomBinding: View {
        @State var date: Date
        @State var int: Int
        
        var descriptiveDate: String {
            date.description
        }
                
        var descriptiveInt : String{
            String(self.int)
        }
        
        var body: some View {
            VStack {
                BindingButton(title: descriptiveDate) {
                    date = Date()
                }
                BindingButton(title: descriptiveInt) {
                    int += 2
                }
            }
        }
    }
    
    struct BindingButton: View {
        let title: String
        var onTap: () -> Void
        var body: some View {
            Button {
                onTap()
            } label: {
                Text(title)
            }
        }
    }
    

    lazy doesn’t work with with SwiftUI Views when @State triggers a redraw you need to recalculate the body and descriptive variables.

    Login or Signup to reply.
  2. I totally agree with the previous answer that in this case the binding is not needed. But to address the other part of the question: "Is it possible to have interdependent @State and @Binding without Double Binding?"

    Well, first of all, even in the most primitive way, you don’t need a custom binding. Instead you can use 2 state variables:

        @State var descriptiveDate: String
        @State var descriptiveInt: String
    

    and utilize either didSet:

        @State var date: Date {
            didSet {
                descriptiveDate = date.description
            }
        }
        @State var int: Int {
            didSet {
                descriptiveInt = String(int)
            }
        }
        
        @State var descriptiveDate: String
        @State var descriptiveInt: String
    

    Or have onChange implemented for date and int state:

    struct CustomBinding: View {
        
        @State var date: Date
        @State var int: Int
        @State var descriptiveDate: String
        @State var descriptiveInt: String
        
        var body: some View {
            VStack {
                BindingButton(title: $descriptiveDate) {
                    date = Date()
                }
                .onChange(of: date) { newDate in
                    descriptiveDate = newDate.description
                }
                BindingButton(title: $descriptiveInt) {
                    int += 2
                }
                .onChange(of: int) { newInt in
                    descriptiveInt = String(int)
                }
            }
            
        }
    }
    

    But this is still a bit redundant, as you will have duplication of the @State for every variable. So to avoid that custom binding is helpful:

                BindingButton(title: Binding(
                    get: { int.description },
                    set: { int = Int($0)! }
                )) {
                    int += 2
                }
    

    That is: you don’t need to keep additional state for each state, while still have a possibility to change parent value from child. Doesn’t make much sense in this concrete example, but it does in some cases.

    So did we avoid "double" variable – not really. Since you have 2 pieces of information that can change following some rules, but independently, you do need 2 of something. But custom binding is just one of the possibilities.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search