我有一个通用的控件类,它需要根据视图控制器设置按钮的完成。由于setLeftButtonActionWithClosure函数需要将一个闭包作为参数,该闭包应设置为解开的动作。如何可能在Swift中,因为我们需要将函数名称作为String传递给action:parameter。
func setLeftButtonActionWithClosure(completion: () -> Void)
{
self.leftButton.addTarget(<#target: AnyObject?#>, action: <#Selector#>, forControlEvents: <#UIControlEvents#>)
}
答案 0 :(得分:80)
与已经列出的类似的解决方案,但可能更轻的重量:
class ClosureSleeve {
let closure: ()->()
init (_ closure: @escaping ()->()) {
self.closure = closure
}
@objc func invoke () {
closure()
}
}
extension UIControl {
func addAction(for controlEvents: UIControl.Event, _ closure: @escaping ()->()) {
let sleeve = ClosureSleeve(closure)
addTarget(sleeve, action: #selector(ClosureSleeve.invoke), for: controlEvents)
objc_setAssociatedObject(self, "[\(arc4random())]", sleeve, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN)
}
}
用法:
button.addAction(for: .touchUpInside) {
print("Hello, Closure!")
}
或者如果避免保留循环:
self.button.addAction(for: .touchUpInside) { [unowned self] in
self.doStuff()
}
用户MH175提到如果他们使用控件上的“allTargets属性:静态Set_unnconditionalBridgeFromObjectiveC(_ :) - ”,将会获得运行时异常。从NSObject扩展ClosureSleeve将解决问题:
@objc class ClosureSleeve: NSObject {
let closure: ()->()
init (_ closure: @escaping ()->()) {
self.closure = closure
super.init()
}
@objc func invoke () {
closure()
}
}
答案 1 :(得分:25)
注意: 喜欢@EthanHuang说 “如果你有两个以上的实例,这个解决方案就不起作用了。所有的操作都会被最后一个任务覆盖。” 开发时请记住这一点,我会尽快发布另一种解决方案。
如果要将关闭作为目标添加到validate
,则必须使用UIButton
UIButton
类添加一个函数
Swift 5
extension
<强>旧版强>
import UIKit
extension UIButton {
private func actionHandler(action:(() -> Void)? = nil) {
struct __ { static var action :(() -> Void)? }
if action != nil { __.action = action }
else { __.action?() }
}
@objc private func triggerActionHandler() {
self.actionHandler()
}
func actionHandler(controlEvents control :UIControl.Event, ForAction action:@escaping () -> Void) {
self.actionHandler(action: action)
self.addTarget(self, action: #selector(triggerActionHandler), for: control)
}
}
和电话:
import UIKit
extension UIButton {
private func actionHandleBlock(action:(() -> Void)? = nil) {
struct __ {
static var action :(() -> Void)?
}
if action != nil {
__.action = action
} else {
__.action?()
}
}
@objc private func triggerActionHandleBlock() {
self.actionHandleBlock()
}
func actionHandle(controlEvents control :UIControlEvents, ForAction action:() -> Void) {
self.actionHandleBlock(action)
self.addTarget(self, action: "triggerActionHandleBlock", forControlEvents: control)
}
}
答案 2 :(得分:13)
您可以通过继承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)
}
}
使用:
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")
}
答案 3 :(得分:3)
这基本上是Armanoide's回答,但是有一些对我有用的微小变化:
UIButton
参数,允许您传递self
重命名函数和参数,对我来说,澄清正在发生的事情,例如通过区分Swift闭包和UIButton
动作。
private func setOrTriggerClosure(closure:((button:UIButton) -> Void)? = nil) {
//struct to keep track of current closure
struct __ {
static var closure :((button:UIButton) -> Void)?
}
//if closure has been passed in, set the struct to use it
if closure != nil {
__.closure = closure
} else {
//otherwise trigger the closure
__. closure?(button: self)
}
}
@objc private func triggerActionClosure() {
self.setOrTriggerClosure()
}
func setActionTo(closure:(UIButton) -> Void, forEvents :UIControlEvents) {
self.setOrTriggerClosure(closure)
self.addTarget(self, action:
#selector(UIButton.triggerActionClosure),
forControlEvents: forEvents)
}
虽然在这里有一些重型魔法,但很多道具给了阿玛诺德。
答案 4 :(得分:2)
这是一种通用的Swift 5方法。它在动作块内有一个发件人,并且消除了两次为同一事件添加动作
import UIKit
protocol Actionable {
associatedtype T = Self
func addAction(for controlEvent: UIControl.Event, action: ((T) -> Void)?)
}
private class ClosureSleeve<T> {
let closure: ((T) -> Void)?
let sender: T
init (sender: T, _ closure: ((T) -> Void)?) {
self.closure = closure
self.sender = sender
}
@objc func invoke() {
closure?(sender)
}
}
extension Actionable where Self: UIControl {
func addAction(for controlEvent: UIControl.Event, action: ((Self) -> Void)?) {
let previousSleeve = objc_getAssociatedObject(self, String(controlEvent.rawValue))
objc_removeAssociatedObjects(previousSleeve as Any)
removeTarget(previousSleeve, action: nil, for: controlEvent)
let sleeve = ClosureSleeve(sender: self, action)
addTarget(sleeve, action: #selector(ClosureSleeve<Self>.invoke), for: controlEvent)
objc_setAssociatedObject(self, String(controlEvent.rawValue), sleeve, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN)
}
}
extension UIControl: Actionable {}
答案 5 :(得分:1)
我已经开始使用 Armanoide 的答案,而不考虑它会被第二项任务覆盖的事实,主要是因为起初我需要它特定的地方,它没有& #39;重要。但它开始分崩离析。
我已经想出了一个使用 AssicatedObjects 的新实现,它没有这个限制,我认为它有一个更聪明的语法,但它不是一个完整的解决方案:
这是:
typealias ButtonAction = () -> Void
fileprivate struct AssociatedKeys {
static var touchUp = "touchUp"
}
fileprivate class ClosureWrapper {
var closure: ButtonAction?
init(_ closure: ButtonAction?) {
self.closure = closure
}
}
extension UIControl {
@objc private func performTouchUp() {
guard let action = touchUp else {
return
}
action()
}
var touchUp: ButtonAction? {
get {
let closure = objc_getAssociatedObject(self, &AssociatedKeys.touchUp)
guard let action = closure as? ClosureWrapper else{
return nil
}
return action.closure
}
set {
if let action = newValue {
let closure = ClosureWrapper(action)
objc_setAssociatedObject(
self,
&AssociatedKeys.touchUp,
closure as ClosureWrapper,
.OBJC_ASSOCIATION_RETAIN_NONATOMIC
)
self.addTarget(self, action: #selector(performTouchUp), for: .touchUpInside)
} else {
self.removeTarget(self, action: #selector(performTouchUp), for: .touchUpInside)
}
}
}
}
正如您所看到的,我已决定为touchUpInside
制作一个专门案例。我知道控件比这个有更多的事件,但是我们开玩笑的是谁?我们每个人都需要采取行动吗?!这种方式简单得多。
用法示例:
okBtn.touchUp = {
print("OK")
}
在任何情况下,如果您想扩展此答案,您可以为所有事件类型制作Set
个操作,或为其他事件添加更多事件的属性,它是相对简单。
干杯, 微米。
答案 6 :(得分:1)
<强>夫特强>
在尝试了所有解决方案之后,即使在可重复使用的表格视图单元格中的按钮
,这个也适用于所有情况import UIKit
typealias UIButtonTargetClosure = UIButton -> ()
class ClosureWrapper: NSObject {
let closure: UIButtonTargetClosure
init(_ closure: UIButtonTargetClosure) {
self.closure = closure
}
}
extension UIButton {
private struct AssociatedKeys {
static var targetClosure = "targetClosure"
}
private var targetClosure: UIButtonTargetClosure? {
get {
guard let closureWrapper = objc_getAssociatedObject(self, &AssociatedKeys.targetClosure) as? ClosureWrapper else { return nil }
return closureWrapper.closure
}
set(newValue) {
guard let newValue = newValue else { return }
objc_setAssociatedObject(self, &AssociatedKeys.targetClosure, ClosureWrapper(newValue), objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
}
func addTargetClosure(closure: UIButtonTargetClosure) {
targetClosure = closure
addTarget(self, action: #selector(UIButton.closureAction), forControlEvents: .TouchUpInside)
}
func closureAction() {
guard let targetClosure = targetClosure else { return }
targetClosure(self)
}
}
然后你这样称呼它:
loginButton.addTargetClosure { _ in
// login logics
}
答案 7 :(得分:1)
与已经列出的类似的解决方案,但可能更轻,并且不依赖于随机性来生成唯一ID:
class ClosureSleeve {
let closure: ()->()
init (_ closure: @escaping ()->()) {
self.closure = closure
}
@objc func invoke () {
closure()
}
}
extension UIControl {
func add (for controlEvents: UIControlEvents, _ closure: @escaping ()->()) {
let sleeve = ClosureSleeve(closure)
addTarget(sleeve, action: #selector(ClosureSleeve.invoke), for: controlEvents)
objc_setAssociatedObject(self, String(ObjectIdentifier(self).hashValue) + String(controlEvents.rawValue), sleeve,
objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN)
}
}
用法:
button.add(for: .touchUpInside) {
print("Hello, Closure!")
}
答案 8 :(得分:1)
这里是answer的aepryus的有趣变体。我的版本使用Combine的Cancellable
协议:
objc_setAssociatedObject
的需要。// Swift 5
import Combine
import UIKit
class BlockObject: NSObject {
let block: () -> Void
init(block: @escaping () -> Void) {
self.block = block
}
@objc dynamic func execute() {
block()
}
}
extension UIControl {
func addHandler(
for controlEvents: UIControl.Event,
block: @escaping () -> Void)
-> Cancellable
{
let blockObject = BlockObject(block: block)
addTarget(blockObject, action: #selector(BlockObject.execute), for: controlEvents)
return AnyCancellable {
self.removeTarget(blockObject, action: #selector(BlockObject.execute), for: controlEvents)
}
}
}
用法:
let button = UIButton(type: .system)
// Add the handler
let cancellable = button.addHandler(for: .touchUpInside) {
print("Button pressed!")
}
// Remove the handler
cancellable.cancel()
别忘了存储对Cancellable
的引用,否则处理程序将立即注销。
答案 9 :(得分:1)
Swift 4.2(用于UIControl和UIGestureRecognizer),并通过swift扩展存储的属性范例删除目标。
选择器的包装器类
class Target {
private let t: () -> ()
init(target t: @escaping () -> ()) { self.t = t }
@objc private func s() { t() }
public var action: Selector {
return #selector(s)
}
}
带有associatedtype
的协议,因此我们可以隐藏objc_
代码
protocol PropertyProvider {
associatedtype PropertyType: Any
static var property: PropertyType { get set }
}
protocol ExtensionPropertyStorable: class {
associatedtype Property: PropertyProvider
}
扩展名以使属性成为默认值并可用
extension ExtensionPropertyStorable {
typealias Storable = Property.PropertyType
var property: Storable {
get { return objc_getAssociatedObject(self, String(describing: type(of: Storable.self))) as? Storable ?? Property.property }
set { return objc_setAssociatedObject(self, String(describing: type(of: Storable.self)), newValue, .OBJC_ASSOCIATION_RETAIN) }
}
}
让我们运用魔法
extension UIControl: ExtensionPropertyStorable {
class Property: PropertyProvider {
static var property = [String: Target]()
}
func addTarget(for controlEvent: UIControl.Event = .touchUpInside, target: @escaping () ->()) {
let key = String(describing: controlEvent)
let target = Target(target: target)
addTarget(target, action: target.action, for: controlEvent)
property[key] = target
}
func removeTarget(for controlEvent: UIControl.Event = .touchUpInside) {
let key = String(describing: controlEvent)
let target = property[key]
removeTarget(target, action: target?.action, for: controlEvent)
property[key] = nil
}
}
和手势
extension UIGestureRecognizer: ExtensionPropertyStorable {
class Property: PropertyProvider {
static var property: Target?
}
func addTarget(target: @escaping () -> ()) {
let target = Target(target: target)
addTarget(target, action: target.action)
property = target
}
func removeTarget() {
let target = property
removeTarget(target, action: target?.action)
property = nil
}
}
用法示例:
button.addTarget {
print("touch up inside")
}
button.addTarget { [weak self] in
print("this will only happen once")
self?.button.removeTarget()
}
button.addTarget(for: .touchDown) {
print("touch down")
}
slider.addTarget(for: .valueChanged) {
print("value changed")
}
textView.addTarget(for: .allEditingEvents) { [weak self] in
self?.editingEvent()
}
gesture.addTarget { [weak self] in
self?.gestureEvent()
self?.otherGestureEvent()
self?.gesture.removeTarget()
}
答案 10 :(得分:1)
现在可以在iOS 14上进行此操作。创建UIAction
时,您可以传递带有处理程序闭包的UIButton
:
let action = UIAction(title: "") { action in
print("Button tapped!")
}
UIButton(type: .system, primaryAction: action)
或更短:
UIButton(type: .system, primaryAction: UIAction(title: "") { action in
print("Button tapped!")
})
答案 11 :(得分:1)
我对UIControl进行了扩展,可以让您真正轻松地对任何UIControl上的任何操作使用闭包。
您可以在这里找到它:https://gist.github.com/nathan-fiscaletti/8308f00ff364b72b6a6dec57c4b13d82
以下是一些实际的例子:
设置按钮操作
<xsl:template match="/">
<xsl:for-each-group select="//Measurement" group-by="../@name">
<xsl:element name="Level">
<xsl:attribute name="name" select="current-grouping-key()"/>
<xsl:for-each select="current-group()">
<xsl:copy-of select="current()"/>
</xsl:for-each>
</xsl:element>
</xsl:for-each-group>
</xsl:template>
检测更改值的开关
myButton.action(.touchUpInside, { (sender: UIControl) in
// do something
})
答案 12 :(得分:1)
这是一个很好的框架:HandlersKit。最大的优点是,您可以在闭包内部访问发件人,而无需进行类型转换或可选的拆包。
UIButton示例:
import HandlersKit
let button = MyActivityIndicatorButton()
button.onTap { (sender: MyActivityIndicatorButton) in
sender.showActivityIndicator()
}
UISwitch的示例:
let switchView = UISwitch(frame: CGRect(x: 0.0, y: 0.0, width: 100.0, height: 50.0))
switchView.onChange { isOn in
print("SwitchView is: \(isOn)")
}
答案 13 :(得分:0)
我更改了@Nathan F 发布的 UIControl 的一个小扩展。 here
我使用 objc_setAssociatedObject
和 objc_getAssociatedObject
来获取/设置闭包,并使用所有创建的按钮的键删除了全局静态变量。
所以现在为每个实例存储事件并在 dealloc 后释放
extension UIControl {
typealias Handlers = [UInt:((UIControl) -> Void)]
private enum AssociatedKey {
static var actionHandlers = "UIControl.actionHandlers"
}
/**
* A map of closures, mapped as [ event : action ] .
*/
private var actionHandlers: Handlers {
get {
return objc_getAssociatedObject(self, &AssociatedKey.actionHandlers) as? Handlers ?? [:]
}
set(newValue) {
objc_setAssociatedObject(self, &AssociatedKey.actionHandlers, newValue, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN)
}
}
}
您可以在这里找到它:https://gist.github.com/desyatov/6ed83de58ca1146d85fedab461a69b12
以下是一些示例:
myButton.action(.touchUpInside, { (sender: UIControl) in
// do something
})
答案 14 :(得分:0)
下面的扩展是为 UIView 的关卡添加点击手势,它适用于任何基于 UIView 的东西。
注意:我几年前在 StackOverflow 上也找到了这个解决方案,但现在我似乎找不到原始来源。
extension UIView {
// In order to create computed properties for extensions, we need a key to
// store and access the stored property
fileprivate struct AssociatedObjectKeys {
static var tapGestureRecognizer = "MediaViewerAssociatedObjectKey_mediaViewer"
}
fileprivate typealias Action = (() -> Void)?
// Set our computed property type to a closure
fileprivate var tapGestureRecognizerAction: Action? {
set {
if let newValue = newValue {
// Computed properties get stored as associated objects
objc_setAssociatedObject(self, &AssociatedObjectKeys.tapGestureRecognizer, newValue, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN)
}
}
get {
let tapGestureRecognizerActionInstance = objc_getAssociatedObject(self, &AssociatedObjectKeys.tapGestureRecognizer) as? Action
return tapGestureRecognizerActionInstance
}
}
// This is the meat of the sauce, here we create the tap gesture recognizer and
// store the closure the user passed to us in the associated object we declared above
public func addTapGestureRecognizer(action: (() -> Void)?) {
self.isUserInteractionEnabled = true
self.tapGestureRecognizerAction = action
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(handleTapGesture))
tapGestureRecognizer.cancelsTouchesInView = false
self.addGestureRecognizer(tapGestureRecognizer)
}
// Every time the user taps on the UIImageView, this function gets called,
// which triggers the closure we stored
@objc fileprivate func handleTapGesture(sender: UITapGestureRecognizer) {
if let action = self.tapGestureRecognizerAction {
action?()
} else {
print("no action")
}
}
}
用法示例:
let button = UIButton()
button.addTapGestureRecognizer {
print("tapped")
}
let label = UILabel()
label.addTapGestureRecognizer {
print("label tapped")
}
答案 15 :(得分:0)
@Armanoide解决方案很酷,因为它在内部使用了struct
和static var
的技巧,但是如果您重复使用一个按钮几次则并不完美,因为在这种情况下,动作关闭将始终存储最后一个处理程序。
我已经为UIKitPlus库修复了它
import UIKit
extension UIControl {
private func actionHandler(action: (() -> Void)? = nil) {
struct Storage { static var actions: [Int: (() -> Void)] = [:] }
if let action = action {
Storage.actions[hashValue] = action
} else {
Storage.actions[hashValue]?()
}
}
@objc func triggerActionHandler() {
actionHandler()
}
func actionHandler(controlEvents control: UIControl.Event, forAction action: @escaping () -> Void) {
actionHandler(action: action)
addTarget(self, action: #selector(triggerActionHandler), for: control)
}
}
答案 16 :(得分:0)
我的解决方案。
typealias UIAction = () -> Void;
class Button: UIButton {
public var touchUp :UIAction? {
didSet {
self.setup()
}
}
func setup() -> Void {
self.addTarget(self, action: #selector(touchInside), for: .touchUpInside)
}
@objc private func touchInside() -> Void {
self.touchUp!()
}
}
答案 17 :(得分:0)
class ViewController : UIViewController {
var aButton: UIButton!
var assignedClosure: (() -> Void)? = nil
override func loadView() {
let view = UIView()
view.backgroundColor = .white
aButton = UIButton()
aButton.frame = CGRect(x: 95, y: 200, width: 200, height: 20)
aButton.backgroundColor = UIColor.red
aButton.addTarget(self, action: .buttonTapped, for: .touchUpInside)
view.addSubview(aButton)
self.view = view
}
func fizzleButtonOn(events: UIControlEvents, with: @escaping (() -> Void)) {
assignedClosure = with
aButton.removeTarget(self, action: .buttonTapped, for: .allEvents)
aButton.addTarget(self, action: .buttonTapped, for: events)
}
@objc func buttonTapped() {
guard let closure = assignedClosure else {
debugPrint("original tap")
return
}
closure()
}
}
fileprivate extension Selector {
static let buttonTapped = #selector(ViewController.buttonTapped)
}
然后在应用程序生命周期的某个时刻,您将改变实例的闭包。这是一个例子
fizzleButtonOn(events: .touchUpInside, with: { debugPrint("a new tap action") })
答案 18 :(得分:0)
另一项优化(如果您在很多地方使用它并且不想复制对objc_setAssociatedObject
的调用,则非常有用)。它使我们不必担心objc_setAssociatedObject
中的脏部分,并将其保留在ClosureSleeve
的构造函数中:
class ClosureSleeve {
let closure: () -> Void
init(
for object: AnyObject,
_ closure: @escaping () -> Void
) {
self.closure = closure
objc_setAssociatedObject(
object,
String(format: "[%d]", arc4random()),
self,
objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN
)
}
@objc func invoke () {
closure()
}
}
所以你的扩展程序看起来会更清晰一些:
extension UIControl {
func add(
for controlEvents: UIControlEvents,
_ closure: @escaping ()->()
) {
let sleeve = ClosureSleeve(
for: self,
closure
)
addTarget(
sleeve,
action: #selector(ClosureSleeve.invoke),
for: controlEvents
)
}
}