自定义脉冲UIButton动画期间无法响应触摸事件问题咨询
我之前也碰到过类似的坑,核心问题其实有两个:一是默认的UIView.animate会禁用视图的用户交互,二是直接修改按钮的transform会导致视觉大小和实际可点击区域不匹配。下面给你两种可行的解决方案:
方案一:开启动画交互并修正点击区域
首先,我们需要在动画方法里加上.allowUserInteraction选项,让视图在动画期间依然能响应触摸。然后重写hitTest方法,确保点击视觉上的按钮区域都能被识别。
修改你的动画代码:
func grow() { UIView.animate(withDuration: 0.8, delay: 0, options: .allowUserInteraction) { self.transform = CGAffineTransform(scaleX: 2.5, y: 2.5) self.layoutIfNeeded() } } func shrink() { UIView.animate(withDuration: 0.8, delay: 0, options: .allowUserInteraction) { self.transform = .identity self.layoutIfNeeded() } }
接着给PulseButton添加hitTest的重写:
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { // 计算缩放后的按钮边界 let scaledBounds = bounds.applying(transform) // 将点击点转换到未缩放的坐标系下 let inverseTransform = CGAffineTransform(scaleX: 1/transform.a, y: 1/transform.d) let scaledPoint = point.applying(inverseTransform) return scaledBounds.contains(scaledPoint) ? self : nil }
这样修改后,动画期间按钮就能正常响应触摸,而且点击放大后的按钮任何位置都会触发事件。
方案二:用子视图实现动画(更推荐)
直接修改按钮的transform容易带来交互区域的适配问题,更优雅的方式是让按钮本身保持原始状态,用一个子视图来做缩放动画。这样按钮的交互区域始终不变,完全不会影响触摸响应。
重构后的PulseButton代码:
class PulseButton: UIButton { // 专门用来做脉冲动画的子视图 private let pulseView = UIView() override init(frame: CGRect) { super.init(frame: frame) setup() } required init?(coder: NSCoder) { super.init(coder: coder) setup() } private func setup() { // 配置脉冲视图,和按钮外观保持一致 pulseView.frame = bounds pulseView.backgroundColor = self.backgroundColor pulseView.layer.cornerRadius = self.layer.cornerRadius pulseView.isUserInteractionEnabled = false // 禁止子视图抢交互,让按钮本身处理 // 把脉冲视图放在按钮最底层,不影响按钮的文字/图标 insertSubview(pulseView, at: 0) // 启动定时动画 Timer.scheduledTimer(timeInterval: 1.6, target: self, selector: #selector(animatePulse), userInfo: nil, repeats: true) } override func layoutSubviews() { super.layoutSubviews() // 按钮布局变化时,同步更新脉冲视图的大小和圆角 pulseView.frame = bounds pulseView.layer.cornerRadius = self.layer.cornerRadius } @objc private func animatePulse() { // 重置脉冲视图的状态 pulseView.transform = .identity pulseView.alpha = 1 // 执行放大+淡出动画,视觉效果更自然 UIView.animate(withDuration: 0.8) { self.pulseView.transform = CGAffineTransform(scaleX: 2.5, y: 2.5) self.pulseView.alpha = 0.3 } completion: { _ in // 动画结束后重置透明度,为下一次动画做准备 self.pulseView.alpha = 1 } } }
这种方式下,按钮本身的所有交互逻辑都不受影响,动画完全由子视图完成,视觉效果和原来一致,但稳定性更高,也更容易维护。
内容的提问来源于stack exchange,提问作者Adrian
相关产品推荐
相关产品推荐

