如何在iOS中获取原始触摸事件以自定义复杂手势?
嘿,我完全懂你想要避开系统手势识别器的限制,直接拿到iOS的原始触摸事件,像Android的MotionEvent那样实现自己的复杂多点触控状态机和绘图需求——毕竟系统手势识别器确实搞不定太定制化的逻辑。下面给你一步步拆解怎么实现:
核心:通过UIResponder拦截原始触摸事件
iOS里所有能接收触摸的控件都继承自UIResponder,它提供了四个关键方法来获取原始触摸事件流,这就是你要的类似Android MotionEvent的入口:
touchesBegan(_:with:):手指刚接触屏幕时触发touchesMoved(_:with:):手指在屏幕上移动时触发(会多次调用)touchesEnded(_:with:):手指离开屏幕时触发touchesCancelled(_:with:):触摸被系统中断时触发(比如来电、弹窗)
你只需要自定义一个View(不需要任何系统组件,纯自定义载体),重写这四个方法,就能拿到所有触摸的原始数据。
处理多点触控:跟踪每个触摸的身份
多点触控的关键是区分不同的手指——每个UITouch对象都有唯一的identifier属性,你可以用它来跟踪每根手指的状态。比如用一个字典存储每个触摸的位置、阶段等信息:
class CustomGestureView: UIView { // 存储每个触摸的状态:key是触摸的唯一ID,value是自定义的状态结构体 private var touchTrackers: [Int: TouchInfo] = [:] // 自定义触摸信息结构体,按需扩展字段 struct TouchInfo { var currentPosition: CGPoint var phase: UITouch.Phase var previousPosition: CGPoint? // 可选:记录上一位置,用于绘制轨迹 } }
实现触摸事件的处理逻辑
在重写的四个触摸方法里,更新触摸状态并驱动你的自定义状态机:
// MARK: - 触摸事件处理 extension CustomGestureView { override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) { super.touchesBegan(touches, with: event) for touch in touches { let location = touch.location(in: self) touchTrackers[touch.identifier] = TouchInfo( currentPosition: location, phase: .began, previousPosition: nil ) // 触发状态机的"触摸开始"逻辑 customStateMachine.handleTouch(touch, phase: .began) } setNeedsDisplay() // 触发绘图 } override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) { super.touchesMoved(touches, with: event) for touch in touches { guard var info = touchTrackers[touch.identifier] else { continue } info.previousPosition = info.currentPosition info.currentPosition = touch.location(in: self) info.phase = .moved touchTrackers[touch.identifier] = info // 触发状态机的"触摸移动"逻辑 customStateMachine.handleTouch(touch, phase: .moved) } setNeedsDisplay() } override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) { super.touchesEnded(touches, with: event) for touch in touches { guard var info = touchTrackers[touch.identifier] else { continue } info.currentPosition = touch.location(in: self) info.phase = .ended // 触发状态机的"触摸结束"逻辑 customStateMachine.handleTouch(touch, phase: .ended) touchTrackers.removeValue(forKey: touch.identifier) } setNeedsDisplay() } override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) { super.touchesCancelled(touches, with: event) // 处理触摸被中断的情况,逻辑和ended类似 for touch in touches { customStateMachine.handleTouch(touch, phase: .cancelled) touchTrackers.removeValue(forKey: touch.identifier) } setNeedsDisplay() } }
自定义状态机的实现思路
你的状态机可以是一个独立的类,负责处理触摸事件流并维护手势的状态(比如“单指拖动”、“双指缩放”、“自定义三连击”等):
class CustomGestureStateMachine { enum GestureState { case idle case singleFingerDrag case twoFingerScale // 自定义你需要的所有状态 } private var currentState: GestureState = .idle func handleTouch(_ touch: UITouch, phase: UITouch.Phase) { // 根据当前状态和触摸事件,切换状态并执行对应逻辑 switch currentState { case .idle: if phase == .began && touchTrackers.count == 1 { currentState = .singleFingerDrag // 执行单指拖动的初始化逻辑 } else if phase == .began && touchTrackers.count == 2 { currentState = .twoFingerScale // 执行双指缩放的初始化逻辑 } case .singleFingerDrag: if phase == .moved { // 执行单指拖动的实时逻辑 } else if phase == .ended { currentState = .idle // 执行单指拖动结束的收尾逻辑 } // 其他状态的处理逻辑... } } }
无系统组件的绘图实现
在自定义View的draw(_:)方法里,根据touchTrackers里的触摸状态绘制内容,完全不需要依赖系统组件:
override func draw(_ rect: CGRect) { super.draw(rect) guard let context = UIGraphicsGetCurrentContext() else { return } // 设置绘图属性 context.setStrokeColor(UIColor.blue.cgColor) context.setLineWidth(3.0) context.setLineCap(.round) // 绘制每个触摸的轨迹 for info in touchTrackers.values { if let prevPos = info.previousPosition { context.move(to: prevPos) context.addLine(to: info.currentPosition) context.strokePath() } else { // 绘制触摸开始的点 context.addArc(center: info.currentPosition, radius: 3, startAngle: 0, endAngle: .pi*2, clockwise: true) context.fillPath() } } }
注意事项
- 确保你的自定义View的
isUserInteractionEnabled属性为true(默认是true,但如果是从Storyboard/XIB创建的,偶尔会被关掉) - 如果你的View嵌套在
UIScrollView或其他有系统手势的容器里,可以通过重写gestureRecognizerShouldBegin(_:)来控制手势优先级,或者直接在触摸方法里处理逻辑,原始触摸事件会优先被你的View接收 UITouch还提供了很多额外信息,比如force(3D Touch压力)、timestamp(触摸时间戳)、altitudeAngle(Apple Pencil的倾斜角度)等,你可以按需获取
内容的提问来源于stack exchange,提问作者Joe Lapp
相关产品推荐
相关产品推荐

