Search code examples
listanimationswiftuiopacitywatchos

SwiftUI: List is messing up animation for its subviews inside an HStack


I'm making a WatchOS app that displays a bunch of real-time arrival times. I want to place a view, a real-time indicator I designed, on the trailing end of each cell of a List that will be continuously animated.

The real-time indicator view just has two image whose opacity I'm continuously animating. This View by itself seems to work fine:

animated view by itself

However, when embedded inside a List then inside an HStack the animation seems to be affecting the position of my animated view not only its opacity.

animated view inside a cell

The distance this view travels seems to only be affected by the height of the HStack.

Animated view code:

struct RTIndicator: View {
    @State var isAnimating = true

    private var repeatingAnimation: Animation {
        Animation
            .spring()
            .repeatForever()
    } 

    private var delayedRepeatingAnimation: Animation {
        Animation
            .spring()
            .repeatForever()
            .delay(0.2)
    }

    var body: some View {
        ZStack {
            Image("rt-inner")
                .opacity(isAnimating ? 0.2 : 1)
                .animation(repeatingAnimation)
            Image("rt-outer")
                .opacity(isAnimating ? 0.2 : 1)
                .animation(delayedRepeatingAnimation)
        }
        .frame(width: 16, height: 16, alignment: .center)
        .colorMultiply(.red)
        .padding(.top, -6)
        .padding(.trailing, -12)
        .onAppear {
            self.isAnimating.toggle()
        }
    }
}

All code:

struct SwiftUIView: View {
    var body: some View {
        List {
            HStack {
                Text("Cell")
                    .frame(height: 100)
                Spacer()
                RTIndicator()
            }.padding(8)
        }
    }
}

Solution

  • Although it's pretty hacky I have found a temporary solution to this problem. It's based on the answer from Asperi.

    I have create a separate View called ClearView which has an animation but does not render anything visual and used it as a second overall in the same HStack.

    struct ClearView: View {
        @State var isAnimating = false
    
        var body: some View {
            Rectangle()
                .foregroundColor(.clear)
                .onAppear {
                    withAnimation(Animation.linear(duration: 0)) {
                        self.isAnimating = true
                    }
                }
        }
    }
    
    var body: some View {
        List {
            HStack {
                Text("Cell")
                    .frame(height: 100)
                Spacer()
            }
            .overlay(RTIndicator(), alignment: .trailing)
            .overlay(ClearView(), alignment: .trailing)
            .padding(8)
        }
    }