不知道大家有沒有發現,iPhone 主畫面左右滑動切換頁面,或是抖音用上下滑動切換頁面的操作,與上一節圖片輪播非常類似,只是自動輪播改成手動輪播。手動輪播要如何做呢?

如果仔細觀察iPhone主畫面或抖音的手勢操作,會發現一個關鍵細節:

因此,手動輪播其實有兩段行程,第一段是用「拖曳」手勢,控制左右滑動或上下滑動;當拖曳手勢結束時,若滑動速率不夠,則回到原畫面,若速率夠快,則啟動第二段行程,自動繼續滑到下一頁停止。概念如下圖:

截圖 2022-06-29 上午9.27.53.png

第一段手動行程比較簡單,用「拖曳」手勢來控制畫布的左右滑動,當手勢結束時,測量水平滑動的速度,若速率小於300(點/秒),則回到起始點,若大於300(點/秒)則停在原處不動(接續第二段自動行程)。

第一段程式如下:

// 4-9c 手動輪播(拖曳手勢) -- 第一段
// Created by Heman, 2022/06/24
import PlaygroundSupport
import SwiftUI

struct 畫布: View {
    @State var 拖曳位移: CGSize = .zero
    @State var 拖曳開始 = Date()
    var 拖曳: some Gesture {
        DragGesture()
            .onChanged { 拖曳參數 in
                if 拖曳位移 == .zero { 拖曳開始 = 拖曳參數.time }
                拖曳位移 = 拖曳參數.translation
            }
            .onEnded { 拖曳參數 in
                let 時間差 = 拖曳參數.time.timeIntervalSince(拖曳開始)
                let 速度 = 拖曳參數.translation.width / 時間差
                if abs(速度) < 300 {
                    拖曳位移 = .zero
                }
                // print(時間差, 拖曳參數.translation)
                // print(速度)
            }
    }
    var body: some View {
        Canvas { 圖層, 尺寸 in
            let 寬 = 尺寸.width
            let 高 = 尺寸.height
            let 半徑 = min(寬/2, 高/2)
            let 中心 = CGPoint(x: 寬/2, y: 高/2)
            圖層.translateBy(x: 拖曳位移.width, y: 0)
            var 畫筆 = Path()
            for i in [-1.0, 0.0, 1.0] {
                let 圓心 = CGPoint(x: 中心.x + 寬*i, y: 中心.y)
                let 起點 = CGPoint(x: 中心.x + 寬*(i+0.5), y: 中心.y)
                畫筆.move(to: 起點)
                畫筆.addArc(
                    center: 圓心, 
                    radius: 半徑, 
                    startAngle: .zero, 
                    endAngle: .degrees(360), 
                    clockwise: false)
            }
            圖層.stroke(畫筆, with: .color(.cyan), lineWidth: 20.0)
        }
        .border(.red)
        .gesture(拖曳)
        Circle()
            .frame(height: 200)
            .border(.blue)
            .foregroundColor(.blue)
            .offset(拖曳位移)
            .gesture(拖曳)
    }
}

PlaygroundPage.current.setLiveView(畫布())

先看看執行的過程:

https://youtu.be/XWIwmFOScL4

上方較大的空心圓是用畫布(Canvas)所繪,下方較小的實心圓則是Circle()視圖,兩者都顯示視框邊界(.border()),也同樣套用「拖曳」手勢(.gesture()),可以藉此觀察到幾個特點:

  1. 手勢是作用在整個視框範圍,而不是只有圖形上。
  2. 拖曳時,兩個圓形共享「拖曳位移」,所以會一起移動。
  3. 下方Circle()透過.offset()套用拖曳位移,隨著手勢上下左右均可活動,而且是整個視框移動。
  4. 上方空心圓形的移動,是透過畫布的計算,將拖曳位移轉為圖層位移,只取其水平值(x: 拖曳位移.width),所以只會左右移動,而且移動時視框範圍不會跑掉。