iosswiftswiftuiswiftui-animationswiftui-sheet

SwiftUI PhaseAnimator make a view jitter in sheet presentation


First here's a block of code that reproduce the issue:

import SwiftUI

struct CustomAnimation: ViewModifier {

    @State private var isAnimated: Bool = false

    func body(content: Content) -> some View {
        content
            .phaseAnimator([true, false], trigger: self.isAnimated) { view, phase in
                view
                    .scaleEffect(phase ? 1 : 1.2)
            }
            .onTapGesture {
                self.isAnimated.toggle()
            }
    }
}

#Preview {
    @Previewable @State var isPresented: Bool = true
    VStack {
        Button {
            isPresented = true
        } label: {
            Text("show sheet")
        }
    }
    .sheet(isPresented: $isPresented) {
        VStack {
            Spacer()
            Text("my animated text")
                .modifier(CustomAnimation())
        }
    }
}

The issue is that the animated text, placed on the bottom of the screen with a spacer, jitter up (and goes back to its place) when I drag the sheet up, after the view has been animated at least once.

The issue is visible only if the animated view is sticked to the bottom of the screen (eg. with a spacer), and only after an animation.

I've tried to debug with instruments hitches but I'm new to that one and don't understand much how to get informations.

The issue appears when building on a device but also in previews. I've noticed on device if I put the app in background it's like the animation didn't take place and the issue disappear, until I trigger an other animation.

Xcode preview that shows the animation issue when dragging a SwiftUI sheet


Solution

  • It is easier to see that the jitter is somehow connected with the sheet height when you set medium detents:

    .sheet(isPresented: $isPresented) {
        VStack {
            // ...
        }
        .presentationDetents([.medium]) // 👈 here
    }
    

    Animation

    It helps to add .geometryGroup() after the modifier .phaseAnimator:

    content
        .phaseAnimator([true, false], trigger: isAnimated) { view, phase in
            view
                .scaleEffect(phase ? 1 : 1.2)
        }
        .onTapGesture {
            isAnimated.toggle()
        }
        .geometryGroup() // 👈 here
    

    This seems to "anchor" the text, like on first show.

    Animation