.tapToShake()

SwiftUIAnimationsAnimatableModifiers

本例用兩種方式 (TapToShake, TapToShake2) 來設定 "keyframe" 的參數值。

import SwiftUI

struct ContentView: View {
    @State private var taps: CGFloat = 0
    var body: some View {
        VStack(spacing: 16) {
            Text("🍌 Shaking Banana 🍌")
                .foregroundColor(.yellow)
                .tapToShake()
            Text("👋 Shaking Hello 👋")
                .foregroundColor(.blue)
                .tapToShake2()
        }
        .font(.headline)
        .padding()
        .border(.secondary)
    }
}

// 👔 ShakeOffset
// calculate animation frame for shake effect at specific time `t`
struct ShakeOffset: Animatable, ViewModifier {
    
    var t: CGFloat = 0            // ⭐️ animation parameter
    let amplitude: CGFloat = 10
    
    // 🅿️ Animatable
    // ⭐️ animation parameter (for interpolation)
    var animatableData: CGFloat {
        get { t } 
        set { t = newValue }
    }
    
    // 🅿️ ViewModifier
    func body(content: Content) -> some View {
        content
            .offset(x: sin(t * .pi * 2) * amplitude)
    }
}

// 👔 TapToShake
struct TapToShake: ViewModifier {
    
    // ⭐️ "keyframe" parameter
    @State private var t: CGFloat = 0
    
    // 🅿️ ViewModifier
    func body(content: Content) -> some View {
        content
            // ⭐️ interpolates between "keyframes" using `ShakeOffset`
            .modifier(ShakeOffset(t: t))
            .onTapGesture {
                withAnimation(.linear(duration: 0.5)) {
                    // ⭐️ "keyframe" param 
                    //    t = 0, 3, 6, 9 ...
                    // 🔸 註解:
                    //    這種方式每次按都是先往右擺動。
                    t += 3
                }
            }
    }
}

// 👔 TapToShake2
struct TapToShake2: ViewModifier {
    
    @State private var flag = false
    
    // ⭐️ "keyframe" parameter
    var t: CGFloat { flag ? 1 : 0 }
    
    // 🅿️ ViewModifier
    func body(content: Content) -> some View {
        content
            // ⭐️ interpolates between "keyframes" using `ShakeOffset`
            .modifier(ShakeOffset(t: t))
            .onTapGesture {
                withAnimation(.linear(duration: 0.5).repeatCount(3, autoreverses: false)) {
                    // ⭐️ "keyframe" param toggles between 0 and 1
                    //    t = 0, 1, 0, 1 ...
                    // 🔸 註解:
                    //    這種方式第一次按會先往右擺動,第二次按會先往左擺動。
                    flag.toggle()
                }
            }
    }
}

// 🌀 View+
extension View {
    /// `view.tapToShake()`
    func tapToShake() -> some View {
        modifier(TapToShake())
    }
    
    /// `view.tapToShake2()`
    func tapToShake2() -> some View {
        modifier(TapToShake2())
    }
}

Last updated

Was this helpful?