问题
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?
回答1:
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
}
)
}
}
回答2:
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)
}
}
来源:https://stackoverflow.com/questions/57488175/swiftui-gesture-and-offset-are-not-working-as-intended