iOS系列-关于点击事件失效的快速排查方法

829 阅读4分钟

点击事件失效的快速排查步骤

点击事件对于app来说太常见了,但有时候在开发过程中会遇到明明设置了点击事件,但点击却没有响应的问题,以下是定位点击事件失效的原因:

1、Size检查

通过检查下视图树对应视图的宽高是否为0,任何一项为0都会导致点击事件无响应。如果在列表场景下,某个cell里的view宽高正常,但是cell显示的宽高有0,依旧会导致View点击事件异常(尽管不会影响展示)。如果宽高都正常但点击依旧却没有响应,则进入下一步。

2、事件检查

重写pointinrect和hittest,通过断点或者日志方式来检查是否正常响应。

简单介绍下两个方法的作用:hitTestpoint(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
    }
}

小结

  1. point(inside:with:): 判断点是否在视图的可触摸区域。
  2. hitTest(_:with:): 查找在特定点上是否有响应触摸的视图(包括自身)。

这两个方法常用于自定义视图的触摸事件处理,可以根据需要来实现更复杂的触摸交互。

扩展:针对UITableView/UICOllectionView下cell的view宽高正常,但是cell显示的宽高有0,如何进行排查?

方法:通过KVO去监听cell宽度被改变的原因 在 iOS 开发中,通过 KVO(键值观察)来监听一个 UITableViewCell 宽度的变化并不是一种常见的实践方法,因为 UITableViewCell 的宽度通常是由其父视图(如 UITableView)根据布局和约束自动计算的。不过,如果你确实需要监听某个特定的属性变化,你可以通过 KVO 来实现。

以下是实现 KVO 监听 UITableViewCell 宽度变化的一个基本思路:

  1. 创建自定义的 UITableViewCell:在这个自定义的 cell 中,你可以添加一个计算属性来获取宽度。
  2. 使用 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)")
            }
        }
    }
}

注意事项:

  1. KVO的限制:KVO 只能观察 @objc dynamic 标记的属性,确保定义的属性符合该要求。
  2. 使用 KVO 时的内存管理:在适当的地方添加和移除观察者,以免引起内存泄漏或崩溃。
  3. 在布局完成后再计算宽度: Cell 宽度的计算通常发生在布局后,确保在合适的时机访问宽度属性。
  4. 性能考虑:使用 KVO 可能带来一定的性能开销,在监控对象的数量较多时要谨慎。

结论:

虽然可以通过 KVO 监听 cell 的宽度变化,但通常更推荐使用其他方法,如 Auto Layout 的回调,或者在表格视图的 layoutSubviews 中进行处理。