Search code examples
animationswiftuitabview

Broken animation inside SwiftUI TabView


I am working on an animation within SwiftUI and faced a problem when using the animated View within a TabView. I searched a lot and did find always the same approach to use a state property with animations. Everything works fine except when I use the view inside a TabView. Here is a sample code

import SwiftUI

public struct AnimatedView: View {
    @State var isRotating: Bool? = false
    @State var isAnimating: Bool = true
    
    public var body: some View {
        
        ZStack {
            Circle()
                .trim(from:0.1, to: 0.2)
                .stroke(lineWidth: 5)
                .rotation(Angle.degrees(0))
                .frame(width: 60, height: 60)
                .foregroundColor(.blue)
                .rotationEffect(.degrees(isRotating! ? 360 : 0))
                .onAppear() {
                    //if !isAnimating {
                    //    return
                    //}
                    //isAnimating = false
                    
                    withAnimation(Animation.linear(duration: 1).repeatForever(autoreverses: false)) {
                        self.isRotating!.toggle()
                    }
                }
        }
    }
}

struct ContentView: View {
    enum Tab: Hashable {
        case tab1
        case tab2
    }
    
    @State private var selectedTab: Tab = .tab1
    
    var body: some View {
        TabView(selection: $selectedTab) {
            Text("Placeholder 11")
                .tabItem {
                    Text("Tab 1")
                }
            AnimatedView()
                .tabItem {
                    Text("Tab 2")
                }
            
        }
    }
}

When opening the second tab everything works fine, but when I leave it and reenter it, then the animation behaves different as it seems it now has two states and it becomes even wilder when I reenter the screen multiple times. I have come to the solution that I have another flag which will be inverted after the first start, so that the animation will not be reinitialized, but this seems a bit hacky to me.

When I understood it correctly, then the onAppear and onDisappear method will be called when the View is closed, but its State will be preserved. So it seems that the same view will have multiple states.

As I am pretty new into animation I wanted to know if there is a best practice for animations so that they also work inside TabViews (as this seems not that special case to me right now :-)) Thanks in advance


Solution

  • This took me a bit of trial and error to figure out, but in the end I made it! It's probably some kind of SwiftUI bug though. The solution was to simply use a CGFloat state value directly in the degrees constructor like so:

    public struct AnimatedView: View {
        
        @State private var rotation: CGFloat = 0
        
        public var body: some View {
            
            ZStack {
                Circle()
                    .trim(from:0.1, to: 0.2)
                    .stroke(lineWidth: 5)
                    .rotation(Angle.degrees(0))
                    .frame(width: 60, height: 60)
                    .foregroundColor(.blue)
                    .rotationEffect(.degrees(rotation))
                    .onAppear {
                        withAnimation(Animation.linear(duration: 1).repeatForever(autoreverses: false)) {
                            self.rotation = 360
                        }
                    }
                
            }
        }
    }
    

    You make it start from 0 and make it reach 360 with the linear animation just like you were doing. Let me know if this works for you!