iOS UIButton 偶现不响应问题

2022-06-27  本文已影响0人  果哥爸

一. 问题背景

有同时反馈,评价弹框的匿名评价按钮,在他的手机上点击没有任何响应,但是在其他人的手机上面是正常的,因此进行排查。

二. 问题分析

经排查项目里面的代码对按钮的声明类似如下:

var testButton: UIButton = {
        let btn = UIButton()
        btn.setTitle("点击", for: .normal)
        btn.setTitleColor(.red, for: .normal)
        btn.addTarget(self, action: #selector(testButtonClicked), for: .touchUpInside)
        return btn
    }()

swfit声明UIButton变量,没有加上lazy,使得这个变量调用是在init构造方法之前,所以这里testButtontargetself,因为还没调用init方法,所以这里selfnil

之所以nil也没问题,是因为点击触摸到屏幕的时候,首先通过响应链找到第一响应者testButton,然后testButton将添加到自身点击事件发送给UIApplicationUIApplication判断如果targetnil,就会以第一响应者testButton为起点,沿着响应链去判断,响应链条上面的视图是否可以响应点击事件,如果可以响应,则将事件发送给该视图,触发selector,停止传递;

如果响应链上的视图都不能响应该事件,就直接抛弃这个响应事件,因此这里即使target传入了nil,沿着响应链去寻找方法响应者,刚好找到了FJFButtonTestView可以响应#selector(testButtonClicked),所以直接将事件传给FJFButtonTestView的去处理。

但在有问题的手机上,调试发现,因为testButton是写在FJFButtonTestView里面的,testButton响应的selector方法也是放在FJFButtonTestView里面,因为传入的targetnilUIApplication则直接将事件发送到UIViewController上,直接沿着UIViewController的响应链去传递,而不是从第一响应者的响应链开始传递,因此导致了,放到FJFButtonTestView上的点击事件压根不响应。

至于为什么在有问题手机系统上面,没有第一响应者testButton为起点,沿着响应链去查找是否可以响应该事件的响应者,而是直接从UIViewController开始查找,这个问题,我看官方文档是这样说的:

When adding an action method to a control, you specify both the action method and an object that defines that method to the addTarget(_:action:for:) method. (You can also configure the target and action of a control in Interface Builder.) The target object can be any object, but it’s typically the view controller’s root view that contains the control. If you specify nil for the target object, the control searches the responder chain for an object that defines the specified action method.
https://developer.apple.com/documentation/uikit/uicontrol

这里意思是说如果你的target传入为nil,那么UIControl将在响应链中搜索定义action的对象。文章并没有明确是从第一响应者沿着响应链去查找。

这是我当时拿到反馈问题手机调试的堆栈(是另一个OC版本的Demo):

origin_img_v2_b8f033f6-24ca-4537-8aff-ea70c34b079g.jpg

从堆栈可以很明显看出UIApplication直接将事件发送给FJFButtonClickedViewController上定义的- (void)testButtonClicked:(UIButton *)sender响应事件。

至于本质原因我也没找到,如果有了解的朋友,还请告知下。

三. 解决方案

方案一: 添加lazy声明为懒加载方式

// MARK: - Lazy
    lazy var testButton: UIButton = {
        let btn = UIButton()
        btn.setTitle("点击", for: .normal)
        btn.setTitleColor(.red, for: .normal)
        btn.addTarget(self, action: #selector(testButtonClicked), for: .touchUpInside)
        return btn
    }()

通过将变量声明为lazy,懒加载方式,等到View初始化完成之后,再去真正的调用。

方案二:将button声明为可选项,等到真正用到再去初始化

    private var testButton: UIButton?
    // MARK: - Life
    override init(frame: CGRect) {
        super.init(frame: frame)
        
        setupViewControls()
        layoutViewControls()
    }
    // MARK: - Private
    private func setupViewControls() {
        let btn = UIButton()
        btn.setTitle("点击", for: .normal)
        btn.setTitleColor(.red, for: .normal)
        btn.addTarget(self, action: #selector(testButtonClicked), for: .touchUpInside)
        self.addSubview(btn)
        testButton = btn
    }

这两种方式都能保证,button添加targetself是有值的。

上一篇下一篇

猜你喜欢

热点阅读