You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.22 09:10:22