โœจ.tapToShake()

SwiftUI โŸฉ Animations โŸฉ Animatable โŸฉ Modifiers โŸฉ

  • ็”จ withAnimation() ไพ†่จญๅฎš "keyframe" ็š„ๅƒๆ•ธๅ€ผใ€‚

  • ็”จ Animatable Modifiers ไพ†่จˆ็ฎ—ๅ…งๆ’ๆ–ผ "keyframe" ไน‹้–“็š„ "frame"ใ€‚

ๆœฌไพ‹็”จๅ…ฉ็จฎๆ–นๅผ (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