設定手勢

學習如何控制手勢 (gestures) 的觸控事件 (events)。本節新增了「拖曳」主卡片時,會產生的行為:「拖曳主卡時,所有卡片都會跟著跑;拖曳停止時,所有卡片彈回原來的位置」。

重點

  • 用 view.onTapGesture() 來設定「點擊」的觸控事件。

  • 用 view.gesture() 來設定一般的觸控事件 (如:drag, pinch, long press 等)。

  • 用 drag.onChanged() 來控制「拖曳發生時」的行為,用 drag.onEnded() 來控制「拖曳結束時」的行為。

  • 拖曳時,系統會提供一個 DragGesture.Value (struct) 的值供我們使用,其中的 translation 屬性是「拖曳的平移量」 (類別為 CGSize)。(🤔 謎之音:真不知 Apple 的工程師在想什麼,平移量是一個「向量」,為什麼不是 CGPointCGVector 呢?)

/*
 Design+Code - SwiftUI for iOS 13
 https://designcode.io/swiftui-gestures-and-events
 */

import SwiftUI
import PlaygroundSupport

struct ContentView: View {
    
    // view states
    @State private var show = false              // ⭐️ 控制要不要「展開卡片」
    @State private var translation = CGSize.zero // ⭐️ 控制拖曳主卡時的「位移量」
    
    // view body
    var body: some View {
        ZStack {
            // 標題
            TitleView()
                .blur(radius: show ? 20 : 0)
                .animation(.default)
                
            // 底卡一
            BackCard(.purple)
                .scaleEffect(0.9)
                .offset(x: 0, y: show ? -400 : -40)   // ⚠️ 注意:
                .offset(translation)                  //    平移(offset)愛設定幾次都可以!
                .rotationEffect(.degrees(show ? 0 : 10))
                .rotation3DEffect(.degrees(10), axis: (x: 1, y: 0, z: 0))
                .blendMode(.hardLight)
                .animation(.easeInOut(duration: 0.5))
            
            // 底卡二
            BackCard(.pink)
                .scaleEffect(0.95)
                .offset(x: 0, y: show ? -200 : -20)
                .offset(translation)
                .rotationEffect(.degrees(show ? 0 : 5))
                .rotation3DEffect(.degrees(5), axis: (x: 1, y: 0, z: 0))
                .blendMode(.hardLight)
                .animation(.easeInOut(duration: 0.3))
            
            // 主卡
            Card()
                // 1. 設定平移量
                .offset(translation)
                .blendMode(.hardLight)
                // 2. 設定彈回動畫
                .animation(.spring(response: 0.3, dampingFraction: 0.6, blendDuration: 0))
                
                // ⭐️ 點擊主卡時,展開或收合卡片。
                .onTapGesture { self.show.toggle() }
                
                // ⭐️ 拖曳主卡時,主卡會跟著跑。   (1.2.3.)
                //    停止拖曳時,主卡會彈回原位。 (1.2.4.)
                .gesture(
                    DragGesture().onChanged{ value in // DragGesture.Value
                        // 拖曳的「位移量」竟然是個 CGSize,豈不怪哉 🤔⁉️
                        self.translation = value.translation  // 3. 紀錄平移量
                        self.show = true
                    }.onEnded{ value in
                        self.translation = .zero              // 4. 清除平移量
                        self.show = false
                    }
            )
            
            BottomCard()
                .blur(radius: show ? 20 : 0)
                .animation(.default)
            
        } // ZStack (container)
            .frame(maxWidth: .infinity, maxHeight: .infinity)
            .background(Color.white)
    }
}

PlaygroundPage.current.setLiveView(ContentView())

Last updated