George Lee
George Lee

Reputation: 882

SwiftUI: Gesture and Offset Are Not Working As Intended

I am using offset and gesture modifiers to move a circle around the screen. When I use this code, everything works as expected:

import SwiftUI

struct MovingCircle: View {

@State private var dragged = CGSize.zero

var body: some View {
    Circle()
        .offset(x: self.dragged.width)
        .frame(width: 20, height: 20)
        .gesture(DragGesture()
            .onChanged{ value in
                self.dragged = value.translation
        }
        .onEnded{ value in
            self.dragged = CGSize.zero
            }
    )
 }
}

However, I do not want to have the circle reset to the original position onEnded. I would like it to remain in place and then be moved again on dragging. When I use the following code, I lose the ability to move the circle again upon re-dragging and it remains in place:

import SwiftUI

struct MovingCircle: View {

@State private var dragged = CGSize.zero

var body: some View {
    Circle()
        .offset(x: self.dragged.width)
        .frame(width: 20, height: 20)
        .gesture(DragGesture()
            .onChanged{ value in
                self.dragged = value.translation
        }
        .onEnded{ value in
            self.dragged = value.translation
            }
    )
}
}

What is the cause of this, have I encountered some bug or have I coded it incorrectly?

Upvotes: 7

Views: 6262

Answers (2)

user3069232
user3069232

Reputation: 8995

Swift 5.x iOS 13

A Less elegant solution, if you don't care to see the object dragged; you just want to be able to drag it; maybe makes more sense if you got a lot going on and you're looking to save CPU.

Obviously you need to find a better starting position for it then absolute zero.

import SwiftUI

var intern:CGPoint = CGPoint(x:0,y:0)

enum DragState {
  case inactive
  case dragging(translation: CGSize)
}

struct ContentView: View {

@State var position:CGPoint = CGPoint(x:0,y:0)
@GestureState private var dragState = DragState.inactive

var body: some View {
 ZStack {
    Circle()
   .frame(width: 24, height: 24, alignment: .center)
 }.offset(x: self.dragOffset.width, y: self.dragOffset.height)
  .gesture(DragGesture(coordinateSpace: .global)
    .updating($dragState, body: { (drag, state, translation) 
      intern = drag.location
    })
    .onEnded { ( value ) in
      self.position = intern
    }
  ).position(position)
 }
}

Upvotes: -1

kontiki
kontiki

Reputation: 40509

First, to understand the problem, add a .border(Color.red) to the .frame() modifier:

.frame(width: 20, height: 20).border(Color.red)

You'll see that when the dot is moved, its frame remains in place. That is why later, it won't respond to gestures. The "tappable" area no longer matches the dot. And because the content area is now empty, it is no longer "tappable".

To make the frame move with the dot, invert the order. The .offset() should come later:

.frame(width: 20, height: 20).border(Color.red)
.offset(x: self.dragged.width)

Finally, you will see that after each .onEnded(), the whole thing resets back. One way to solve it, is by accumulating how much you dragged in previous gestures:

struct MovingCircle: View {

    @State private var dragged = CGSize.zero
    @State private var accumulated = CGSize.zero

    var body: some View {
        Circle()
            .frame(width: 20, height: 20).border(Color.red)
            .offset(x: self.dragged.width)

            .gesture(DragGesture()
                .onChanged{ value in
                    self.dragged = CGSize(width: value.translation.width + self.accumulated.width, height: value.translation.height + self.accumulated.height)
            }
            .onEnded{ value in
                self.dragged = CGSize(width: value.translation.width + self.accumulated.width, height: value.translation.height + self.accumulated.height)
                self.accumulated = self.dragged
                }
        )
    }
}

Upvotes: 18

Related Questions