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
相关产品推荐
相关产品推荐

