点击事件失效的快速排查步骤
点击事件对于app来说太常见了,但有时候在开发过程中会遇到明明设置了点击事件,但点击却没有响应的问题,以下是定位点击事件失效的原因:
1、Size检查
通过检查下视图树对应视图的宽高是否为0,任何一项为0都会导致点击事件无响应。如果在列表场景下,某个cell里的view宽高正常,但是cell显示的宽高有0,依旧会导致View点击事件异常(尽管不会影响展示)。如果宽高都正常但点击依旧却没有响应,则进入下一步。
2、事件检查
重写pointinrect和hittest,通过断点或者日志方式来检查是否正常响应。
简单介绍下两个方法的作用:hitTest 和 point(inside:with:) 函数用于处理触摸事件,尤其是在 UIView 和 CALayer 的上下文中。这两个函数的作用在于确定触摸点是否在视图的可触摸区域内。
point(inside:with:) 用于判断一个给定的点是否在视图的范围内。它的声明如下:
override func point(inside point: CGPoint, with event: UIEvent?) -> Bool
- point: 你要检查的点(通常是触摸点)。
- event: 触摸事件,通常可以传入
nil。
如果点在视图的边界内(包括视图的透明区域),则返回 true,否则返回 false。
hitTest(_:with:) 会检查在给定的点上是否有任何子视图可响应触摸事件。其定义如下:
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView?
- point: 要检查的点。
- event: 触摸事件。
此方法将返回响应触摸的子视图(如果存在),如果没有子视图响应触摸则返回自己(如果点在自身的有效区域内),如果点不在任何视图上则返回 nil。
示例
以下是一个简单的示例,展示如何重写这两个方法:
class CustomView: UIView {
override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
// 只允许触摸在特定区域内
let customArea = CGRect(x: 10, y: 10, width: 100, height: 100)
return customArea.contains(point)
}
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
// 检查点是否在视图内,如果是则调用超类的实现
if self.point(inside: point, with: event) {
// 找到子视图中能够响应触摸的第一个视图
for subview in subviews.reversed() {
let convertedPoint = subview.convert(point, from: self)
if let hitView = subview.hitTest(convertedPoint, with: event) {
return hitView
}
}
return self // 如果没有子视图响应,则返回自己
}
return nil // 如果点不在视图内部,返回 nil
}
}
小结
point(inside:with:): 判断点是否在视图的可触摸区域。hitTest(_:with:): 查找在特定点上是否有响应触摸的视图(包括自身)。
这两个方法常用于自定义视图的触摸事件处理,可以根据需要来实现更复杂的触摸交互。
扩展:针对UITableView/UICOllectionView下cell的view宽高正常,但是cell显示的宽高有0,如何进行排查?
方法:通过KVO去监听cell宽度被改变的原因 在 iOS 开发中,通过 KVO(键值观察)来监听一个 UITableViewCell 宽度的变化并不是一种常见的实践方法,因为 UITableViewCell 的宽度通常是由其父视图(如 UITableView)根据布局和约束自动计算的。不过,如果你确实需要监听某个特定的属性变化,你可以通过 KVO 来实现。
以下是实现 KVO 监听 UITableViewCell 宽度变化的一个基本思路:
- 创建自定义的 UITableViewCell:在这个自定义的 cell 中,你可以添加一个计算属性来获取宽度。
- 使用 KVO 监听宽度变化:在UIViewController或UITableViewController中观察这个自定义 cell 的宽度属性。
示例代码:
import UIKit
class CustomTableViewCell: UITableViewCell {
@objc dynamic var customWidth: CGFloat {
return self.bounds.width
}
}
class ViewController: UIViewController, UITableViewDataSource {
let tableView = UITableView()
var cell: CustomTableViewCell?
override func viewDidLoad() {
super.viewDidLoad()
tableView.dataSource = self
tableView.register(CustomTableViewCell.self, forCellReuseIdentifier: "cell")
view.addSubview(tableView)
// 约束代码...
// 观察 cell 的 customWidth 属性
cell = tableView.dequeueReusableCell(withIdentifier: "cell") as? CustomTableViewCell
cell?.addObserver(self, forKeyPath: "customWidth", options: .new, context: nil)
}
deinit {
cell?.removeObserver(self, forKeyPath: "customWidth")
}
// 数据源方法
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 10
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! CustomTableViewCell
return cell
}
// KVO 观察者方法
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
if keyPath == "customWidth" {
if let newWidth = change?[.newKey] as? CGFloat {
print("Cell width changed to: (newWidth)")
}
}
}
}
注意事项:
- KVO的限制:KVO 只能观察
@objc dynamic标记的属性,确保定义的属性符合该要求。 - 使用 KVO 时的内存管理:在适当的地方添加和移除观察者,以免引起内存泄漏或崩溃。
- 在布局完成后再计算宽度: Cell 宽度的计算通常发生在布局后,确保在合适的时机访问宽度属性。
- 性能考虑:使用 KVO 可能带来一定的性能开销,在监控对象的数量较多时要谨慎。
结论:
虽然可以通过 KVO 监听 cell 的宽度变化,但通常更推荐使用其他方法,如 Auto Layout 的回调,或者在表格视图的 layoutSubviews 中进行处理。