2

I'm trying to get progress value of a progress bar based on another value. Both of these variables (waterQuantity and progress) are in property wrappers @State

This is my code :

struct CircularProgressBar: View {
@State var waterQuantity: Int
@State var progress: Float = (1 + (Float(waterQuantity)/Float(1500))) * 100

var body: some View {
    ZStack {
        Circle()
            .foregroundColor(.white)
            .padding(25)
            .overlay(
                Circle()
                    .trim(from: 0.0, to: CGFloat(min(self.progress, 1.0)))
                    .stroke(style: StrokeStyle(lineWidth: 25
                        , lineCap: .round, lineJoin: .round))
                    .foregroundColor(Color.blue)
                    .rotationEffect(Angle(degrees: 270.0))
                    .animation(.linear)
                    .padding(50)
        )
        
        VStack {
            Text(String(format: "%.0i mL", 500))
                .font(.largeTitle)
                .bold()
                .foregroundColor(Color("bgStart"))
            Text("1500 mL")
                .foregroundColor(.gray)
        }
    }
}

As you can see, I cannot initialise progress with another variable that has not been initialized yet. I tried passing values with init() and mutating func, but none of these solutions worked for me

Quentin C
  • 307
  • 3
  • 13

3 Answers3

4

You should change progress to a computed property that doesn't store any value, but rather calculates it's value dynamically based on the value of waterQuantity:

var progress: Float {
    Float(waterQuantity)/Float(1500)
}

Now when waterQuantity is being updated, your view will still be redrawn because waterQuantity is used inside this computed property, which is in turn being used in your view.

Quentin C
  • 307
  • 3
  • 13
raynok
  • 173
  • 7
  • Thank you! That's the best solution to me except you cannot wrap a computed property (but it's another minor problem). – Quentin C Jul 15 '20 at 11:44
  • Why would you want to wrap the computed property? – raynok Jul 15 '20 at 15:08
  • The computed property will update in the view whenever the state is changed. As this is showing the progress it assumes something will be updating the state. It seems this will be outside of this progress view and so WaterQuantity should be a binding and then the view that calls this to present progress will have the State / source of truth for the WaterQuatity and as they changes this view will refresh with updated progress. – Hongtron Mar 08 '23 at 12:48
1

You may not need to make this

@State var progress: Float = Float(waterQuantity)/Float(1500)

a property just leave only waterQuantity and copy paste that code inside the body of view and it'll be refreshed when waterQuantity is changed

Quentin C
  • 307
  • 3
  • 13
Shehata Gamal
  • 98,760
  • 8
  • 65
  • 87
  • Giving error `Cannot use instance member 'items' within property initializer; property initializers run before 'self' is available` – user8719510 Jul 01 '22 at 05:55
0

Since progress is dependent on the waterQuantity passed into the view, I would suggest performing this computation within the init function. And then initialing progress with a new initial state value.

@State private var waterQuantity: Int
@State private var progress: Float = 0.0

init(waterQuantity: Int){
    self._waterQuantity = State(initialValue: waterQuantity)
    self._progress = State(initialValue: (1 + (Float(waterQuantity)/Float(1500))) * 100)
}

But since it doesn't look like your using waterQuantity within the body of the View. Then it probably doesn't need to have a @State annotation wrapper, and the code could look something like this instead:

private var waterQuantity: Int
@State private var progress: Float = 0.0

init(waterQuantity: Int){
    self.waterQuantity = waterQuantity
    self._progress = State(initialValue: (1 + (Float(waterQuantity)/Float(1500))) * 100)
}
Paul Mayer
  • 185
  • 1
  • 10