0

I'm new to SwiftUI coming from UIKit. I'm trying to achieve the following:

  • Given an array of mutable states in a view model that represents current progress to be displayed on a Circle using trim(from:to:)
  • Loop over each item to render a circle with the current progress and animate changes from the old to the new position
  • A view model will periodically update the array that is a @Published value in an ObservableObject.

I setup a demo view that looks like this


@StateObject var demoViewModel: DemoViewModel = DemoViewModel()

var body: some View {
            ForEach(demoViewModel.progress, id: \.self) { progress in
                Circle()
                    .trim(from: 0, to: progress.progress)
                     .stroke(style: StrokeStyle(lineWidth: 15, lineCap: .round))
                     .foregroundColor(.red)
                     .frame(width: 100)
                     .rotationEffect(.degrees(-90))
                     .animation(.linear(duration: 1), value: demoViewModel.progress)
            }
        }

and a test view model like this

class DemoViewModel: ObservableObject {

    @Published var progress: [Demo] = [.init(progress: 0)]

    struct Demo: Hashable {
        let progress: Double
    }

    init() {
        Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { timer in
            let current = self.progress[0]

            if current.progress < 1 {
                self.progress[0] = .init(progress: current.progress + 0.1)
            }

        }
    }
}

For the sake of the demo the array only has one item. What I expected was on each iteration a new value is written to the item in the array which triggers the animation to animate to that value. What actually happens is the progress updates, but it jumps to each new value for no animation.

1 Answer 1

2

Try this approach, where the self.demos[0].progress in init() is updated rather than creating a new Demo(...) at each time tick.

Note, modified a few other things as well, such as the .animation(...value:..) and in particular the ForEach loop with the added Demo unique id.

struct ContentView: View {
    @StateObject var demoViewModel: DemoViewModel = DemoViewModel()
    
    var body: some View {
        VStack {
            ForEach(demoViewModel.demos) { demo in  // <-- here
                Circle()
                    .trim(from: 0, to: demo.progress)
                    .stroke(style: StrokeStyle(lineWidth: 15, lineCap: .round))
                    .foregroundColor(.red)
                    .frame(width: 100)
                    .rotationEffect(.degrees(-90))
                    .animation(.linear(duration: 1), value: demo.progress) // <-- here
            }
        }
    }
}

class DemoViewModel: ObservableObject {
    @Published var demos:[Demo] = [Demo(progress: 0)]
    
    struct Demo: Identifiable, Hashable {  // <-- here
        let id = UUID()  // <-- here
        var progress: Double  // <-- here
    }
    
    init() {
        Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { timer in
            if self.demos[0].progress < 1 {
                self.demos[0].progress = self.demos[0].progress + 0.1 // <-- here
            }
        }
    }
   
}
Sign up to request clarification or add additional context in comments.

1 Comment

This worked great. Seems like I need to ready a bit more about identity in SwiftUI. Thanks!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.