# .tapToShake()

[SwiftUI](../../..) ⟩ [Animations](../..) ⟩ [Animatable](..) ⟩ [Modifiers]() ⟩&#x20;

![](https://1830103165-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-M5-JmwCZMKh_d7RfBaN%2Fuploads%2Fx8oNchAq38TriFJzgiPU%2FtapToShake.gif?alt=media\&token=0b743f41-f9b2-4c71-834e-f1d19cb32e1a)

{% tabs %}
{% tab title="💈範例" %}
{% hint style="success" %}
* 用 <mark style="color:purple;">**withAnimation()**</mark> 來設定 "<mark style="color:red;">**keyframe**</mark>" 的參數值。
* 用 [](<> "mention") 來計算內插於 <mark style="color:red;">**"keyframe" 之間**</mark>的 "frame"。
{% endhint %}

{% hint style="info" %}
本例用兩種方式 (`TapToShake`, `TapToShake2`) 來設定 "<mark style="color:red;">**keyframe**</mark>" 的參數值。
{% endhint %}

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

{% tab title="📗 參考" %}
* SwiftOnTap ⟩ [Animatable](https://swiftontap.com/animatable) ⭐️
{% endtab %}
{% endtabs %}
