我可以让#selector引用Swift中的闭包吗?

时间:2016-05-02 13:28:06

标签: swift closures selector

我想让我的方法的selector参数引用一个闭包属性,它们都存在于同一范围内。例如,

func backgroundChange() {
    self.view.backgroundColor = UIColor.blackColor()
    self.view.alpha = 0.55

    let backToOriginalBackground = {
        self.view.backgroundColor = UIColor.whiteColor()
        self.view.alpha = 1.0
    }

    NSTimer.scheduledTimerWithTimeInterval(0.5, target: self, selector: #selector(backToOriginalBackground), userInfo: nil, repeats: false)
}

但是,这显示错误:Argument of #selector cannot refer to a property

当然,我可以定义一个新的,单独的方法并将闭包的实现移动到它,但我想为这么小的实现保持节俭。

是否可以将闭包设置为#selector参数?

11 个答案:

答案 0 :(得分:24)

不直接,但可能有一些变通方法。看一下下面的例子。

/// 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)

答案 1 :(得分:8)

我至少尝试过UIBarButtonItem:

private var actionKey: Void?

extension UIBarButtonItem {

    private var _action: () -> () {
        get {
            return objc_getAssociatedObject(self, &actionKey) as! () -> ()
        }
        set {
            objc_setAssociatedObject(self, &actionKey, newValue, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
        }
    }

    convenience init(title: String?, style: UIBarButtonItemStyle, action: @escaping () -> ()) {
        self.init(title: title, style: style, target: nil, action: #selector(pressed))
        self.target = self
        self._action = action
    }

    @objc private func pressed(sender: UIBarButtonItem) {
        _action()
    }

}

然后你可以这样做:

navigationItem.leftBarButtonItem = UIBarButtonItem(title: "Test", style: .plain, action: {
    print("Hello World!")
})

答案 2 :(得分:6)

正如@ gnasher729所指出的那样,这是不可能的,因为选择器只是方法的名称,而不是方法本身。在一般情况下,我在这里使用dispatch_after,但在这种特殊情况下,更好的工具IMO是UIView.animateWithDuration,因为它正是该函数的用途,并且很容易调整转换:

UIView.animateWithDuration(0, delay: 0.5, options: [], animations: {
    self.view.backgroundColor = UIColor.whiteColor()
    self.view.alpha = 1.0
}, completion: nil)

答案 3 :(得分:5)

现在可以了。我在Swift 4中为基于块的选择器创建了一个要点。

https://gist.github.com/cprovatas/98ff940140c8744c4d1f3bcce7ba4543

用法:

UIButton().addTarget(Selector, action: Selector { debugPrint("my code here") }, for: .touchUpInside)

答案 4 :(得分:1)

不,#selector指的是Objective-C方法。

你可以做得更好:向NSTimer添加一个扩展,它允许你创建一个不带目标和选择器但带有闭包的计划计时器。

答案 5 :(得分:1)

您可以使用ActionClosurable,它支持UIControl,UIButton,UIRefreshControl,UIGestureRecognizer和UIBarButtonItem。 https://github.com/takasek/ActionClosurable

UIBarButtonItem的以下显示示例

// UIBarButtonItem
let barButtonItem = UIBarButtonItem(title: "title", style: .plain) { _ in
    print("barButtonItem title")
}

答案 6 :(得分:1)

@werediver的回答很好。这是一个更新,允许您将其作为函数调用。

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) }
}

您需要管理对Selector.Perform的强引用。一种实现方法是将旨在与target-action一起使用的UIKit类子类化:

/// 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("??") }
tapRecognizer.perform() // "??"

答案 7 :(得分:0)

如果将块的范围更改为类范围而不是函数,并在那里保持对闭包的引用。

您可以使用函数调用该闭包。在课堂里。这样你可以调用该闭包作为选择器。

这样的事情:

class Test: NSObject {
    let backToOriginalBackground = {

    }
    func backgroundChange() {
        NSTimer.scheduledTimerWithTimeInterval(0.5, target: self, selector: #selector(test), userInfo: nil, repeats: false)
    }

    func test() {
        self.backToOriginalBackground()
    }
}

答案 8 :(得分:0)

我的解决方案是创建一个类块变量,如:

let completionBlock: () -> () = nil

创建一个调用此completionBlock的方法:

func completed(){
    self.completionBlock!()
}

在我想把我的选择器放在我所做的块之内:

func myFunc(){
  self.completionBlock = {//what I want to be done}
  NSTimer.scheduledTimerWithTimeInterval(0.5, target: self, selector: #selector(Myclass.completed), userInfo: nil, repeats: false)
}

答案 9 :(得分:0)

所以我以快捷的方式将selector分配给closure的答案与已经有的答案类似,但我想我会分享一个现实生活中的例子。是在UIViewController扩展名内完成的。

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)

答案 10 :(得分:0)

Swift 5.2.x

首先,您需要为代码块声明“易于使用” typealias

typealias Completion = () -> ()

然后,您必须声明私有var才能对函数使用“作为门”:

private var action: Completion?

此后,您应该创建一个可由Selector调用的函数(该函数仅接受字符串格式)并调用私有完成:

@objc func didAction() {
     self.action?()
}

最后,您可以像下面这样重新编写函数(使用新的swift语法):

Timer.scheduledTimer(timeInterval: 0.5, target: self, selector: #selector(didAction), userInfo: nil, repeats: false)
self.action = backToOriginalBackground

PS :请记住,您的变量(或参数,如果您将其嵌入到函数中)必须与typeAlias声明的类型相同,因此,在我们的情况下:

var backToOriginalBackground: () -> ()

或:

var backToOriginalBackground: Completion