在Swift中,你可以为这样的按钮创建一个函数:
button.addTarget(self, action: #selector(buttonAction), forControlEvents: .TouchUpInside)
但有一种方法我可以这样做:
button.whenButtonIsClicked({Insert code here})
这样我甚至没有为按钮声明一个显式函数。我知道我可以使用按钮标签,但我更愿意这样做。
答案 0 :(得分:10)
创建自己的UIButton
子类来执行此操作:
class MyButton: UIButton {
var action: (() -> Void)?
func whenButtonIsClicked(action: @escaping () -> Void) {
self.action = action
self.addTarget(self, action: #selector(MyButton.clicked), for: .touchUpInside)
}
// Button Event Handler:
// I have not marked this as @IBAction because it is not intended to
// be hooked up to Interface Builder
@objc func clicked() {
action?()
}
}
以编程方式创建按钮时,将MyButton
替换为UIButton
,然后调用whenButtonIsClicked
来设置其功能。
您也可以在故事板中使用此UIButton
(仅将其课程更改为MyButton
),然后在whenButtonIsClicked
中致电viewDidLoad
。
@IBOutlet weak var theButton: MyButton!
var count = 0
override func viewDidLoad() {
super.viewDidLoad()
// be sure to declare [unowned self] if you access
// properties or methods of the class so that you
// don't create a strong reference cycle
theButton.whenButtonIsClicked { [unowned self] in
self.count += 1
print("count = \(self.count)")
}
功能更强大的实施
认识到程序员可能想要处理的事件多于.touchUpInside
,我编写了这个功能更强的版本,它支持每UIButton
个多个闭包,每个事件类型有多个闭包。
class ClosureButton: UIButton {
private var actions = [UInt : [((UIControl.Event) -> Void)]]()
private let funcDict: [UInt : Selector] = [
UIControl.Event.touchCancel.rawValue: #selector(eventTouchCancel),
UIControl.Event.touchDown.rawValue: #selector(eventTouchDown),
UIControl.Event.touchDownRepeat.rawValue: #selector(eventTouchDownRepeat),
UIControl.Event.touchUpInside.rawValue: #selector(eventTouchUpInside),
UIControl.Event.touchUpOutside.rawValue: #selector(eventTouchUpOutside),
UIControl.Event.touchDragEnter.rawValue: #selector(eventTouchDragEnter),
UIControl.Event.touchDragExit.rawValue: #selector(eventTouchDragExit),
UIControl.Event.touchDragInside.rawValue: #selector(eventTouchDragInside),
UIControl.Event.touchDragOutside.rawValue: #selector(eventTouchDragOutside)
]
func handle(events: [UIControl.Event], action: @escaping (UIControl.Event) -> Void) {
for event in events {
if var closures = actions[event.rawValue] {
closures.append(action)
actions[event.rawValue] = closures
} else {
guard let sel = funcDict[event.rawValue] else { continue }
self.addTarget(self, action: sel, for: event)
actions[event.rawValue] = [action]
}
}
}
private func callActions(for event: UIControl.Event) {
guard let actions = actions[event.rawValue] else { return }
for action in actions {
action(event)
}
}
@objc private func eventTouchCancel() { callActions(for: .touchCancel) }
@objc private func eventTouchDown() { callActions(for: .touchDown) }
@objc private func eventTouchDownRepeat() { callActions(for: .touchDownRepeat) }
@objc private func eventTouchUpInside() { callActions(for: .touchUpInside) }
@objc private func eventTouchUpOutside() { callActions(for: .touchUpOutside) }
@objc private func eventTouchDragEnter() { callActions(for: .touchDragEnter) }
@objc private func eventTouchDragExit() { callActions(for: .touchDragExit) }
@objc private func eventTouchDragInside() { callActions(for: .touchDragInside) }
@objc private func eventTouchDragOutside() { callActions(for: .touchDragOutside) }
}
<强>演示强>
class ViewController: UIViewController {
var count = 0
override func viewDidLoad() {
super.viewDidLoad()
let button = ClosureButton(frame: CGRect(x: 50, y: 100, width: 60, height: 40))
button.setTitle("press me", for: .normal)
button.setTitleColor(.blue, for: .normal)
// Demonstration of handling a single UIControl.Event type.
// If your closure accesses self, be sure to declare [unowned self]
// to prevent a strong reference cycle
button.handle(events: [.touchUpInside]) { [unowned self] _ in
self.count += 1
print("count = \(self.count)")
}
// Define a second handler for touchUpInside:
button.handle(events: [.touchUpInside]) { _ in
print("I'll be called on touchUpInside too")
}
let manyEvents: [UIControl.Event] = [.touchCancel, .touchUpInside, .touchDown, .touchDownRepeat, .touchUpOutside, .touchDragEnter,
.touchDragExit, .touchDragInside, .touchDragOutside]
// Demonstration of handling multiple events
button.handle(events: manyEvents) { event in
switch event {
case .touchCancel:
print("touchCancel")
case .touchDown:
print("touchDown")
case .touchDownRepeat:
print("touchDownRepeat")
case .touchUpInside:
print("touchUpInside")
case .touchUpOutside:
print("touchUpOutside")
case .touchDragEnter:
print("touchDragEnter")
case .touchDragExit:
print("touchDragExit")
case .touchDragInside:
print("touchDragInside")
case .touchDragOutside:
print("touchDragOutside")
default:
break
}
}
self.view.addSubview(button)
}
}
答案 1 :(得分:1)
如果你不想做任何“有问题的”(即使用Objective-C的动态功能,或添加你自己的触摸处理程序等),并且纯粹在Swift中这样做,不幸的是这是不可能的。
每次在Swift中看到#selector
时,编译器都会调用objc_MsgSend
。 Swift不支持Objective-C的动态性。无论好坏,这意味着为了将这个选择器的使用换成一个块,你可能需要执行一些黑魔法来使它工作,你必须使用Objective-C结构来那样做。
如果您对“令人讨厌的动态Objective-C东西”没有任何疑虑,您可以通过在UIButton
上定义扩展来实现这一点,然后使用关联对象动态地将函数关联到对象。我要停在这里,但如果你想阅读更多内容,NSHipster在关联对象上有一个很棒的overview以及如何使用它们。
答案 2 :(得分:1)
这将工作! 确保您不更改按钮的标签
extension UIButton {
private func actionHandleBlock(action:(()->())? = nil) {
struct __ {
var closure : (() -> Void)?
typealias EmptyCallback = ()->()
static var action : [EmptyCallback] = []
}
if action != nil {
// __.action![(__.action?.count)!] = action!
self.tag = (__.action.count)
__.action.append(action!)
} else {
let exe = __.action[self.tag]
exe()
}
}
@objc private func triggerActionHandleBlock() {
self.actionHandleBlock()
}
func addAction(forControlEvents control :UIControlEvents, ForAction action:@escaping () -> Void) {
self.actionHandleBlock(action: action)
self.addTarget(self, action: #selector(triggerActionHandleBlock), for: control)
}
}
答案 3 :(得分:0)
你也可以只是UIView
的子类,并拥有像vacawama这样的闭包属性。
var action: () -> ()?
然后覆盖touchesBegan
方法以在触摸按钮时调用该函数。使用这种方法虽然你没有从使用UIBitton开始获得所有好处。
答案 4 :(得分:-1)
mainTimer