I am exploring the use of drag gestures.

Below is a small test code to see what happens and because I want to know the location on the screen / map.

The first time I drag, it works fine, but when I try to drag again, the object jumps around and I get screen locations that are completely wrong.

I want to drag the object and, after stopping, continue dragging it further.

Ultimately, the code will be used, in part, in an app with a map.

import SwiftUI

struct ContentView: View {
    @State private var offSet: CGSize = .zero
    
    var body: some View {
        VStack {
            Image(systemName: "globe")
                .imageScale(.large)
                .foregroundStyle(.tint)
                .offset(x: offSet.width, y: offSet.height)
                .gesture(
                    DragGesture()
                        .onChanged({ value in
                            offSet = value.translation
                        })
                        .onEnded({ value in
                            offSet = value.translation
                            
                            print("Location stopt: \(value.location)")
                        })
                )
        }
        .padding()
    }
}

#Preview {
    ContentView()
}

5 Replies 5

You are saving the offset of the current drag gesture. So when a new gesture is started, it replaces the offset of the previous gesture. That's why you see a jump.

Try saving the offset from the previous gesture. Then, when a gesture ends, update the saved offset.

I don't quite understand what you mean.

Do you have an example?

Thanks in advance.

Note, dragging something on a Map is very different than what you have with your current code. Ask a question how to drag a "globe" image on a Map in SwiftUI. Show some attempt at doing that. I've got just the code for that.

I'm going to do that later this week.
I'll be back for that.

Thanks so far :)

You need to be aware that a DragGesture will always start at (0,0), so the value.translation will always be relative to (0,0), and not relative to your saved offset.

This becomes obvious if you also add a print statement in the .onChanged:

print("Translation: \(value.translation)")

I want to drag the object and, after stopping, continue dragging it further.

To "continue dragging it further" means that you must add the current translation (the one you started after stopping, and which started from (0,0) again) to the offset/position of where you last stopped.

The problem in your code is that the .onChange always updates the offset state with the current translation (which always starts at zero). So do you see the conflict? You're saving the final value to the offset state, but then .onChanged overwrites that saved value when you drag again with the new (0-based) translation value.

The solution is to use two different states - one that tracks the current translation value, and another one that saves the final position. You can do that with two @State properties, but I suggest you use a @State and a @GestureState, since a gesture state always resets to zero at the end of the gesture.

Note that when using a @GestureState, you need to use the .updating method of the gesture, rather than .onChange.

Here's the working code to try:

import SwiftUI

struct DragGestureView: View {
    @State private var imagePosition: CGSize = .zero
    @GestureState private var currentOffset: CGSize = .zero

    @State private var offSet: CGSize = .zero
    var body: some View {
        VStack {
            Image(systemName: "globe")
                .imageScale(.large)
                .foregroundStyle(.tint)
                .offset(x: imagePosition.width + currentOffset.width,
                        y: imagePosition.height + currentOffset.height)
                .gesture(
                    DragGesture()
                        .updating($currentOffset) { value, state, transaction in // <- use updating, which also resets at the end of the gesture
                            state = value.translation // <- state here is basically an alias for currentOffset
                        }
                        .onEnded { value in
                            //Add the current translation to the previously saved position
                            imagePosition.width += value.translation.width
                            imagePosition.height += value.translation.height
                        }
                )
        }
        .padding()
    }
}

#Preview {
    DragGestureView()
}

Your Reply

By clicking “Post Your Reply”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.