โจlong press + drag
Last updated
Last updated
SwiftUI โฉ Gestures โฉ Combine โฉ .sequenced โฉ long press + drag
โญ๏ธ ใ้ทๆๅพๆๅ ่จฑๆๆณใ (Long Press + Drag)
่ฆๆไฝๆ็บไธ็งๅพ๏ผๆ่ฝ้ๅงๆๆณ๏ผๅฆๆๆๅๆๆณ๏ผๆๅขๆ็ดๆฅ่ฎๆ .inactive ็ๆ ใ
๐ ๅ่๏ผMastering SwiftUI, Ch. 17: Using Gestures (p.380)
import SwiftUI
import PlaygroundSupport
PlaygroundPage.current.setLiveView(ContentView())
struct ContentView: View {
var body: some View {
VStack {
Color.pink
.frame(50)
.longpressDraggable() // โญ๏ธ long press + drag
}
.border(.secondary)
}
}
// -----------------------------
// ๐ LongPressDraggable
// -----------------------------
// view modifier
struct LongPressDraggable: ViewModifier {
// โญ๏ธ (long press + drag) gesture state
// โ๏ธ @GestureState will reset itself when gesture ends.
// โข `DragState` defined below
@GestureState private var state = DragState.inactive
// โญ๏ธ vew offset
@State private var offset = CGSize.zero
// โญ๏ธ modify view content
func body(content: Content) -> some View {
content
// โญ๏ธ dimmer/smaller when pressed
.opacity(state.isPressing ? 0.5 : 1)
.scaleEffect(state.isPressing ? 0.8 : 1)
.animation(.easeInOut, value: state.isPressing)
// โญ๏ธ change hue when starting dragging
.hueRotation(state.isDragging ? .degrees(90) : .degrees(0))
// โญ๏ธ total offset = view offset + drag offset
.offset(offset + state.offset)
// โญ๏ธ apply long press + drag gesture
.gesture(longpressDrag)
}//end: body
/// โญ๏ธ long press + drag
var longpressDrag: some Gesture {
// -------------------------------------------------
// โญ๏ธ Long Press + Drag
// โญ๏ธ long press for at least 1 sec before dragging
// -------------------------------------------------
LongPressGesture(minimumDuration: 1.0)
.sequenced(before: DragGesture())
// โญ๏ธ update gesture state
// โข value: SequenceGesture<LongPressGesture, DragGesture>.Value
// โข state: LongPressDraggable.DragState
// โข transaction: animation "context" (ignored)
.updating($state) { value, state, _ in
// value = (longpressValue, dragValue)
switch value {
// โญ๏ธ (long) press detected
case .first(true):
state = .pressing
// โญ๏ธ long press confirmed,
// โญ๏ธ drag may or may NOT (yet) be detectedโ๏ธ
case .second(true, let drag):
state = .dragging(offset: drag?.translation ?? .zero)
// โญ๏ธ ignore other cases
default: break
}
}
// โญ๏ธ long press + drag gesture ended
.onEnded { value in
// โญ๏ธ if drag detected, update view offset
guard case .second(true, let drag?) = value else { return }
offset += drag.translation
}
}
}
extension LongPressDraggable {
// ----------------------------------------------------------------
// ๐ธ ๆณจๆ๏ผ
// enum ไธ่ฝๅฝๅ็บ `State` (ๆ `GestureState`)๏ผๅฆๅๆ็ข็้ฏ่ชค๏ผ
// ใ โ๏ธ Error: enum 'State' cannot be used as an attribute ใ
// ๆญคๆ @State (ๆ @GestureState) ๅฐฑๆฒ่พฆๆณไฝฟ็จโ๏ธ
// ----------------------------------------------------------------
/// โญ๏ธ LongPressDraggable.GestureState:
/// gesture state for LongPress -> Drag
enum DragState: CustomStringConvertible {
// state cases
case inactive
case pressing
case dragging(offset: CGSize)
// ---------------------------
// instance properties
// ---------------------------
/// state.offset
var offset: CGSize {
switch self {
case .inactive, .pressing : return CGSize.zero
case .dragging(let offset): return offset
}
}
/// state.isPressing
var isPressing: Bool {
switch self {
case .pressing: return true
default : return false
}
}
/// state.isPressing
var isDragging: Bool {
switch self {
case .dragging: return true
default : return false
}
}
/// CustomStringConvertible
var description: String {
switch self {
case .pressing: return ".pressing"
case .dragging: return ".dragging"
case .inactive: return ".inactive"
}
}
}
}
// ---------------------------------------
// ๐ View + .longpressDraggable()
// ---------------------------------------
extension View {
func longpressDraggable() -> some View {
modifier(LongPressDraggable())
}
}
Gesture โฉ .sequenced(before:) -> SequenceGesture
<Self, Other> where Other : Gesture
SequenceGesture (struct)
SequenceGesture.Value (enum)
combine Long Press and Drag.
use custom enum (with associated values) for its GestureState.