How to Make a Button Draggable/Movable with Swiftui

How to make a button draggable/movable with SwiftUI?

Here is a demo of possible approach.

Update: re-tested with Xcode 13.3 / iOS 15.4

demo

See also notes inline.

struct CircleButton: View {
@State private var dragAmount: CGPoint?
var body: some View {
GeometryReader { gp in // just to center initial position
ZStack {
Button(action: self.performAction) {
ZStack {
Circle()
.foregroundColor(.blue)
.frame(width: 100, height: 100)
Text("Move me")
.foregroundColor(.white)
.font(.system(.caption, design: .serif))
}
}
// Use .none animation for glue effect
.animation(.default, value: dragAmount)
.position(self.dragAmount ?? CGPoint(x: gp.size.width / 2, y: gp.size.height / 2))
.highPriorityGesture( // << to do no action on drag !!
DragGesture()
.onChanged { self.dragAmount = $0.location})
}
.frame(maxWidth: .infinity, maxHeight: .infinity) // full space
}
}

func performAction() {
print("button pressed")
}
}

I try to create a Button with Drag Gesture like the bubble messenger of android

This is nice!

The .position expands the view (and the button) to maximum size, that's why you can click everywhere.

The easiest workaround is not using a Button, but making the image itself tappable – see code below.

PS: You don't have to use @GestureState if you manage the dragging by yourself with onChanged and onEnded – you did a double job. I commented out everything you don't need ;)

struct ContentView: View {

private var bround = UIScreen.main.bounds
@State var isShow = false
@State private var location = CGPoint(x: 60, y: 60)
// @GestureState private var startLocation: CGPoint? = nil

var simpleDrag: some Gesture {
DragGesture()
.onChanged { value in
// var newLocation = location
// newLocation.x += value.translation.width
// newLocation.y += value.translation.height
// self.location = newLocation
self.location = value.location
}
.onEnded{ value in
if(value.translation.width > bround.size.width/2) {
self.location.x = bround.size.width - 30
}
else {
self.location.x = 30
}
}
// .updating($startLocation) { (value, startLocation, transaction) in
// startLocation = startLocation ?? location
// }
}

var body: some View {

Image(systemName: "bubble.right.fill")
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 50)
.animation(.easeInOut, value: location)
.position(location)
.gesture(simpleDrag)

.onTapGesture {
self.isShow.toggle()
if(isShow) {
self.location.x = bround.width/2
}
else {
self.location.x = 30
}
}
}
}

SwiftUI: Place and drag object immediately

A possible approach is to handle drag and creation in "area" (background container), while "item" views are just rendered at the place where needed.

Find below a simplified demo (used Xcode 13.2 / iOS 15.2), see also comments in code snapshot.

demo

Note: tap detection in already "existed" item is an exercise for you.

extension CGPoint: Identifiable { // just a helper for demo
public var id: String { "\(x)-\(y)" }
}

struct TapAndDragDemo: View {
@State private var points: [CGPoint] = [] // << persistent
@State private var point: CGPoint? // << current

@GestureState private var dragState: CGSize = CGSize.zero

var body: some View {
Color.clear.overlay( // << area
Group {
ForEach(points) { // << stored `items`
Rectangle()
.frame(width: 24, height: 24)
.position(x: $0.x, y: $0.y)
}
if let curr = point { // << active `item`
Rectangle().fill(Color.red)
.frame(width: 24, height: 24)
.position(x: curr.x, y: curr.y)
}
}
)
.contentShape(Rectangle()) // << make area tappable
.gesture(DragGesture(minimumDistance: 0.0)
.updating($dragState) { drag, state, _ in
state = drag.translation
}
.onChanged {
point = $0.location // track drag current
}
.onEnded {
points.append($0.location) // push to stored
point = nil
}
)
}
}

SwiftUI: Increase tap/drag area for user interaction

Add a .contentShape(Rectangle()) after the frame.

SwiftUI, create floating button and need can move anywhere like Assistive Touch

According to my understanding of your question, you want a floating button over WKWebview that you can drag anywhere you want.
Initially, I tried with Button but there was a conflict with the tap gesture and drag gesture. Thus I have used Image for showing icon and added a tap gesture and drag gesture to it.

Below is the code:

import SwiftUI
import WebKit

struct FloatingFab : View {
var body: some View {
ZStack {
WebView(request: URLRequest(url: URL(string: "https://stackoverflow.com/")!))

FloatingView()
}
}
}

struct WebView : UIViewRepresentable {

let request: URLRequest

func makeUIView(context: Context) -> WKWebView {
return WKWebView()
}

func updateUIView(_ uiView: WKWebView, context: Context) {
uiView.load(request)
}

}

struct FloatingView: View {

@State private var currentPosition: CGSize = .zero
@State private var newPosition: CGSize = .zero

var body: some View {

Image(systemName: "plus.circle.fill")
.resizable()
.foregroundColor(.blue)
.frame(width: 50, height: 50)
.offset(x: self.currentPosition.width, y: self.currentPosition.height)
.onTapGesture(perform: {
debugPrint("Perform you action here")
})
.gesture(DragGesture()
.onChanged { value in
self.currentPosition = CGSize(width: value.translation.width + self.newPosition.width,
height: value.translation.height + self.newPosition.height)
}
.onEnded { value in
self.currentPosition = CGSize(width: value.translation.width + self.newPosition.width,
height: value.translation.height + self.newPosition.height)

self.newPosition = self.currentPosition
}
)
}
}


Related Topics



Leave a reply



Submit