如何在SwiftUI的点击手势上放大或缩小按钮动画?

Yur*_*ury 4 ios swiftui

为按钮的凹凸效果制作动画的简单而常规的方法,但是在SwiftUI中却不简单。

我试图改变scale的tapGesture改性剂,但它不会有任何效果。我不知道如何制作动画链,可能是因为SwiftUI没有动画。所以我的天真做法是:

@State private var scaleValue = CGFloat(1)

...

Button(action: {
    withAnimation {
        self.scaleValue = 1.5
    }

    withAnimation {
        self.scaleValue = 1.0
    }
}) {
    Image("button1")
        .scaleEffect(self.scaleValue)
}

Run Code Online (Sandbox Code Playgroud)

显然,它不起作用,按钮图像会立即获得上一个比例值。

我的第二个想法是0.8在hold事件release发生时将标度更改为值,然后在事件发生后将标度1.2更改为,并在几毫秒后再次将标度更改为1.0。我猜想这种算法应该会产生更好的自然碰撞效果。但是我gesture在SwiftUI中找不到合适的结构来处理hold-n-release事件。

PS为了便于理解,我将描述hold-n-release算法的步骤:

  1. 比例值为 1.0
  2. 用户触摸按钮
  3. 按钮刻度变为 0.8
  4. 用户释放按钮
  5. 按钮刻度变为 1.2
  6. 延迟0.1秒
  7. 按钮比例返回到默认值 1.0

UPD:我发现了使用动画delay修改器的简单解决方案。但是我不确定这是正确和明确的。而且它不涵盖hold-n-release问题:

@State private var scaleValue = CGFloat(1)

...

Button(action: {
    withAnimation {
        self.scaleValue = 1.5
    }

    //
    // Using delay for second animation block
    //
    withAnimation(Animation.linear.delay(0.2)) {
        self.scaleValue = 1.0
    }
}) {
    Image("button1")
        .scaleEffect(self.scaleValue)
}

Run Code Online (Sandbox Code Playgroud)

UPD 2:我在溶液中发现它上面什么并不重要价值我传递的参数delay修改:0.2或1000将有同样的效果。也许是个错误

所以我用Timer实例代替了delay动画修改器。现在它按预期工作:

...

Button(action: {
    withAnimation {
        self.scaleValue = 1.5
    }

    //
    // Replace it
    //
    // withAnimation(Animation.linear.delay(0.2)) {
    //    self.scaleValue = 1.0
    // }
    //
    // by Timer with 0.5 msec delay
    //
    Timer.scheduledTimer(withTimeInterval: 0.5, repeats: false) { _ in
        withAnimation {
            self.scaleValue = 1.0
        }
    }
}) {
...
Run Code Online (Sandbox Code Playgroud)

UPD 3:直到我们等待苹果官方更新,实现两个事件的合适的解决方案之一touchStart,并touchEnd基于@average乔回答:

import SwiftUI

struct TouchGestureViewModifier: ViewModifier {

    let minimumDistance: CGFloat
    let touchBegan: () -> Void
    let touchEnd: (Bool) -> Void

    @State private var hasBegun = false
    @State private var hasEnded = false

    init(minimumDistance: CGFloat, touchBegan: @escaping () -> Void, touchEnd: @escaping (Bool) -> Void) {
        self.minimumDistance = minimumDistance
        self.touchBegan = touchBegan
        self.touchEnd = touchEnd
    }

    private func isTooFar(_ translation: CGSize) -> Bool {
        let distance = sqrt(pow(translation.width, 2) + pow(translation.height, 2))
        return distance >= minimumDistance
    }

    func body(content: Content) -> some View {
        content.gesture(DragGesture(minimumDistance: 0)
            .onChanged { event in
                guard !self.hasEnded else { return }

                if self.hasBegun == false {
                    self.hasBegun = true
                    self.touchBegan()
                } else if self.isTooFar(event.translation) {
                    self.hasEnded = true
                    self.touchEnd(false)
                }
            }
            .onEnded { event in
                if !self.hasEnded {
                    let success = !self.isTooFar(event.translation)
                    self.touchEnd(success)
                }
                self.hasBegun = false
                self.hasEnded = false
            }
        )
    }
}

extension View {
    func onTouchGesture(minimumDistance: CGFloat = 20.0,
                        touchBegan: @escaping () -> Void,
                        touchEnd: @escaping (Bool) -> Void) -> some View {
        modifier(TouchGestureViewModifier(minimumDistance: minimumDistance, touchBegan: touchBegan, touchEnd: touchEnd))
    }
}

Run Code Online (Sandbox Code Playgroud)

Vic*_*rov 5

struct ScaleButtonStyle: ButtonStyle {
    func makeBody(configuration: Self.Configuration) -> some View {
        configuration.label
            .scaleEffect(configuration.isPressed ? 2 : 1)
    }
}

struct Test2View: View {
    var body: some View {
        Button(action: {}) {
            Image("button1")
        }.buttonStyle(ScaleButtonStyle())
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 是否有任何已知的方法可以在 SwiftUI 中无需按钮即可实现相同的缩放效果? (5认同)

c-v*_*ain 5

升级 iOS 15 的答案代码(但这也适用于 iOS 13)。animation() 修饰符的单参数形式现已被正式弃用,主要是因为它导致了各种意外行为(例如在惰性堆栈中:lazy(v/h)scroll、lazyvgrid):按钮意外动画(跳跃)滚动期间。

public struct ScaleButtonStyle: ButtonStyle {
    public init() {}
    
    public func makeBody(configuration: Self.Configuration) -> some View {
        configuration.label
            .scaleEffect(configuration.isPressed ? 0.95 : 1)
            .animation(.linear(duration: 0.2), value: configuration.isPressed)
            .brightness(configuration.isPressed ? -0.05 : 0)
    }
}

public extension ButtonStyle where Self == ScaleButtonStyle {
    static var scale: ScaleButtonStyle {
        ScaleButtonStyle()
    }
}
Run Code Online (Sandbox Code Playgroud)