Here I used @State CGSize values to represent both the viewState and the dragState
struct Example1: View {
@State var viewState: CGSize = .zero
@State var dragState: CGSize = .zero
var body: some View {
let dragGesture = DragGesture()
.onChanged { (value) in
self.dragState = value.translation
}.onEnded { (value) in
self.viewState.width += value.translation.width
self.viewState.height += value.translation.height
self.dragState = .zero
}
return Circle().foregroundColor(.blue).frame(width: 20, height: 20)
.offset(x: dragState.width + viewState.width,
y: dragState.height + viewState.height)
.gesture(dragGesture)
}
}
This time I made the structure similar to the Composing SwiftUI Gestures article but without the long press gesture.
struct Example2: View {
@State var viewState: CGSize = .zero
@GestureState var dragState: DragState = .inactive
enum DragState {
case inactive
case active(translation: CGSize)
var translation: CGSize {
switch self {
case .active(translation: let translation):
return translation
default:
return .zero
}
}
}
var body: some View {
let dragGesture = DragGesture()
.updating($dragState) { (value, state, _) in
state = .active(translation: value.translation)
}.onEnded { (value) in
self.viewState.width += value.translation.width
self.viewState.height += value.translation.height
}
return Circle().foregroundColor(.red).frame(width: 20, height: 20)
.offset(x: dragState.translation.width + viewState.width,
y: dragState.translation.height + viewState.height)
.gesture(dragGesture)
}
}
The DragGesture.Value
struct provides access to a bunch of data about the current drag.
time
- The time associated with the current event.location
- The location of the current event.startLocation
- The location of the first event.translation
- The total translation fram the first event to the current eventpredictedEndLocation
- A prediction of where the final location would be if dragging stopped now, based on the current drag velocity.predictedEndTranslation
- A prediction of what the final translation would be if dragging stopped now, based on the current drag velocity.
enum FullDragState {
case inactive
case active(
time: Date,
location: CGPoint,
startLocation: CGPoint,
translation: CGSize,
predictedEndLocation: CGPoint,
predictedEndTranslation: CGSize)
var time: Date? {
switch self {
case .active(let time, _, _, _, _, _):
return time
default:
return nil
}
}
var location: CGPoint {
switch self {
case .active(_, let location, _, _, _, _):
return location
default:
return .zero
}
}
var startLocation: CGPoint {
switch self {
case .active(_, _, let startLocation, _, _, _):
return startLocation
default:
return .zero
}
}
var translation: CGSize {
switch self {
case .active(_, _, _, let translation, _, _):
return translation
default:
return .zero
}
}
var predictedEndLocation: CGPoint {
switch self {
case .active(_, _, _, _, let predictedEndLocation, _):
return predictedEndLocation
default:
return .zero
}
}
var predictedEndTranslation: CGSize {
switch self {
case .active(_, _, _, _, _, let predictedEndTranslation):
return predictedEndTranslation
default:
return .zero
}
}
var isActive: Bool {
switch self {
case .active(_, _, _, _, _, _):
return true
default:
return false
}
}
}
struct DragValues: Equatable {
var time: Date?
var location: CGPoint
var startLocation: CGPoint
var translation: CGSize
var predictedEndLocation: CGPoint
var predictedEndTranslation: CGSize
static func zero() -> DragValues{
return DragValues(time: nil,
location: .zero,
startLocation: .zero,
translation: .zero,
predictedEndLocation: .zero,
predictedEndTranslation: .zero)
}
}
enum DragVelocity {
case inactive
case active(time: Date,
location: CGPoint,
startingLocation: CGPoint,
translation: CGSize,
velocity: CGSize)
var time: Date? {
switch self {
case .active(let time, _, _, _, _):
return time
default:
return nil
}
}
var location: CGPoint {
switch self {
case .active(_, let location, _, _, _):
return location
default:
return .zero
}
}
var startLocation: CGPoint {
switch self {
case .active(_, _, let startLocation, _, _):
return startLocation
default:
return .zero
}
}
var translation: CGSize {
switch self {
case .active(_, _, _, let translation, _):
return translation
default:
return .zero
}
}
var velocity: CGSize {
switch self {
case .active(_, _, _, _, let velocity):
return velocity
default:
return .zero
}
}
var isActive: Bool {
switch self {
case .active(_, _, _, _, _):
return true
default:
return false
}
}
}
struct DragVelocityExample: View {
@GestureState var dragState: DragVelocity = .inactive
@State var viewState: DragValues = .zero()
struct DragValues: Equatable {
var time: Date?
var location: CGPoint
var startLocation: CGPoint
var translation: CGSize
var velocity: CGSize
static func zero() -> DragValues{
return DragValues(time: nil,
location: .zero,
startLocation: .zero,
translation: .zero,
velocity: .zero)
}
}
var body: some View {
let dragGesture = DragGesture()
.updating($dragState, body: { (value, state, transaction) in
if state.time == nil {
state = .active(time: value.time,
location: value.location,
startingLocation: value.startLocation,
translation: value.translation,
velocity: .zero)
} else {
let vX = (value.location.x-state.location.x)/CGFloat((state.time?.timeIntervalSince(value.time) ?? 1))
let vY = (value.location.y-state.location.y)/CGFloat((state.time?.timeIntervalSince(value.time) ?? 1))
state = .active(time: value.time,
location: value.location,
startingLocation: value.startLocation,
translation: value.translation,
velocity: CGSize(width: vX, height: vY))
}
})
.onEnded { (value) in
self.viewState = DragValues(time: value.time,
location: value.location,
startLocation: value.startLocation,
translation: CGSize(width: value.translation.width + self.viewState.translation.width,
height: value.translation.height + self.viewState.translation.height),
velocity: .zero)
}
return ZStack {
Circle().foregroundColor( dragState.velocity.width > 40 ? .red: .blue)
.frame(width: 50 , height: 50 )
.offset(x: dragState.translation.width + viewState.translation.width,
y: dragState.translation.height + viewState.translation.height)
.animation(.linear)
.gesture(dragGesture)
}
}
}