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)
- 拿到N个targets
- 移除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);
}
}
绑定好之后, 我传值, 调用, 都非常的方便, 主要还是养成习惯了