需求
通常情况下,我们使用target-action模式为UIControl类型的对象设置触发事件:
1
| control.addTarget(target, action: Selctor("someAction:"), forControlEvents: event)
|
很多时候,对于一些触发的事件较为简单的控件,我们希望有一种更简单的方式:
1 2 3
| control.trigger({ (sender) -> Void in }, onEvent: event)
|
出于(偷懒)精简代码的目的,我们为UIControl
添加扩展来实现这一功能
我们需要实现以下两个接口:
1 2
| public func trigger(closure:((UIControl)->Void), onEvent event:UIControlEvents) public func removeOnEvent(event: UIControlEvents)
|
实现这一功能的大致的思路如下:
利用字典保存不同的event对应需要触发的事件,为这个事件添加一个target为自身的action,调用另一个方法,该方法从字典中取得所触发事件对应的闭包并调用。
实现
为了保存对应不同event的闭包,我们需要为UIControl
添加一个Dictionary
属性:
1 2 3 4 5 6 7 8 9
| private var bs_closuresDictionary : [UInt : ClosureWrapper<UIControl, Void>]! { get{ return objc_getAssociatedObject(self, &AssociatedKey.ClosuresDictionary) as? [UInt :ClosureWrapper<UIControl, Void>] } set{ let dict = newValue as NSDictionary? objc_setAssociatedObject(self, &AssociatedKey.ClosuresDictionary, dict, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC) } }
|
这里用到了关联对象,需要import ObjectiveC
模块,定义了一个嵌套struct Asscociated
来保存关键字
其中,利用ClosureWrapper
类型将Closure
封装成对象:
1 2 3 4 5 6 7 8 9
| class ClosureWrapper<T, R> { var _closure: ((T) -> R)? var closure: ((T) -> R)? { return self._closure } init(_ closure: ((T) -> R)?) { self._closure = closure } }
|
接下来,就可以实现目标接口了:
1 2 3 4 5 6 7 8
| public func trigger(closure:((UIControl)->Void), onEvent event:UIControlEvents){ if self.bs_closuresDictionary == nil { self.bs_closuresDictionary = [UInt : ClosureWrapper<UIControl, Void>]() } self.removeOnEvent(event) self.bs_closuresDictionary[event.rawValue] = ClosureWrapper<UIControl, Void>(closure); self.addTarget(self, action: Selector("targetTriggered:forEvent:"), forControlEvents: event) }
|
然后实现targetTriggered:forEvent:
方法,从字典中获得所触发的UIControlEvents
对应的闭包并调用。嗯,看起来似乎就这么结束了。
取得触发的UIControlEvents
这里有一个问题,如何在target中获得当前所触发的UIControlEvents事件?UIContorl中添加action被触发后的回调仅有两个参数:sender和event,无法从中得知是什么事件被触发。
所以,我们需要为每个添加了触发事件的event另外添加一个action,该action在传入的闭包前被调用,记录被触发的event。
由于target-action对应设计模式的原因,我们是无法在action中得到是什么事件被触发的,所以我们需要对每一个UIControlEvents事件都增加一个方法来记录:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| private class TriggerdEventsHelper { var _event : UIControlEvents! var event : UIControlEvents { return self._event; } @objc func touchDownSelector() { self._event = .TouchDown; } @objc func touchDownRepeatSelector() { self._event = .TouchDownRepeat; }
func selectorForEvent(event:UIControlEvents) -> Selector { switch(event){ case UIControlEvents.TouchDown: return Selector("touchDownSelector") case UIControlEvents.TouchDownRepeat: return Selector("touchDownRepeatSelector") case UIControlEvents.TouchDragInside: return Selector("touchDragInsideSelector"); default: return nil; } } }
|
需要注意的是,由于要使用这些方法的动态特性,所以每个用于记录event的方法都要用@objc修饰,更多有关此修饰符可见此处
然后在UIControl的extension中添加该类型的属性:
1 2 3 4 5 6 7 8
| private var bs_triggeredEvent : TriggerdEventsHelper!{ get{ return objc_getAssociatedObject(self, &AssociatedKey.triggeredEvent) as? TriggerdEventsHelper } set{ objc_setAssociatedObject(self, &AssociatedKey.triggeredEvent, newValue, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC) } }
|
注意事项
接下来就可以直接对所有UIControl
对象调用改方法了,不过需要注意的是由于UIControl会持有传入的闭包,在如果某个类持有了该UIControl
对象,传入的闭包中如果引用了self,需要在捕获列表中添加通过weak或unowned方式捕获self,防止出现引用循环。