3D Touch实现无Peek的Pop及手动创建模糊缩放效果咨询
嘿,针对你想要实现的3D Touch导航需求(类似通讯录/Apple Music,还要无Peek直接Pop的效果),我给你梳理一套可行的方案,亲测好用!
核心思路
既然系统默认的Peek/Pop流程不符合你的场景(进入自定义VC前按钮无法点击),那我们可以手动监听3D Touch的压力变化,在压力达到阈值时触发模糊+缩放的过渡动画,最终在触摸结束时完成导航跳转。完全绕开系统默认的交互逻辑,自己掌控整个流程。
具体实现步骤
- 监听触摸事件的压力值:通过重写
touchesBegan/touchesMoved/touchesEnded方法,判断压力是否达到触发阈值 - 手动构建模糊背景:用
UIVisualEffectView实现和系统Peek一致的模糊效果 - 实现缩放过渡动画:目标VC的View从缩小状态平滑放大到正常尺寸,配合模糊背景的淡入/淡出
- 触摸结束时完成导航:确认用户是触发Pop操作后,执行导航并完成动画收尾
关键代码示例
第一步:定义变量和基础配置
在当前ViewController里先声明需要用到的变量:
import UIKit class CurrentViewController: UIViewController { // 模糊背景视图 private var blurBackgroundView: UIVisualEffectView? // 标记是否正在触发Pop操作 private var isPopTriggered = false // 压力触发阈值(可以根据测试调整,0~1之间,1是设备最大压力) private let pressureThreshold: CGFloat = 0.7
第二步:重写触摸方法监听压力
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) { super.touchesBegan(touches, with: event) guard let touch = touches.first, UITouch.isForceTouchCapabilityAvailable else { return } // 计算相对压力(当前压力/设备最大压力) let relativeForce = touch.force / touch.maximumForce if relativeForce >= pressureThreshold { preparePopAnimation() isPopTriggered = true } } override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) { super.touchesMoved(touches, with: event) guard let touch = touches.first, UITouch.isForceTouchCapabilityAvailable else { return } let relativeForce = touch.force / touch.maximumForce if relativeForce >= pressureThreshold && !isPopTriggered { // 压力达到阈值,开始准备动画 preparePopAnimation() isPopTriggered = true } else if relativeForce < pressureThreshold && isPopTriggered { // 压力不足,取消动画 cancelPopAnimation() isPopTriggered = false } } override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) { super.touchesEnded(touches, with: event) if isPopTriggered { // 确认触发Pop,执行导航 executePopNavigation() } else { // 取消动画 cancelPopAnimation() } isPopTriggered = false }
第三步:实现动画准备/取消/导航方法
// 准备Pop动画:显示模糊背景 private func preparePopAnimation() { let blurEffect = UIBlurEffect(style: .systemMaterial) blurBackgroundView = UIVisualEffectView(effect: blurEffect) blurBackgroundView?.frame = view.bounds blurBackgroundView?.alpha = 0 view.addSubview(blurBackgroundView!) // 模糊背景淡入动画 UIView.animate(withDuration: 0.15) { self.blurBackgroundView?.alpha = 1 } } // 取消Pop动画:隐藏并移除模糊背景 private func cancelPopAnimation() { UIView.animate(withDuration: 0.15, animations: { self.blurBackgroundView?.alpha = 0 }) { _ in self.blurBackgroundView?.removeFromSuperview() self.blurBackgroundView = nil } } // 执行Pop导航:跳转到自定义VC并播放缩放动画 private func executePopNavigation() { // 替换成你的自定义ViewController let targetVC = YourCustomViewController() // 初始状态:缩小+透明 targetVC.view.transform = CGAffineTransform(scaleX: 0.85, y: 0.85) targetVC.view.alpha = 0 // 不使用系统动画,手动控制 navigationController?.pushViewController(targetVC, animated: false) // 播放缩放+淡入动画,配合弹簧效果更贴近系统风格 UIView.animate(withDuration: 0.25, delay: 0, usingSpringWithDamping: 0.8, initialSpringVelocity: 0.5, options: .curveEaseOut) { targetVC.view.transform = .identity targetVC.view.alpha = 1 } completion: { _ in // 动画完成后移除模糊背景 self.blurBackgroundView?.removeFromSuperview() self.blurBackgroundView = nil } } }
注意事项
- 确保设备支持3D Touch:代码里已经用
UITouch.isForceTouchCapabilityAvailable做了判断,也可以在Info.plist里添加UIRequiredDeviceCapabilities并包含forceTouchCapability,过滤不支持的设备。 - 阈值调整:
pressureThreshold可以根据实际测试调整,用相对压力(touch.force/touch.maximumForce)比固定值更适配不同设备。 - 按钮交互处理:如果是按钮触发,记得禁用按钮的默认点击事件(或者用手势识别器代理拦截),避免和我们的压力监听冲突。
- 动画细节:弹簧参数(
usingSpringWithDamping和initialSpringVelocity)可以微调,让动画更贴近系统Peek/Pop的质感。
内容的提问来源于stack exchange,提问作者John Webb
相关产品推荐
相关产品推荐

