挂起UIButtonclosures? (迅速,目标行动)

我想将一个UIButton连接到一段代码 – 从我发现,在Swift中执行此操作的首选方法仍然是使用addTarget(target: AnyObject?, action: Selector, forControlEvents: UIControlEvents)函数。 这大概是为了向后兼容Obj-C库而使用Selector构造。 我认为我理解Obj-C中@selector的原因 – 能够引用一个方法,因为在Obj-C方法中不是一stream的值。

在Swift中,函数是一stream的值。 有没有办法将UIButton连接到闭包,类似这样的:

 // -- Some code here that sets up an object X let buttonForObjectX = UIButton() // -- configure properties here of the button in regards to object // -- for example title buttonForObjectX.addAction(action: {() in // this button is bound to object X, so do stuff relevant to X }, forControlEvents: UIControlEvents.TouchUpOutside) 

据我所知,上述目前是不可能的。 考虑到Swift看起来像是要实现一个function,为什么呢? 为了向后兼容,这两个选项可以清楚地共存。 为什么这不工作更像在JS中onClick()? 看起来,将UIButton连接到目标操作对的唯一方法是使用仅存在于向后兼容性( Selector )中的东西。

我的用例是在一个循环中为不同的对象创buildUIButtons,然后将每个对象都钩到一个闭包上。 (设置标签/查找字典/子类UIButton是肮脏的半解决scheme,但我对如何做到这一点function感兴趣,即这种封闭方法)

任何你认为应该在图书馆里的一般方法,但不是:写一个类别。 GitHub上有很多这样的特性,但在Swift中没有find,所以我写了自己的:

===把它放在自己的文件里,比如UIButton + Block.swift ===

 import ObjectiveC var ActionBlockKey: UInt8 = 0 // a type for our action block closure typealias BlockButtonActionBlock = (sender: UIButton) -> Void class ActionBlockWrapper : NSObject { var block : BlockButtonActionBlock init(block: BlockButtonActionBlock) { self.block = block } } extension UIButton { func block_setAction(block: BlockButtonActionBlock) { objc_setAssociatedObject(self, &ActionBlockKey, ActionBlockWrapper(block: block), objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC) addTarget(self, action: "block_handleAction:", forControlEvents: .TouchUpInside) } func block_handleAction(sender: UIButton) { let wrapper = objc_getAssociatedObject(self, &ActionBlockKey) as! ActionBlockWrapper wrapper.block(sender: sender) } } 

然后像这样调用它:

 myButton.block_setAction { sender in // if you're referencing self, use [unowned self] above to prevent // a retain cycle // your code here } 

显然这可以改善,可以有各种事件的select(不只是在里面),等等。 但是这对我有效。 它比纯粹的ObjC版本稍微复杂一点,因为需要一个包装块。 Swift编译器不允许将块存储为“AnyObject”。 所以我只是把它包起来

这不一定是“挂钩”,但你可以通过inheritanceUIButton来有效地实现这种行为:

 class ActionButton: UIButton { var touchDown: ((button: UIButton) -> ())? var touchExit: ((button: UIButton) -> ())? var touchUp: ((button: UIButton) -> ())? required init?(coder aDecoder: NSCoder) { fatalError("init(coder:)") } override init(frame: CGRect) { super.init(frame: frame) setupButton() } func setupButton() { //this is my most common setup, but you can customize to your liking addTarget(self, action: #selector(touchDown(_:)), forControlEvents: [.TouchDown, .TouchDragEnter]) addTarget(self, action: #selector(touchExit(_:)), forControlEvents: [.TouchCancel, .TouchDragExit]) addTarget(self, action: #selector(touchUp(_:)), forControlEvents: [.TouchUpInside]) } //actions func touchDown(sender: UIButton) { touchDown?(button: sender) } func touchExit(sender: UIButton) { touchExit?(button: sender) } func touchUp(sender: UIButton) { touchUp?(button: sender) } } 

使用:

 let button = ActionButton(frame: buttonRect) button.touchDown = { button in print("Touch Down") } button.touchExit = { button in print("Touch Exit") } button.touchUp = { button in print("Touch Up") } 

您可以通过添加一个帮助封闭包装器(ClosureSleeve)并将其作为关联对象添加到控件中来将其replace为闭包,从而保留它。

这与n13 (顶部)答案中的答案类似。 但我觉得它更简单,更优雅。 闭包被直接调用,包装被自动保留(作为关联对象添加)。

斯威夫特3和4

 class ClosureSleeve { let closure: () -> () init(attachTo: AnyObject, closure: @escaping () -> ()) { self.closure = closure objc_setAssociatedObject(attachTo, "[\(arc4random())]", self, .OBJC_ASSOCIATION_RETAIN) } @objc func invoke() { closure() } } extension UIControl { func addAction(for controlEvents: UIControlEvents, action: @escaping () -> ()) { let sleeve = ClosureSleeve(attachTo: self, closure: action) addTarget(sleeve, action: #selector(ClosureSleeve.invoke), for: controlEvents) } } 

用法:

 button.addAction(for: .touchUpInside) { print("Hello") } 

这很容易使用RxSwift解决

 import RxSwift import RxCocoa ... @IBOutlet weak var button:UIButton! ... let taps = button.rx.tap.asDriver() taps.drive(onNext: { // handle tap }) 

根据n13的解决scheme ,我做了一个swift3版本。

希望能帮助像我这样的人。

 import Foundation import UIKit import ObjectiveC var ActionBlockKey: UInt8 = 0 // a type for our action block closure typealias BlockButtonActionBlock = (_ sender: UIButton) -> Void class ActionBlockWrapper : NSObject { var block : BlockButtonActionBlock init(block: @escaping BlockButtonActionBlock) { self.block = block } } extension UIButton { func block_setAction(block: @escaping BlockButtonActionBlock, for control: UIControlEvents) { objc_setAssociatedObject(self, &ActionBlockKey, ActionBlockWrapper(block: block), objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC) self.addTarget(self, action: #selector(UIButton.block_handleAction), for: .touchUpInside) } func block_handleAction(sender: UIButton, for control:UIControlEvents) { let wrapper = objc_getAssociatedObject(self, &ActionBlockKey) as! ActionBlockWrapper wrapper.block(sender) } } 

UIButton从UIControlinheritance,它处理接收input和转发到select。 根据文档,动作是“标识动作消息的select器,它不能为NULL”。 select器是一个指向方法的指针。

我认为,鉴于斯威夫特的重点似乎放在closures,这将是可能的,但这似乎并非如此。

你可以用一个代理类来处理这个事件,这个代理类通过目标/动作(select器)机制将事件路由到你的closures。 我已经做了这个手势识别器,但相同的模式应该控制。

你可以做这样的事情:

 import UIKit @objc class ClosureDispatch { init(f:()->()) { self.action = f } func execute() -> () { action() } let action: () -> () } var redBlueGreen:[String] = ["Red", "Blue", "Green"] let buttons:[UIButton] = map(0..<redBlueGreen.count) { i in let text = redBlueGreen[i] var btn = UIButton(frame: CGRect(x: i * 50, y: 0, width: 100, height: 44)) btn.setTitle(text, forState: .Normal) btn.setTitleColor(UIColor.redColor(), forState: .Normal) btn.backgroundColor = UIColor.lightGrayColor() return btn } let functors:[ClosureDispatch] = map(buttons) { btn in let functor = ClosureDispatch(f:{ [unowned btn] in println("Hello from \(btn.titleLabel!.text!)") }) btn.addTarget(functor, action: "execute", forControlEvents: .TouchUpInside) return functor } 

这个的一个警告是,因为addTarget:…不保留目标,你需要坚持调度对象(与functors数组一样)。 你当然不需要坚持button,因为你可以通过闭包中的捕获引用来实现,但是可能需要明确的引用。

PS。 我试图在操场上testing,但无法使sendActionsForControlEvents工作。 尽pipe我已经使用这种方法来识别手势。

关联对象,包装和指针以及ObjectiveC的导入是不必要的,至less在Swift 3中是这样。这个工作很好,而且更加Swift-y。 如果你觉得它更具可读性,那么可以在() -> ()为其引入一个typealias,我发现直接读取块签名更容易一些。

 import UIKit class BlockButton: UIButton { fileprivate var onAction: (() -> ())? func addClosure(_ closure: @escaping () -> (), for control: UIControlEvents) { self.addTarget(self, action: #selector(actionHandler), for: control) self.onAction = closure } dynamic fileprivate func actionHandler() { onAction?() } }