4

I have an image that I apply a 360 rotation on to have the effect of loading/spinning. It works fine until I add Text underneath, the image still spins but it bounces vertically.

Here is code to see it:

import SwiftUI

@main
struct SpinnerApp: App {    
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}

struct ContentView: View {
    @State var isAnimating = false
    @State var text = ""
    
    var animation: Animation {
        Animation.linear(duration: 3.0)
            .repeatForever(autoreverses: false)
    }

    var body: some View {
        HStack {
            Spacer()
            VStack {
                Circle()
                    .foregroundColor(Color.orange)
                    .frame(height: 100)
                    .rotationEffect(Angle(degrees: self.isAnimating ? 360 : 0.0))
                    .animation(self.isAnimating ? animation : .default)
                    .onAppear { self.isAnimating = true }
                    .onDisappear { self.isAnimating = false }
                if self.text != "" {
                    Text(self.text)
                }
            }
            Spacer()
        }
        .background(Color.gray)
        .onAppear(perform: {
            DispatchQueue.main.asyncAfter(deadline: .now()+4, execute: {
                self.text = "Test"
            })
        })
    }
}

I replaced the image with a Circle so you won't be able to see the spinning/animation, but you can see the circle bouncing vertically once we set the text. If the text was there from the beginning and it didn't change then all is fine. The issue only happens if the text is added later or if it got updated at some point.

Is there a way to fix this?

mota
  • 5,275
  • 5
  • 34
  • 44

2 Answers2

3

Try using explicit animations instead, with withAnimation. When you use .animation(), SwiftUI sometimes tries to animate the position of your views too.

struct ContentView: View {
    @State var isAnimating = false
    @State var text = ""
    
    var animation: Animation {
        Animation.linear(duration: 3.0)
            .repeatForever(autoreverses: false)
    }

    var body: some View {
        HStack {
            Spacer()
            VStack {
                Circle()
                    .foregroundColor(Color.orange)
                    .overlay(Image(systemName: "plus")) /// to see the rotation animation
                    .frame(height: 100)
                    .rotationEffect(Angle(degrees: self.isAnimating ? 360 : 0.0))
                    .onAppear {
                        withAnimation(animation) { /// here!
                            self.isAnimating = true
                        }
                    }
                    .onDisappear { self.isAnimating = false }
                
                if self.text != "" {
                    Text(self.text)
                }
            }
            Spacer()
        }
        .background(Color.gray)
        .onAppear(perform: {
            DispatchQueue.main.asyncAfter(deadline: .now()+4, execute: {
                self.text = "Test"
            })
        })
    }
}

Result:

Circle continues spinning even after text is added
aheze
  • 24,434
  • 8
  • 68
  • 125
  • 1
    I tried this but the circle is still moving up and down - strange! I tried it on the latest beta thought so that might be the reason we're getting different results. Asperi's solution worked perfectly. Thanks! – mota Feb 23 '21 at 20:04
2

Just link animation to dependent state value, like

//... other code
.rotationEffect(Angle(degrees: self.isAnimating ? 360 : 0.0))
.animation(self.isAnimating ? animation : .default, value: isAnimating)  // << here !!
//... other code
Asperi
  • 228,894
  • 20
  • 464
  • 690