long press + drag
Last updated
Was this helpful?
Last updated
Was this helpful?
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.