UIControl-多个targets-多个actions

UIControl添加事件

最常见的开发中给按钮添加事件, 我们怎么写?

button.addTarget(self, action: #selector(buttonAction1(_:)), for: .touchUpInside)

相当熟练了哈, 我都写几年了, 一点没有怀疑过这句话的正确性, 直到我遇到了UITableViewcCell复用的问题

多targets-多actions

平常我们cell复用, 按钮的事件基本都是一个action, 就是方法名一样, 我们传不同参数去操作不同的数据, 比如给button加上tag, 这样我们可以直到点击的cell对应数组中的哪个模型, 所以几乎不会遇到给按钮添加不同的target和不同的action这种问题

我遇到了

我cell上有UISwitch开关, 界面有若干开关, 我就设置了开关对应的方法, 第一个开关设置A, 第二个开关设置B ... 我给他们传了自定义方法, selector, 这样cellForRow方法里面, 每刷新一下, cell就重新设置target和action

问题来了, 我点了一下开关, N个方法一起触发...检查发现, 一个控件, 有好几个action, 我知道是addTarget的问题, 文档这么写的:

// add target/action for particular event. you can call this multiple times and you can specify multiple target/actions for a particular event.
// passing in nil as the target goes up the responder chain. The action may optionally include the sender and the event in that order
// the action cannot be NULL. Note that the target is not retained.
open func addTarget(_ target: Any?, action: Selector, for controlEvents: UIControl.Event)

写的明明白白, 会有多个targets, 多个actions

测验一下, 可以复制粘贴到你的项目中, 执行一下如下代码(自己创建button):

print("++++++++++++++++++++1")
print(button.actions(forTarget: self, forControlEvent: .touchUpInside))
button.addTarget(self, action: #selector(buttonAction1(_:)), for: .touchUpInside)
print("++++++++++++++++++++2")
print(button.actions(forTarget: self, forControlEvent: .touchUpInside))

button.addTarget(self, action: #selector(buttonAction2(_:)), for: .touchUpInside)
button.addTarget(self, action: #selector(buttonAction3(_:)), for: .touchUpInside)
button.addTarget(self, action: #selector(buttonAction4(_:)), for: .touchUpInside)

print("++++++++++++++++++++3")
print(button.actions(forTarget: self, forControlEvent: .touchUpInside))
++++++++++++++++++++1
nil
++++++++++++++++++++2
Optional(["buttonAction1:"])
++++++++++++++++++++3
Optional(["buttonAction1:", "buttonAction2:", "buttonAction3:", "buttonAction4:"])
++++++++++++++++++++1

确实多个actions

再次给UIControl添加事件

block

那我们思路来了, 第一个思路, 我改用其他方式给button绑定事件, 比如block, 用起来还更优雅, 这里不得不提到一个大家可能已经在用的分类

extension UIControl {
    static let cmButtonAssociatedkey = UnsafeRawPointer.init(bitPattern: "cmButtonAssociatedkey".hashValue)!

    func addAction(for controlEvents: UIControl.Event = .touchUpInside, action: @escaping (UIButton) -> Void) {
        objc_setAssociatedObject(self, UIButton.cmButtonAssociatedkey, action, .OBJC_ASSOCIATION_COPY_NONATOMIC)

        self.addTarget(self, action: #selector(cmButtonClick), for: controlEvents)
    }

    @objc func cmButtonClick() {
        if let action = objc_getAssociatedObject(self, UIButton.cmButtonAssociatedkey) as? (UIButton) -> Void {
            action(self)
        }
    }
}

利用objc_setAssociatedObject 和objc_getAssociatedObject给事件指定一个key, 将事件方法包装成一个block, 那你在写代码的时候, 就可以用block来执行代码, 很方便

button.addAction(for: .touchUpInside) { [weak self] button in
	// do 
}

它可以保证你设置的action覆盖掉上一个action, 你在开发中, 哪怕给按钮加十七八个方法, 也只会有最后一个生效

remove - add 事件

另一个方法, 既然你添加的时候会添加多个, 那我把你移除不就好了吗?
apple支持我们自己操作target和action, 点进addtarget的文档, 有好几个方法, 我们只需要看两个:

// get info about target & actions. this makes it possible to enumerate all target/actions by checking for each event kind
open var allTargets: Set<AnyHashable> { get } // set may include NSNull to indicate at least one nil target

// remove the target/action for a set of events. pass in NULL for the action to remove all actions for that target
open func removeTarget(_ target: Any?, action: Selector?, for controlEvents: UIControl.Event)
  1. 拿到N个targets
  2. 移除actions(看第二个移除方法, 不传action, 表示移除所有的actions)

这里面具体的要移除控件所有的target还是指定target, 是所有的actions还是指定的action, 自己根据需求去判断, 假设我们移除所有的targets和actions:

for target in button.allTargets {
    // target也可以自己传过来, action我们传nil表示所有事件, 后面的touchUpInside自己看之前添加的时候传的啥
    button.removeTarget(target, action: nil, for: .touchUpInside)
}

试试看, 上面的代码最后一个addTarget前, 我们来移除他的所有的actions

print("++++++++++++++++++++1")
print(button.actions(forTarget: self, forControlEvent: .touchUpInside))
button.addTarget(self, action: #selector(buttonAction1(_:)), for: .touchUpInside)
print("++++++++++++++++++++2")
print(button.actions(forTarget: self, forControlEvent: .touchUpInside))

button.addTarget(self, action: #selector(buttonAction2(_:)), for: .touchUpInside)
button.addTarget(self, action: #selector(buttonAction3(_:)), for: .touchUpInside)

for target in button.allTargets {
    button.removeTarget(target, action: nil, for: .touchUpInside)
}

button.addTarget(self, action: #selector(buttonAction4(_:)), for: .touchUpInside)
print("++++++++++++++++++++3")
print(button.actions(forTarget: self, forControlEvent: .touchUpInside))
++++++++++++++++++++1
nil
++++++++++++++++++++2
Optional(["buttonAction1:"])
++++++++++++++++++++3
Optional(["buttonAction4:"])

ok, 搞定

总结

如果遇到action前后变化的时候, 要留意是保留多个actions还是覆盖掉上一个.
方便的时候, 你可以用block的方式执行事件, 这样便于书写和阅读.

你问我为什么会选择用selector, 这里我补充一个, 平常遇到多个事件摆在一起, 习惯创建一个模型数组, 事件用string表示, 这样执行代码可以用:

// OC
/// 执行自定义方法
- (void)performSelfFuncWithString:(NSString *)funcString object:(id)obj {

    if (funcString.length == 0) {
        return;
    }

    if ([self respondsToSelector:NSSelectorFromString(funcString)]) {
        SEL selector = NSSelectorFromString(funcString);
        IMP imp = [self methodForSelector:selector];

        // 第一个id表示执行对象, SEL是方法选择器, 第二个id是参数, 可以为空, 直接传nil
        void (*func)(id, SEL, id) = (void *)imp;
        func(self, selector, obj);
    }
}

绑定好之后, 我传值, 调用, 都非常的方便, 主要还是养成习惯了

上一篇:python gui


下一篇:论文笔记之:Optical Flow Estimation using a Spatial Pyramid Network