Search code examples
animationswiftuiprogress-barspinnervisual-glitch

SwiftUI on macOS - Cannot scale a ProgressView with a smooth, centered animation


I'm wondering if someone can help me understand why the animation that results from the below SwiftUI code is glitchy. Here's the code:

struct ProgressViewAnimationGlitch: View {
    @State private var isShowingProgressView: Bool = false
    var body: some View {
        ZStack(alignment: .center) {
            if !isShowingProgressView {
                Circle()
                    .fill(Color.red)
                    .onTapGesture {
                        withAnimation {
                            isShowingProgressView = true
                        }
                    }
                    .transition(.scale)
            }
            
            if isShowingProgressView {
                ProgressView()
                    .transition(.scale)
            }
        }
        .frame(width: 100, height: 100, alignment: .center)
    }
}

When I say "glitchy" I mean that not only does the ProgressView slide in from the lower right corner instead of popping in from the center, but also it seems to delay for a moment after I click the red circle before beginning its (incorrect) entry animation.

I tried many variations (include the .scaleEffect(_:) modifier) and was unable to get the ProgressView to scale in and out smoothly from the center as I wanted.

Thanks!


Solution

  • Looks like when circle finishes its animation scaling before that time Progressview is already in its full-scaled view. And placing it over the scaled cirle it shows a graphic glitch. But yes the above code is working fine if you run it over simulator, But it fails if you run it over Mac. Above suggested solution is also working I tried. But from my understanding, you have to utilize animation with its own values like below solution. Its working for me on both iOS and Mac. Hope it helps. Tested on Xcode 14.3

    struct ProgressViewAnimationGlitch: View {
        @State private var isShowingProgressView: Bool = false
        @State var scale = 0.1
        let animation = Animation.linear(duration: 0.5).repeatCount(1)
        var body: some View {
            ZStack(alignment: .center) {
                if !isShowingProgressView {
                    Circle()
                        .fill(Color.red)
                        .onTapGesture {
                            withAnimation {
                                isShowingProgressView = true
                                withAnimation(animation) {
                                    scale  = 1.0
                                    }
                            }
                        }
                        .transition(.scale)
                }
                
                if isShowingProgressView {
                    ProgressView()
                        .frame(alignment: .center)
                        .scaleEffect(scale)
    
                }
            }
            .frame(width: 100, height: 100, alignment: .center)
        }
    }