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

323 Views Asked by At

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!

2

There are 2 best solutions below

1
B25Dec On BEST ANSWER

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)
    }
}
1
Cheolhyun On
  1. my development environment is Xcode 14.3 / macOS Ventura 13.1
  2. your code works fine on iOS 16.4 simulator.
  3. only on macOS does the problem you said occur, and it can be solved by deleting .transition(.scale) of ProgressView() as shown below. (Or you can change it to .opacity, .identity type.)
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)
    }
}