# long press + drag

[SwiftUI](/ios/swiftui.md) ⟩ [Gestures](/ios/swiftui/gestures.md) ⟩ [Combine](/ios/swiftui/gestures/combine.md) ⟩ [.sequenced](/ios/swiftui/gestures/combine/sequenced.md) ⟩ long press + drag

{% tabs %}
{% tab title="💾 程式" %}
{% hint style="info" %}
⭐️ 「<mark style="color:red;">**長按**</mark>後才允許<mark style="color:red;">**拖曳**</mark>」 ([Long Press](/ios/swiftui/gestures/long-press.md) + [Drag](/ios/swiftui/gestures/drag.md))

要按住持續一秒後，才能開始拖曳，如果提前拖曳，手勢會直接變成 .inactive 狀態。
{% endhint %}

{% embed url="<https://youtu.be/yMk_z-TxgYw>" %}

📗 參考：[Mastering SwiftUI](https://www.appcoda.com/swiftui/), Ch. 17: Using Gestures (p.380)

```swift
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()) 
    }
}
```

{% endtab %}

{% tab title="📗 參考" %}

* [ ] [Mastering SwiftUI](https://www.appcoda.com/swiftui/), Ch. 17: Using Gestures (p.380)
* [x] [SwiftUI](https://developer.apple.com/documentation/swiftui) ⟩ [Gestures](https://developer.apple.com/documentation/swiftui/gestures) ⟩&#x20;
  * [x] [Adding Interactivity with Gestures](https://developer.apple.com/documentation/swiftui/adding-interactivity-with-gestures)&#x20;
  * [x] [Composing SwiftUI Gestures](https://developer.apple.com/documentation/swiftui/composing-swiftui-gestures) ⭐️
    {% endtab %}

{% tab title="📘 手冊" %}

* [SwiftUI](https://developer.apple.com/documentation/swiftui) ⟩ [Gestures](https://developer.apple.com/documentation/swiftui/gestures) ⟩&#x20;
  * [Gesture](https://developer.apple.com/documentation/swiftui/gesture) ⟩ [.sequenced(before:)](https://developer.apple.com/documentation/swiftui/gesture/sequenced\(before:\)) -> [`SequenceGesture`](https://developer.apple.com/documentation/swiftui/sequencegesture)\<Self, Other> where Other : [`Gesture`](https://developer.apple.com/documentation/swiftui/gesture)
  * [SequenceGesture](https://developer.apple.com/documentation/swiftui/sequencegesture) (<mark style="color:red;">**struct**</mark>)
    * [SequenceGesture.Value](https://developer.apple.com/documentation/swiftui/sequencegesture/value) (<mark style="color:green;">**enum**</mark>)
      {% endtab %}

{% tab title="👥 相關" %}

* combine [Long Press](/ios/swiftui/gestures/long-press.md) and [Drag](/ios/swiftui/gestures/drag.md).
* use custom [enum](/ios/swift/type/category/basic/enum.md) (with <mark style="color:purple;">**associated values**</mark>) for its [GestureState](/ios/swiftui/gestures/gesturestate.md).
  {% endtab %}
  {% endtabs %}


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://lochiwei.gitbook.io/ios/swiftui/gestures/combine/sequenced/longpressdrag.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
