Dragging a SwiftUI View
Using `DragGesture` a floating movable `Button` can be easily implemented. Use the `.offset()` view modifier to shift the position of the movable view. Use the `DragGesture` to record dragging events and adjust the offset for the movable view.
When the drag gesture begins, a start offset is stored. Subsequent drag translations are used to calculate the current offset based on the start value and the dragging translation. When the drag gesture ends, the busy state is reset to be ready for the next drag.
import SwiftUI
struct SampleDraggingOverlayView: View {
@State private var dragOffset: CGSize = .zero
@State private var dragStart: CGSize = .zero
@State private var dragBusy: Bool = false
var body: some View {
Text("Sample Dragging Overlay")
.font(.largeTitle)
VStack {
Text("You can drag the floating button")
.padding()
Spacer()
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background {
Color(white: 0.95)
.ignoresSafeArea(edges: [.leading, .trailing, .bottom])
}
.overlay {
GeometryReader { geometry in
HStack(spacing: 12) {
Text("Drag me")
Button(action: {
reset(geometry.size)
}, label: {
Image(systemName: "sun.max")
})
}
.onAppear(perform: {
reset(geometry.size)
})
.padding()
.background {
Color(white: 0.99)
}
.clipShape(Capsule())
.shadow(radius: 6)
.overlay(content: {
Capsule()
.inset(by: 1)
.stroke(dragBusy ? Color.red : Color.blue, lineWidth: 2)
})
.offset(dragOffset)
.gesture(
DragGesture()
.onChanged({ value in
if !dragBusy {
dragStart = dragOffset
dragBusy = true
}
let newOffset = CGSize(
width: dragStart.width + value.translation.width,
height: dragStart.height + value.translation.height)
dragOffset = newOffset
})
.onEnded({ value in
dragBusy = false
dragStart = .zero
})
)
.padding(8)
}
}
}
private func reset(_ size: CGSize) {
dragOffset = CGSize(width: size.width / 2 * 0.3, height: size.height / 2 * 0.3)
}
}
#Preview {
SampleDraggingOverlayView()
}