用闭包实现按钮的链式点击事件

  1. 通常姿势
    通常按钮的点击事件我们需要这样写:
1
2
3
4
5
btn.addTarget(self, action: #selector(actionTouch), for: .touchUpInside)

@objc func actionTouch() {
print("按钮点击事件")
}

如果有多个点击事件,往往还要写多个方法,写多了有没有觉得有点烦,代码阅读起来还要上下跳转.

  1. 使用闭包封装
  2. 实现
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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
private var actionDictKey: Void?
public typealias ButtonAction = (UIButton) -> ()

extension UIButton {

// MARK: - 属性
// 用于保存所有事件对应的闭包
private var actionDict: (Dictionary<String, ButtonAction>)? {
get {
return objc_getAssociatedObject(self, &actionDictKey) as? Dictionary<String, ButtonAction>
}
set {
objc_setAssociatedObject(self, &actionDictKey, newValue, .OBJC_ASSOCIATION_COPY_NONATOMIC)
}
}

// MARK: - API
@discardableResult
public func addTouchUpInsideAction(_ action: @escaping ButtonAction) -> UIButton {
self.addButton(action: action, for: .touchUpInside)
return self
}

@discardableResult
public func addTouchUpOutsideAction(_ action: @escaping ButtonAction) -> UIButton {
self.addButton(action: action, for: .touchUpOutside)
return self
}

@discardableResult
public func addTouchDownAction(_ action: @escaping ButtonAction) -> UIButton {
self.addButton(action: action, for: .touchDown)
return self
}

// ...其余事件可以自己扩展

// MARK: - 私有方法
private func addButton(action: @escaping ButtonAction, for controlEvents: UIControl.Event) {

let eventKey = String(controlEvents.rawValue)

if var actionDict = self.actionDict {
actionDict.updateValue(action, forKey: eventKey)
self.actionDict = actionDict
}else {
self.actionDict = [eventKey: action]
}

switch controlEvents {
case .touchUpInside:
addTarget(self, action: #selector(touchUpInsideControlEvent), for: .touchUpInside)
case .touchUpOutside:
addTarget(self, action: #selector(touchUpOutsideControlEvent), for: .touchUpOutside)
case .touchDown:
addTarget(self, action: #selector(touchDownControlEvent), for: .touchDown)
default:
break
}
}

// 响应事件
@objc private func touchUpInsideControlEvent() {
executeControlEvent(.touchUpInside)
}
@objc private func touchUpOutsideControlEvent() {
executeControlEvent(.touchUpOutside)
}
@objc private func touchDownControlEvent() {
executeControlEvent(.touchDown)
}

@objc private func executeControlEvent(_ event: UIControl.Event) {
let eventKey = String(event.rawValue)
if let actionDict = self.actionDict, let action = actionDict[eventKey] {
action(self)
}
}
}
  1. 使用

    1
    2
    3
    4
    5
    6
    7
    8
    btn
    .addTouchUpInsideAction { btn in
    print("addTouchUpInsideAction")
    }.addTouchUpOutsideAction { btn in
    print("addTouchUpOutsideAction")
    }.addTouchDownAction { btn in
    print("addTouchDownAction")
    }
  2. 实现原理
    利用runtime在按钮的extension中添加一个字典属性,key对应的是事件类型,value对应的是该事件类型所要执行的闭包.然后再添加按钮的监听事件,在响应方法中,根据事件类型找到并执行对应的闭包.

链式调用就是不断返回自身.