Swift use selector argument like a closure

杀马特。学长 韩版系。学妹 提交于 2019-12-03 14:56:56

问题


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.


回答1:


Here's an updated solution for 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?()
  }
}



回答2:


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.

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:

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



回答3:


  • Updated for Swift 5.
  • Implements all of the convenience initializers that have targets.
  • Adds a typealias for the function signature to clean up the syntax.
  • Passes the UIBarButtonItem into the action handler in keeping with Cocoa conventions.
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)
    }
}



回答4:


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

To use it though I had to add the Button programatically rather than through storyboard. 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)!!!")
   })



回答5:


Unfortunatelly it is not possible with Apple provided initialisers. 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.



来源:https://stackoverflow.com/questions/33652284/swift-use-selector-argument-like-a-closure

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!