raf*_*lio 40 closures uibutton ios swift
我想将一个UIButton连接到一段代码 - 从我发现的,在Swift中执行此操作的首选方法仍然是使用该addTarget(target: AnyObject?, action: Selector, forControlEvents: UIControlEvents)
函数.这使用该Selector
结构可能是为了向后兼容Obj-C库.我想我理解@selector
Obj-C 的原因- 能够引用一个方法,因为在Obj-C方法中不是一流的值.
但在Swift中,函数是一流的值.有没有办法将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)
Run Code Online (Sandbox Code Playgroud)
据我所知,上述目前是不可能的.考虑到Swift看起来像是一个非常实用的功能,为什么会这样?这两个选项可以明显共存,以实现向后兼容.为什么这不像JS中的onClick()更像?似乎将UIButton连接到目标 - 动作对的唯一方法是使用仅出于向后兼容性原因而存在的东西(Selector
).
我的用例是在循环中为不同的对象创建UIButtons,然后将每个对象挂钩到一个闭包.(在字典/子类化UIButton中设置标记/查找是脏的半解决方案,但我对如何在功能上执行此操作感兴趣,即此闭包方法)
Mar*_*rný 26
您可以通过添加辅助闭包装置(ClosureSleeve)并将其作为关联对象添加到控件中来替换目标操作,从而保留它.
这是与n13(上)答案中类似的解决方案.但我发现它更简单,更优雅.更直接地调用闭包,并自动保留包装器(作为关联对象添加).
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 = .primaryActionTriggered, action: @escaping () -> ()) {
let sleeve = ClosureSleeve(attachTo: self, closure: action)
addTarget(sleeve, action: #selector(ClosureSleeve.invoke), for: controlEvents)
}
}
Run Code Online (Sandbox Code Playgroud)
用法:
button.addAction {
print("Hello")
}
Run Code Online (Sandbox Code Playgroud)
它会自动挂钩到与UIButton .primaryActionTriggered
相等的事件.touchUpInside
.
n13*_*n13 19
您认为应该在库中的一般方法,但不是:写一个类别.在GitHub上有很多这个特别的,但在Swift中找不到一个,所以我写了自己的:
===把它放在自己的文件中,比如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)
}
}
Run Code Online (Sandbox Code Playgroud)
然后像这样调用它:
myButton.block_setAction { sender in
// if you're referencing self, use [unowned self] above to prevent
// a retain cycle
// your code here
}
Run Code Online (Sandbox Code Playgroud)
显然,这可以改进,可以有各种事件的选项(不仅仅是内部触摸)等等.但这对我有用.它比纯ObjC版本稍微复杂一些,因为需要块的包装器.Swift编译器不允许将块存储为"AnyObject".所以我把它包裹起来.
这不一定是"挂钩",但您可以通过继承UIButton来有效地实现此行为:
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)
}
}
Run Code Online (Sandbox Code Playgroud)
使用:
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")
}
Run Code Online (Sandbox Code Playgroud)
使用RxSwift可以轻松解决这个问题
import RxSwift
import RxCocoa
...
@IBOutlet weak var button:UIButton!
...
let taps = button.rx.tap.asDriver()
taps.drive(onNext: {
// handle tap
})
Run Code Online (Sandbox Code Playgroud)
编辑:
我想承认RxSwift/RxCocoa是一个非常重量级的依赖项,只是为了解决这个要求而添加到项目中.可能有更轻量级的解决方案或只是坚持目标/行动模式.
无论如何,如果处理应用程序和用户事件的通用声明方法的想法吸引你,那么一定要看看RxSwift.这是炸弹.
根据n13的解决方案,我制作了一个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)
}
}
Run Code Online (Sandbox Code Playgroud)
UIButton继承自UIControl,它处理输入的接收和转发到选择。根据文档,该操作是“标识操作消息的选择器。它不能为 NULL”。选择器严格来说是指向方法的指针。
我认为考虑到 Swift 似乎对闭包的重视,这是可能的,但事实似乎并非如此。
归档时间: |
|
查看次数: |
14723 次 |
最近记录: |