Home > Software design >  SwiftUI: Changing a private var with a slider
SwiftUI: Changing a private var with a slider

Time:08-20

I have a private variable in a struct which I can only access using a setter and getter. I want to change this variable using a slider, so I am attempting to bind a different var with this var using willSet:

struct MyStruct {
    private var myVar: Float? = nil
    mutating func setMyVar(newVal: Float) {
        if (SomeCondition) { // always true when the slider is in use
            myVar = newVal
        } else {
            // stuff
        }
    }
    func getMyVar() -> Float {
        myVar == nil ? 0.0 : myVar!
    }
}

struct MyView: View {
    @State var myStructToEdit = MyStruct()
    @State var tempVar: Double = 0.0 {
        willSet {
            myStructToEdit.setMyVar(newVal: Float(newValue))
        }
    }
    var body: some View {
        VStack {
            Text(String(tempVar))
            Text(String(myStructToEdit.getMyVar()))
            Slider(value: $tempVar, in: 1.0...20.0, step: 1.0)
        }
    }
}

As the slider moves, tempVar changes but MyVar doesn't. What is the correct way to achieve this binding?

CodePudding user response:

Property observers won't work with SwiftUI @State variables.

Use .onChange(of:) to act upon changes to tempVar:

struct MyView: View {
    @State var myStructToEdit = MyStruct()
    @State var tempVar: Double = 0.0

    var body: some View {
        VStack {
            Text(String(tempVar))
            Text(String(myStructToEdit.getMyVar()))
            Slider(value: $tempVar, in: 1.0...20.0, step: 1.0)
                .onChange(of: tempVar) { value in
                    myStructToEdit.setMyVar(newVal: Float(value))
                }
        }
    }
}

Use Binding(get:set:) to directly set and get the value in your struct:

You don't need tempVar. You can directly set and get the value to and from your struct.

struct ContentView: View {
    @State var myStructToEdit = MyStruct()

    var body: some View {
        VStack {
            Text(String(myStructToEdit.getMyVar()))
            Slider(value: Binding(get: {
                myStructToEdit.getMyVar()
            }, set: { value in
                myStructToEdit.setMyVar(newVal: value)
            }), in: 1.0...20.0, step: 1.0)
        }
    }
}

or assign the Binding to a let to make it cleaner:

struct ContentView: View {
    @State var myStructToEdit = MyStruct()

    var body: some View {
        let myVarBinding = Binding(
            get: { myStructToEdit.getMyVar() },
            set: { value in myStructToEdit.setMyVar(newVal: value) }
        )
        
        VStack {
            Text(String(myStructToEdit.getMyVar()))
            Slider(value: myVarBinding, in: 1.0...20.0, step: 1.0)
        }
    }
}

Note: Since myVarBinding is already a binding, you do not need to use a $ to turn it into a binding.

  • Related