web-dev-qa-db-ja.com

#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引数にクロージャーを設定することは可能ですか?

22
Blaszard

@ 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)
7
Rob Napier

直接ではありませんが、いくつかの回避策が可能です。次の例を見てください。

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

少なくとも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!")
})
9
schirrmacher

今では可能です。 Swift 4.でブロックベースのセレクターの要点を作成しました。

https://Gist.github.com/cprovatas/98ff940140c8744c4d1f3bcce7ba454

使用法:

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

6

UIControl、UIButton、UIRefreshControl、UIGestureRecognizer、およびUIBarButtonItemをサポートするActionClosurableを使用できます。 https://github.com/takasek/ActionClosurable

UIBarButtonItemの以下の表示例

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

いいえ、#selectorはObjective-Cメソッドを指します。

ただし、NSTimerに拡張機能を追加すると、ターゲットとセレクターではなく、クロージャーを使用してスケジュールタイマーを作成できます。

1
gnasher729

ブロックのスコープを関数ではなくクラススコープに変更し、そこでクロージャーへの参照を保持する場合。

そのクロージャーを関数で呼び出すことができます。クラスで。そのため、そのクロージャーをセレクターとして呼び出すことができます。

このようなもの:

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

したがって、selectorをSwiftのような方法で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)
0
Justin Wright

私の解決策は、次のようなクラスブロック変数を作成することでした。

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)
}
0
Aitor Pagán