Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 626
George Lee Avatar asked Aug 14 '19 04:08

George Lee


1 Answers

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
                }
        )
    }
}
like image 114
kontiki Avatar answered Oct 11 '22 23:10

kontiki