swiftxcodeswiftuidraggesture

SwiftUI DragGesture weird jump up behavior


I have created a bottom card with a drag gesture that is not yet finished. But I ran into an issue when I set the default translation height from zero and it just jumps to what I think is translation 0.

import SwiftUI

struct TimetableBottomCardView: View {
    @State var translation: CGSize = CGSize(width: .zero, height: 785)
    var body: some View {
            VStack {
                TimetableBottomCardUI()
                Spacer()
            }.frame(maxWidth: .infinity, maxHeight: .infinity)
                .background(Material.ultraThick)
                .mask(RoundedRectangle(cornerRadius: 35).ignoresSafeArea(.all, edges: .bottom))
                .offset(y: translation.height)
                .gesture(
                DragGesture()
                    .onChanged { value in
                        translation = value.translation
                    }
                    .onEnded { value in
                        withAnimation {
                            let DefaultTranslation: CGSize = CGSize(width: .zero, height: 785)
                            translation = DefaultTranslation
                        }
                    }
            )
    }
}

struct TimetableBottomCardView_Previews: PreviewProvider {
    static var previews: some View {
        TimetableBottomCardView()
            .background(.blue)
    }
}

Solution

  • You are assigning your transition value when your DragGesture changes. When you use onChanged, its value gives you a relative position, rather than an absolute one. That means your transition value only changes depending on where your last touch appeared, so as you're offsetting your view by 785, when onChanged gets triggered your view jumps up due to DragGestures relative value.

    bug

    As you can see here, your offset was 785, but when onChanged gets triggered, it sets it to 0, which means, it also sets its offset to zero

    To avoid this behavior, you have to add your offset amount (785) to your card so that you can save your previous offset.

    Result:

    result

    Code:

    import SwiftUI
    
    struct TimetableBottomCardView: View {
        @State var translation: CGSize = CGSize(width: .zero, height: 785)
        var body: some View {
                VStack {
                    Text("Draggable Card")
                        .frame(width: 300, height: 300)
                        .background(.teal)
                    Spacer()
                }.frame(maxWidth: .infinity, maxHeight: .infinity)
                    .background(Material.ultraThick)
                    .mask(RoundedRectangle(cornerRadius: 35).ignoresSafeArea(.all, edges: .bottom))
                    .offset(y: translation.height)
                    .gesture(
                    DragGesture()
                        .onChanged { value in
                            print(value.translation)
                            translation = CGSize(width: value.translation.width, height: value.translation.height + 785)
                        }
                        .onEnded { value in
                            withAnimation {
                                let DefaultTranslation: CGSize = CGSize(width: .zero, height: 785)
                                translation = DefaultTranslation
                            }
                        }
                )
                    .onChange(of: translation) {_ in
                        // print(translation)
                    }
        }
    }
    
    struct TimetableBottomCardView_Previews: PreviewProvider {
        static var previews: some View {
            TimetableBottomCardView()
                .background(.blue)
        }
    }