Могу ли я сделать #selector ссылкой на закрытие в Swift?

Я хочу, чтобы аргумент 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?

Ответы

Ответ 1

Как отмечает @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)

Ответ 2

Не напрямую, но возможны обходные пути. Взгляните на следующий пример.

/// Target-Action helper.
final class Action: NSObject {

    private let _action: () -> ()

    init(action: () -> ()) {
        _action = action
        super.init()
    }

    func action() {
        _action()
    }

}

let action1 = Action { print("action1 triggered") }

let button = UIButton()
button.addTarget(action1, action: #selector(action1.action), forControlEvents: .TouchUpInside)

Ответ 3

Нет, #selector ссылается на метод Objective-C.

Вы можете сделать что-то гораздо лучше: добавьте расширение в NSTimer, которое позволяет создавать запланированный таймер не с целью и селектором, а с закрытием.

Ответ 4

Теперь это возможно. Я создал сущность для блочных селекторов в Swift 4.

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

Использование:

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

Ответ 5

Если вы измените область действия блока на область класса, а не на функцию, и сохраните ссылку на ее закрытие.

Вы можете вызвать это закрытие с помощью функции. в классе. Таким образом, вы можете вызвать это закрытие как селектор.

Что-то вроде этого:

class Test: NSObject {
    let backToOriginalBackground = {

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

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

Ответ 6

Мое решение состояло в создании переменной блока класса, например:

let completionBlock: () -> () = nil

Создайте метод, который вызывает это завершениеBlock:

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

Ответ 7

Итак, мой ответ на то, чтобы 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)

Ответ 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!")
})