Can I make #selector refer to a closure in Swift?

后端 未结 11 1783
深忆病人
深忆病人 2020-12-09 15:12

I want to make a selector argument of my method refer to a closure property, both of them exist in the same scope. For example,

func backgroundC         


        
相关标签:
11条回答
  • 2020-12-09 15:33

    You can use ActionClosurable which support UIControl, UIButton, UIRefreshControl, UIGestureRecognizer and UIBarButtonItem. https://github.com/takasek/ActionClosurable

    Bellow show example of UIBarButtonItem

    // UIBarButtonItem
    let barButtonItem = UIBarButtonItem(title: "title", style: .plain) { _ in
        print("barButtonItem title")
    }
    
    0 讨论(0)
  • 2020-12-09 15:33

    @werediver's answer is excellent. Here's an update that allows you to call it as a function.

    import Foundation
    
    public extension Selector {
      /// Wraps a closure in a `Selector`.
      /// - Note: Callable as a function.
      final class Perform: NSObject {
        public init(_ perform: @escaping () -> Void) {
          self.perform = perform
          super.init()
        }
    
        private let perform: () -> Void
      }
    }
    
    //MARK: public
    public extension Selector.Perform {
      @objc func callAsFunction() { perform() }
      var selector: Selector { #selector(callAsFunction) }
    }
    

    You need to manage strong references to Selector.Performs. One way to do that is to subclass UIKit classes that were designed to work with target-action:

    /// A `UITapGestureRecognizer` that wraps a closure.
    public final class TapGestureRecognizer: UITapGestureRecognizer {
      public init(_ perform: @escaping () -> Void) {
        self.perform = .init(perform)
        super.init(target: self.perform, action: self.perform.selector)
      }
    
      public let perform: Selector.Perform
    }
    
    let tapRecognizer = TapGestureRecognizer { print("                                                                    
    0 讨论(0)
  • 2020-12-09 15:34

    So my answer to having a selector be assigned to a closure in a swift like manner is similar to some of the answers already, but I thought I would share a real life example of how I did it within a UIViewController extension.

    fileprivate class BarButtonItem: UIBarButtonItem {
      var actionCallback: ( () -> Void )?
      func buttonAction() {
        actionCallback?()
      }
    }
    
    fileprivate extension Selector {
      static let onBarButtonAction = #selector(BarButtonItem.buttonAction)
    }
    
    extension UIViewController {
      func createBarButtonItem(title: String, action: @escaping () -> Void ) -> UIBarButtonItem {
        let button = BarButtonItem(title: title, style: .plain, target nil, action: nil)
        button.actionCallback = action
        button.action = .onBarButtonAction
        return button
      }
    }
    
    // Example where button is inside a method of a UIViewController 
    // and added to the navigationItem of the UINavigationController
    
    let button = createBarButtonItem(title: "Done"){
      print("Do something when done")
    }
    
    navigationItem.setLeftbarButtonItems([button], animated: false)
    
    0 讨论(0)
  • 2020-12-09 15:37

    Not directly, but some workarounds are possible. Take a look at the following example.

    /// Target-Action helper.
    final class Action: NSObject {
    
        private let _action: () -> ()
    
        init(action: @escaping () -> ()) {
            _action = action
            super.init()
        }
    
        @objc func action() {
            _action()
        }
    
    }
    
    let action1 = Action { print("action1 triggered") }
    
    let button = UIButton()
    button.addTarget(action1, action: #selector(action1.action), forControlEvents: .TouchUpInside)
    
    0 讨论(0)
  • 2020-12-09 15:37

    If you change the scope of block to a class scope rather than function and hold a reference to closure there.

    You could invoke that closure with a function. in the class. So that way you can invoke that closure as a selector.

    Something like this:

    class Test: NSObject {
        let backToOriginalBackground = {
    
        }
        func backgroundChange() {
            NSTimer.scheduledTimerWithTimeInterval(0.5, target: self, selector: #selector(test), userInfo: nil, repeats: false)
        }
    
        func test() {
            self.backToOriginalBackground()
        }
    }
    
    0 讨论(0)
  • 2020-12-09 15:38

    As @gnasher729 notes, this is not possible because selectors are just names of methods, not methods themselves. In the general case, I'd use dispatch_after here, but in this particular case, the better tool IMO is UIView.animateWithDuration, because it's exactly what that function is for, and it's very easy to tweak the transition:

    UIView.animateWithDuration(0, delay: 0.5, options: [], animations: {
        self.view.backgroundColor = UIColor.whiteColor()
        self.view.alpha = 1.0
    }, completion: nil)
    
    0 讨论(0)
提交回复
热议问题