Why is my SwiftUI custom drawer overlay animation jittery when I'm not scrolling in a ScrollView?

126 Views Asked by At

Running into some weird behavior with the SwiftUI rendering engine and I'm wondering if anyone can shed some light into it.

For context, I'm creating a custom Drawer View using an overlay. The specific animation in question is the drawer view moving up from the bottom of the screen using .transition(.move(.bottom))

For some reason, the drawer animation coming up is a bit jittery, unless a scrollview is moving while I tap the drawer open button.

Testing on a real device, 2 scenarios to test

  1. tap open drawer with scrollview being static
  2. swipe horizontal scroll view and tap open drawer while it still is moving

And here is a minimally reproduced example:

Main Base View

struct BaseView: View {
    @State private var isDrawerActive:Bool = false

    var body: some View {

        VStack {
            Button {
                isDrawerActive.toggle()
            } label: {
                Text("Open Drawer")
            }
            .buttonStyle(.bordered)
            .padding(.top)
            
            ScrollView(.horizontal) {
                HStack {
                    ForEach(0..<10) { _ in
                        Text("scroll for buttery smooth animations")
                            .padding()
                    }
                }
            }
            .frame(height: 100)
            
            Spacer()
        }
        .frame(maxWidth: .infinity, maxHeight: .infinity)
        .overlay(
            DrawerContainerView(isActive: $isDrawerActive)
        )
    }
}

Drawer Container View

struct DrawerContainerView: View {
    @Binding var isActive:Bool
    @State private var verticalOffset:CGFloat = .zero
    
    var body: some View {
        
        ZStack(alignment: .bottom) {
            Color.black
                .opacity(0.1)
                .ignoresSafeArea()
                .onTapGesture {
                    withAnimation(.interactiveSpring(response: 0.4, dampingFraction: 0.8, blendDuration: 0)) {
                        verticalOffset += 100
                        DispatchQueue.main.asyncAfter(deadline: .now()+0.01) {
                            isActive = false
                        }
                    }
                }
                .opacity(isActive ? 1.0 : 0.0)
            if isActive {
                DrawerView()
                    .offset(y: verticalOffset)
                    .zIndex(2)
                    .transition(.move(edge: .bottom))
            }
        }
        .animation(.interactiveSpring(response: 0.4, dampingFraction: 0.8, blendDuration: 0), value: isActive)
        .onChange(of: isActive) { b in
            verticalOffset = .zero
        }
    }
}

Drawer View

struct DrawerView: View {
    let cells:[String] = ["Edit Settings", "Edit Bio", "Settings", "Close Friend", "Emojis", "Status Settings", "Account"]
    
    var body: some View {
        VStack {
            RoundedRectangle(cornerRadius: 6)
                .frame(width: 32, height: 3.5)
                .foregroundColor(.gray.opacity(0.3))
                .padding(.top, 15)
                .padding(.bottom, 10)
            
            ForEach(cells, id: \.self) { text in
                Text(text)
                    .font(.headline.weight(.semibold))
                    .frame(maxWidth: .infinity, alignment: .leading)
                    .padding()
                    .background(RoundedRectangle(cornerRadius: 18, style: .continuous).foregroundColor(.gray.opacity(0.1)))
                    .padding(.horizontal)
            }
        }
        .background(
            RoundedRectangle(cornerRadius: 30, style: .continuous)
                .foregroundColor(.white)
                .shadow(color: .gray.opacity(0.5), radius: 8)
                .padding(.bottom, -20)
        )
        .padding(.bottom)
        .padding(.horizontal, 10)
    }
}

Would be amazing if anyone could shed light on this. Thanks

0

There are 0 best solutions below