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

iOS不规则UIView的Hit Testing问题:手势仅在指定区域触发

解决不规则UIView手势绑定的触摸判断异常问题

你的核心问题是:CAShapeLayer使用evenOdd填充规则实现了全屏挖空的视觉效果,但hitTest里的path.contains(point)判断逻辑和遮罩的视觉区域不匹配——因为CGPath默认的contains(_:)方法用的是非零缠绕规则,和mask的evenOdd规则区域判断逻辑相反,导致触摸非目标区域时判断结果为true,手势触发异常。

修复现有代码的两种思路

思路1:直接利用已有的挖空区域数组判断

既然你已经维护了rectsArray存储挖空的单元格区域,完全可以跳过path的复杂判断,直接在hitTest里判断触摸点是否在挖空区域内:

override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
    // 触摸点在挖空区域内,让事件穿透给下层视图(比如单元格)
    if let rects = rectsArray {
        for rect in rects {
            if rect.contains(point) {
                return nil
            }
        }
    }
    // 触摸点在遮罩区域,响应当前视图的手势
    return super.hitTest(point, with: event)
}

这个方法逻辑直观,完全避开了CGPath填充规则的坑,适合你的场景。

思路2:适配evenOdd规则的path判断

如果一定要基于mask的path判断,需要使用CGPath的contains(_:using:transform:)方法,手动指定evenOdd规则,和mask的fillRule保持一致:

override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
    guard let shapeLayer = self.layer.mask as? CAShapeLayer, let path = shapeLayer.path else {
        return nil
    }
    // 用evenOdd规则判断点是否在遮罩的填充区域(非挖空部分)
    let isInMaskedArea = path.contains(point, using: .evenOdd, transform: .identity)
    return isInMaskedArea ? super.hitTest(point, with: event) : nil
}

这里的关键是using: .evenOdd参数,让点的区域判断逻辑和遮罩的视觉效果完全匹配。

更轻量的替代方案:全屏透明拦截视图

针对你“单元格滑出选项后,点击其他区域关闭选项”的场景,其实可以不用复杂的遮罩视图,直接用一个全屏透明视图实现:

class CellOptionBlockerView: UIView {
    var activeCellFrame: CGRect? // 激活单元格在当前视图坐标系下的frame
    
    init(frame: CGRect, activeCellFrame: CGRect) {
        super.init(frame: frame)
        self.activeCellFrame = activeCellFrame
        // 可选:添加半透明背景增强视觉提示
        backgroundColor = UIColor.black.withAlphaComponent(0.2)
        // 添加点击手势,触发关闭逻辑
        let tap = UITapGestureRecognizer(target: self, action: #selector(closeCellOptions))
        addGestureRecognizer(tap)
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
        // 点击激活单元格区域,让事件穿透
        if let cellFrame = activeCellFrame, cellFrame.contains(point) {
            return nil
        }
        // 点击其他区域,响应手势
        return super.hitTest(point, with: event)
    }
    
    @objc private func closeCellOptions() {
        // 在这里实现关闭单元格选项的逻辑,比如通知TableView复位单元格
        removeFromSuperview()
    }
}

这个方案逻辑更简单,不需要处理CAShapeLayer的路径问题,维护成本更低。

内容的提问来源于stack exchange,提问作者chadbag

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.16 14:51:04