简体   繁体   English

Swift使用选择器参数,如闭包

[英]Swift use selector argument like a closure

I was just wondering if it was possible to pass a function to a button action (which is usually a selector). 我只是想知道是否可以将一个函数传递给一个按钮动作(通常是一个选择器)。

For example, normally I'd say: 例如,通常我会说:

UIBarButtonItem(title: "Press", style: .Done, target: self, action: "functionToCall")

func functionToCall() {
    // Do something
}

But I was wondering if it's possible to do something like: 但我想知道是否可以做以下事情:

UIBarButtonItem(title: "Press", style: .Done, target: self, action: {
    // Do Something
})

Reason I want to do this is because my function is super simple and it seems like it would be neater and more Swift-like what with the emphasis they are placing on closures. 我想要这样做的原因是因为我的功能非常简单,看起来它更整洁,更像Swift,因为它们强调它们放在封闭上。

Here's an updated solution for Swift 3. 这是Swift 3的更新解决方案。

class BlockBarButtonItem: UIBarButtonItem {
  private var actionHandler: ((Void) -> Void)?

  convenience init(title: String?, style: UIBarButtonItemStyle, actionHandler: ((Void) -> Void)?) {
    self.init(title: title, style: style, target: nil, action: #selector(barButtonItemPressed))
    self.target = self
    self.actionHandler = actionHandler
  }

  convenience init(image: UIImage?, style: UIBarButtonItemStyle, actionHandler: ((Void) -> Void)?) {
    self.init(image: image, style: style, target: nil, action: #selector(barButtonItemPressed))
    self.target = self
    self.actionHandler = actionHandler
  }

  func barButtonItemPressed(sender: UIBarButtonItem) {
    actionHandler?()
  }
}

This is an alternative solution without subclassing: 这是一个没有子类化的替代解决方案:

extension UIBarButtonItem {
    private struct AssociatedObject {
        static var key = "action_closure_key"
    }

    var actionClosure: (()->Void)? {
        get {
            return objc_getAssociatedObject(self, &AssociatedObject.key) as? ()->Void
        }
        set {
            objc_setAssociatedObject(self, &AssociatedObject.key, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
            target = self
            action = #selector(didTapButton(sender:))
        }
    }

    @objc func didTapButton(sender: Any) {
        actionClosure?()
    }
}

It relies on the associated objects from the Objective-C runtime to add a closure/block property. 它依赖于Objective-C运行时的关联对象来添加闭包/块属性。

When set, it changes the target to itself and points the selector to a new function that calls the closure if it exists. 设置时,它将目标更改为自身,并将选择器指向一个新函数,如果存在,则调用该闭包。

With this, in any moment you can just set the actionClosure of any UIBarButtonItem and expect everything to work, here's an example: 这种情况,在任何时刻,你可以只设置actionClosure任何UIBarButtonItem ,并期望一切工作,这里有一个例子:

let button = UIBarButtonItem(
    barButtonSystemItem: .action,
    target: nil, action: nil
)
button.actionClosure = {
    print("hello")
}

A post on Reddit explains a solution for this with a custom component - https://www.reddit.com/r/swift/comments/3fjzap/creating_button_action_programatically_using Reddit上的一篇文章用自定义组件解释了这个问题 - https://www.reddit.com/r/swift/comments/3fjzap/creating_button_action_programatically_using

To use it though I had to add the Button programatically rather than through storyboard. 要使用它,虽然我必须以编程方式而不是通过故事板添加Button。 Here was how I did it. 我是这样做的。

let tempVariableIWantToReference = "Classy"

navigationTitle.leftBarButtonItem = BlockBarButtonItem.init(
   title: "< Back", style: UIBarButtonItemStyle.Plain, 
   actionHandler: { () -> Void in
       print("Hey I'm in a closure")
       print("I can even reference temporary variables. \(self.tempVariableIWantToReference)!!!")
   })
  • Updated for Swift 5. 更新了Swift 5。
  • Implements all of the convenience initializers that have targets. 实现具有目标的所有便利初始化程序。
  • Adds a typealias for the function signature to clean up the syntax. 添加一个typealias的函数签名清理语法。
  • Passes the UIBarButtonItem into the action handler in keeping with Cocoa conventions. UIBarButtonItem传递给动作处理程序以符合Cocoa约定。
import UIKit

class SwiftBarButtonItem: UIBarButtonItem {
    typealias ActionHandler = (UIBarButtonItem) -> Void

    private var actionHandler: ActionHandler?

    convenience init(image: UIImage?, style: UIBarButtonItem.Style, actionHandler: ActionHandler?) {
        self.init(image: image, style: style, target: nil, action: #selector(barButtonItemPressed(sender:)))
        target = self
        self.actionHandler = actionHandler
    }

    convenience init(title: String?, style: UIBarButtonItem.Style, actionHandler: ActionHandler?) {
        self.init(title: title, style: style, target: nil, action: #selector(barButtonItemPressed(sender:)))
        target = self
        self.actionHandler = actionHandler
    }

    convenience init(barButtonSystemItem systemItem: UIBarButtonItem.SystemItem, actionHandler: ActionHandler?) {
        self.init(barButtonSystemItem: systemItem, target: nil, action: #selector(barButtonItemPressed(sender:)))
        target = self
        self.actionHandler = actionHandler
    }

    @objc func barButtonItemPressed(sender: UIBarButtonItem) {
        actionHandler?(sender)
    }
}

Unfortunatelly it is not possible with Apple provided initialisers. 不幸的是,Apple提供的初始化程序是不可能的。 The way this works in the background is with reflection, and providing a closure is something completely different which isn't currently supported. 它在后台运行的方式是使用反射,并且提供闭包是完全不同的,目前不支持。

You might be able to create a custom solution with some hacking or Apple may introduce that in the future. 您可能能够通过一些黑客攻击来创建自定义解决方案,或者Apple可能会在将来引入该解决方案。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM