SwiftUI: How to change the speed of drag based on distance already dragged?

Viewed 950

For example: I have a joystick that can be moved around freely, how can I make the dragging slower with distance? The further I drag the joystick, the slower the drag is. Thanks in advance.

My Joystick code, dragging works but has no bounds and isn’t slowed down if you drag it to the edges:

import SwiftUI

struct ContentView: View {

    @State var isDragging = false
    @State var dragValue = CGSize.zero

    var body: some View {
        VStack {
            Text("width: \(dragValue.width)")
            Text("height: \(dragValue.height)")
            VStack (spacing: 16) {
                HStack(spacing: 35) {
                    Image(systemName: "chevron.left")
                        .foregroundColor(.gray)


                    VStack (spacing: 80) {
                        Image(systemName: "chevron.up")
                            .foregroundColor(.gray)
                        Image(systemName: "chevron.down")
                            .foregroundColor(.gray)

                    }
                    Image(systemName: "chevron.right")
                        .foregroundColor(.gray)

                }


            }
            .offset(x: dragValue.width * 0.05, y: dragValue.height * 0.05)

            .frame(width: 150, height: 150)

            .background(LinearGradient(gradient: Gradient(colors: [Color(#colorLiteral(red: 1, green: 1, blue: 1, alpha: 1)), Color(#colorLiteral(red: 0.8705882353, green: 0.8941176471, blue: 0.9450980392, alpha: 1))]), startPoint: .top, endPoint: .bottom))
            .clipShape(RoundedRectangle(cornerRadius: isDragging ? (55 - abs(dragValue.height) / 10) : 55, style: .continuous))
            .offset(x: dragValue.width, y: dragValue.height)

            .shadow(color: Color.black.opacity(0.2), radius: 20, x: 0, y: 20)
            .padding(.horizontal, 30)
            .gesture(
                DragGesture().onChanged { value in
                    self.dragValue = value.translation
                    self.isDragging = true
                }
                .onEnded { value in
                    self.dragValue = .zero
                    self.isDragging = false
                }
            )
                .animation(.spring(response: 0.5, dampingFraction: 0.6, blendDuration: 0))
        }

    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

2 Answers

Here is possible solution based on asymptotic curve (if somebody find it helpful).

Tested with Xcode 11.4 / iOS 13.4

Update: retested with Xcode 13.4 / iOS 15.5

demo

Changed part

DragGesture().onChanged { value in
    let limit: CGFloat = 200        // the less the faster resistance 
    let xOff = value.translation.width
    let yOff = value.translation.height
    let dist = sqrt(xOff*xOff + yOff*yOff);
    let factor = 1 / (dist / limit + 1)
    self.dragValue = CGSize(width: value.translation.width * factor,
                        height: value.translation.height * factor)
    self.isDragging = true
}

A guy on reddit helped me with this issue, I just had to change .gesture to this:

.gesture(
    DragGesture().onChanged { dragValue in
        let translation: CGSize = dragValue.translation
        let multiplier: CGFloat = 15
        let incrementalW: CGFloat = log(abs(translation.width)) * multiplier
        let incrementalH: CGFloat = log(abs(translation.height)) * multiplier
        let incremental = CGSize(
            width: translation.width > 0 ? incrementalW : -incrementalW,
            height: translation.height > 0 ? incrementalH : -incrementalH
        )
        let resistance = CGSize(
            width: abs(translation.width) < abs(incremental.width) ? translation.width : incremental.width,
            height: abs(translation.height) < abs(incremental.height) ? translation.height : incremental.height
        )

        self.dragValue = resistance
        self.isDragging = true
    }
    .onEnded { dragValue in
        self.dragValue = .zero
        self.isDragging = false
    }
)
Related