Swift:实现视图绕一角随拖动手势旋转,释放后复位
实现视图绕指定角随拖动手势旋转并自动复位
核心实现思路
- 用
UIPanGestureRecognizer替代滑动手势,实时响应手指拖动动作 - 以目标角为旋转锚点,通过计算触摸点与锚点的角度差,实现精准跟随旋转
- 限制旋转范围到设定的最大角度,超出后不再继续旋转
- 手势结束或取消时触发平滑复位动画
完整代码实现
1. UIView扩展:安全设置锚点(避免视图位移)
直接修改layer.anchorPoint会导致视图位置跳动,这个扩展会先调整视图位置再设置锚点:
extension UIView { func setAnchorPoint(_ anchorPoint: CGPoint) { var newPoint = CGPoint(x: bounds.size.width * anchorPoint.x, y: bounds.size.height * anchorPoint.y) var oldPoint = CGPoint(x: bounds.size.width * layer.anchorPoint.x, y: bounds.size.height * layer.anchorPoint.y) newPoint = newPoint.applying(transform) oldPoint = oldPoint.applying(transform) var position = layer.position position.x -= oldPoint.x position.x += newPoint.x position.y -= oldPoint.y position.y += newPoint.y layer.position = position layer.anchorPoint = anchorPoint } }
2. 视图控制器核心代码
class YourViewController: UIViewController { @IBOutlet weak var container: UIView! @IBOutlet weak var backgroundView: UIView! // 旋转锚点(示例为右下角:x=1, y=1) private let rotationAnchor = CGPoint(x: 1, y: 1) // 最大允许旋转角度(15°转弧度) private let maxRotationAngle = 15.0 * .pi / 180.0 // 记录手势初始状态 private var initialRotation: CGFloat = 0.0 private var initialTouchAngle: CGFloat = 0.0 // 触觉反馈 private let haptic = UIImpactFeedbackGenerator(style: .medium) override func viewDidLoad() { super.viewDidLoad() setupPanGesture() // 提前设置锚点,避免手势触发时视图跳动 container.setAnchorPoint(rotationAnchor) } } // MARK: - 拖动手势处理 extension YourViewController { private func setupPanGesture() { let panGesture = UIPanGestureRecognizer(target: self, action: #selector(handlePan(_:))) container.addGestureRecognizer(panGesture) } @objc private func handlePan(_ gesture: UIPanGestureRecognizer) { guard let gestureView = gesture.view else { return } switch gesture.state { case .began: // 记录初始旋转角度和触摸点相对于锚点的角度 initialRotation = gestureView.transform.rotationAngle let touchPoint = gesture.location(in: view) let anchorInSuperview = gestureView.convert(rotationAnchor, to: view) initialTouchAngle = angle(from: anchorInSuperview, to: touchPoint) haptic.prepare() case .changed: // 计算当前触摸点与锚点的角度 let touchPoint = gesture.location(in: view) let anchorInSuperview = gestureView.convert(rotationAnchor, to: view) let currentTouchAngle = angle(from: anchorInSuperview, to: touchPoint) // 计算目标旋转角度,限制在0到最大角度之间 var targetRotation = currentTouchAngle - initialTouchAngle + initialRotation targetRotation = max(0.0, min(targetRotation, CGFloat(maxRotationAngle))) // 实时更新视图旋转 gestureView.transform = CGAffineTransform(rotationAngle: targetRotation) // 到达最大角度时触发反馈和背景色变化 if targetRotation >= CGFloat(maxRotationAngle) { backgroundView.backgroundColor = .systemGreen haptic.impactOccurred() } else { backgroundView.backgroundColor = .systemBackground } case .ended, .cancelled: // 手势结束,复位视图 rotateContainerToInitialPosition() default: break } } // 计算两点之间的角度(基于x轴正方向) private func angle(from pointA: CGPoint, to pointB: CGPoint) -> CGFloat { let deltaX = pointB.x - pointA.x let deltaY = pointB.y - pointA.y return atan2(deltaY, deltaX) } // 复位动画 func rotateContainerToInitialPosition() { UIView.animate(withDuration: 0.6) { self.container.transform = .identity self.backgroundView.backgroundColor = .systemBackground } } }
关键细节说明
- 锚点提前设置:在
viewDidLoad中完成锚点设置,避免手势触发时视图突然位移 - 角度计算逻辑:通过
atan2计算触摸点与锚点的相对角度,确保旋转完全跟随手指移动轨迹 - 旋转范围限制:使用
max/min函数将旋转角度锁定在0到设定的最大角度之间,防止过度旋转 - 触觉反馈优化:在手势开始前调用
haptic.prepare(),确保反馈及时触发
内容的提问来源于stack exchange,提问作者Martin Lahn
相关产品推荐
相关产品推荐

