1
votes

I tried to make a color picker that has a custom init function to set the original color. And 3 state variable (hue, saturation, brightness) to hold value that will be changed by smaller components. After trying many ways, I still cannot change value of 3 @State variables in custom init function. So weir. Can anyone help me? Thanks

struct ColorPicker: View {

    @State var saturation: CGFloat = 0.5
    @State var brightness: CGFloat = 0.5
    @State var hue: CGFloat = 0.5
    @State var alpha: CGFloat = 1

    var oriColor : UIColor? = nil
    var oriHue: CGFloat = 0
    var oriSaturation: CGFloat = 0
    var oriBrightness: CGFloat = 0

    init(oriColor:UIColor?) {
        self.oriColor = oriColor

        if let color = oriColor{

            color.getHue(&oriHue, saturation: &oriSaturation, brightness: &oriBrightness, alpha: &alpha)

            self._hue = State(initialValue:oriHue)
            self._saturation = State(initialValue:oriSaturation)
            self._brightness = State(initialValue:oriBrightness)

            self.hue = oriHue

            debugPrint("set value:\(hue)")
        }
    }

    var body: some View {

        debugPrint("make view with:\(self.hue)")
        let bindingSaturation = Binding<CGFloat>(get: {return self.saturation}, set: {val in self.saturation = val})
        let bindingBrightness = Binding<CGFloat>(get: {return self.brightness}, set: {val in self.brightness = val})
        let bindingHue = Binding<CGFloat>(get: {return self.hue}, set: {val in self.hue = val})

        debugPrint("make view with:\(self.hue)")
        return Text("debugging")
    }
}

Click here to view code and output

--- UPDATE apply answer from Procrastin8

I changed my code as your suggest

struct ColorPicker: View {

    @State var hue: CGFloat

    var oriHue: CGFloat = 0.5
    var oriSaturation: CGFloat = 0.5
    var oriBrightness: CGFloat = 0.5
    var oriAlpha: CGFloat = 1

    init(oriColor:UIColor?) {

        if let color = oriColor {
            color.getHue(&oriHue, saturation: &oriSaturation, brightness: &oriBrightness, alpha: &oriAlpha)
            self.hue = oriHue
        }
        else {
            self._hue = State(initialValue:oriHue)
        }
    }

    var body: some View {
        debugPrint("make view with:\(self.hue)")
        return VStack {
            BrightnessGrid.init(hue: self.$hue)
        }
    }
}

struct BrightnessGrid : View {
    @Binding var hue: CGFloat

    var body: some View {
        VStack{
            Text("hue:\(self.hue)")
        }
    }
}

But got "'self' used before all stored properties are initialized" error, as you can see in attach image here

I guest the reason is when we have custom init, then the default initializer will not work then we must manual set the value for @State variable by ourself. Then it's back to the first case.

The init function now is

init(oriColor:UIColor?) {

        if let color = oriColor {
            color.getHue(&oriHue, saturation: &oriSaturation, brightness: &oriBrightness, alpha: &oriAlpha)
        }
        self._hue = State(initialValue:oriHue)
    }

and still not work.

--- UPDATE 2 I make a new test project and apply the answer of User3441734. When apply in main view it's work perfectly. Then I separated to module it like this.

import SwiftUI

struct ContentView: View {
    var colorA: UIColor = UIColor.orange
    var colorB: UIColor = UIColor.blue
    @State var oriColor: UIColor? = nil

    func changeToColorA(){
        oriColor = colorA
    }

    func changeToColorB(){
        oriColor = colorB
    }

    var body: some View {
        VStack{
            HStack{
                Button(action:changeToColorA){
                    Text("Color A")
                }
                Button(action:changeToColorB){
                    Text("Color B")
                }
            }
            TestView(oriColor: oriColor)
        }
    }
}
struct TestView: View {

    @State var hue: CGFloat

    var oriHue: CGFloat = 0.5
    var oriSaturation: CGFloat = 0.5
    var oriBrightness: CGFloat = 0.5
    var oriAlpha: CGFloat = 1

    init(oriColor:UIColor?) {

        if let color = oriColor {
            color.getHue(&oriHue, saturation: &oriSaturation, brightness: &oriBrightness, alpha: &oriAlpha)
        }
        _hue = State(initialValue: oriHue)
    }

    var body: some View {
        debugPrint("make view with:\(self.hue)")
        return VStack {
            Text("\(hue)").background(Color(hue: Double(hue), saturation: Double(oriSaturation), brightness: Double(oriBrightness), opacity: Double(oriAlpha)))

            .onTapGesture {
                self.hue = 0.22
            }
            Slider(value: $hue, in: 0 ... 1) {
                Text("HUE")
            }
        }
    }
}

Then nothing happen when clicking on buttons Color A, Color B. Did I do some thing wrong?

3
it would be much easier for us, who want to help, if you copy reproducable and copyable code in here instead of letting out code for BrightnessGrid and HueSlider. Or Change/simplify your code in that way, we can copy and run it, if you dont want to show your code here...Chris

3 Answers

3
votes

You are setting up default initial values then attempting to overwrite the State container in your init function. Try setting them up at the same time, either with your calculated values or your defaults, but all in the init function.

struct ColorPicker: View {

    @State var saturation: CGFloat
    @State var brightness: CGFloat
    @State var hue: CGFloat
    @State var alpha: CGFloat

    init(_ color: UIColor?) {
      if let color = color {
          // what you had before
      } else {
          self._saturation = State(initialValue: 0.5)
          // etc.
      }
    }

I also wouldn't entirely rely on calculated values inside your body function. Try just having a label that prints out those values instead and see what you get, as opposed to your debug printing.

Note: You know that @State vends a Binding as its projectedValue right?

SomeView(colorComponent: "hue", binding: $hue)
0
votes

to change initial value of State use its underlying value

import SwiftUI
import PlaygroundSupport

struct ContentView: View {

    @State var hue: CGFloat

    var oriHue: CGFloat = 0.5
    var oriSaturation: CGFloat = 0.5
    var oriBrightness: CGFloat = 0.5
    var oriAlpha: CGFloat = 1

    init(oriColor:UIColor?) {

        if let color = oriColor {
            color.getHue(&oriHue, saturation: &oriSaturation, brightness: &oriBrightness, alpha: &oriAlpha)
        }
        _hue = State(initialValue: oriHue)
    }

    var body: some View {
        debugPrint("make view with:\(self.hue)")
        return VStack {
            Text("\(hue)").background(Color(hue: Double(hue), saturation: Double(oriSaturation), brightness: Double(oriBrightness), opacity: Double(oriAlpha)))

            .onTapGesture {
                self.hue = 0.22
            }
            Slider(value: $hue, in: 0 ... 1) {
                Text("HUE")
            }
        }
    }
}

PlaygroundPage.current.setLiveView(ContentView(oriColor: UIColor.orange))

enter image description here

0
votes

After trying many ways but not success. I use this code that can work.

import SwiftUI

struct ContentView: View {

    @State var oriColor: UIColor? = nil
    @State var hue: CGFloat = 1
    @State var saturation: CGFloat = 1
    @State var brightness: CGFloat = 1
    @State var alpha: CGFloat = 1

    func setColor(_ oriColor: UIColor){
        self.oriColor = oriColor
        oriColor.getHue(&self.hue, saturation: &self.saturation, brightness: &self.brightness, alpha: &self.alpha)
    }
    func changeToColorA(){
        setColor(UIColor.orange)
    }
    func changeToColorB(){
        setColor(UIColor.blue)
    }
    var body: some View {

         VStack {
            HStack {
                Button(action:changeToColorA) {
                    Text("Color A")
                }

                Button(action:changeToColorB) {
                    Text("Color B")
                }
            }

            ColorPicker(hue: self.$hue, saturation: self.$saturation, brightness: self.$brightness, alpha: self.$alpha)
        }
    }
}

struct ColorPicker: View {

    @Binding var hue: CGFloat
    @Binding var saturation: CGFloat
    @Binding var brightness: CGFloat
    @Binding var alpha: CGFloat

    var body: some View {

         return VStack {
            TestView.init(hue: self.$hue, saturation: self.$saturation, brightness: self.$brightness, alpha:self.$alpha )
        }
    }
}

struct TestView: View {

    @Binding var hue: CGFloat
    @Binding var saturation: CGFloat
    @Binding var brightness: CGFloat
    @Binding var alpha: CGFloat

    var body: some View {

        debugPrint("make view with:\(self.hue)")

        return VStack {
            Text("\(hue)").background(Color(hue: Double(hue), saturation: Double(saturation), brightness: Double(brightness), opacity: Double(alpha)))


            Slider(value: $hue, in: 0 ... 1) {
                Text("HUE")
            }
        }
    }
}