24
23

More than 5 years have passed since last update.

Swiftで UIButton#addTarget するActionには @IBAction が必須??(→ではなかったけど気をつけないといけないことがあるようです)

Last updated at Posted at 2014-08-22

はじめに

昔からそうだったのかもしれませんが(addTarget使ったの初めてだから以前をしらない ^^;)、

Xcode6 Beta6 で UIButton などに addTarget するActionに @IBAction が無いとエラーになりました。

追加検証も御覧ください

現象

func someMethod() {
    btnRight.addTarget(self, action: "onBtnRight:", forControlEvents: .TouchUpInside)
}

func onBtnRight(sender: AnyObject) {
   ...
}

と書いてたら、

2014-08-22 16:16:15.265 hogehoge[33698:60b] *** NSForwarding: warning: object 0x7fd9dac1d2c0 of class '_TtC6hogehoge18TileViewController' does not implement methodSignatureForSelector: -- trouble ahead
Unrecognized selector -[hogehoge.TileViewController onBtnRight:]

となります。 よく コロン : を忘れて action: "onBtnRight" とやってしまったときのような症状です。

解決?

謎だなーと思って調べてもよくわからず。なんとなく @IBAction を試しに付けてみたら、ちゃんとActionが呼ばれました。

func someMethod() {
    btnRight.addTarget(self, action: "onBtnRight:", forControlEvents: .TouchUpInside)
}

@IBAction func onBtnRight(sender: AnyObject) {
   ...
}

さいごに

@IBAction は InterfaceBuilder 専用のMarkerだと思っていたんだけど。。。
何かの間違い(バグ)かもしれないし、昔からこうなのかもしれないですが、ちょっと意外でわからなかった…

(2014/8/23) 追加検証・1

@takabosoft さんからコメントを頂いたので追加検証をしてみました。
コメントで頂いたようによくある使い方だとそのような問題はおきませんでした。

結論としては、Target Objectがポイントとなるようで、 TargetがNSObject的な性質を持っていれば問題ない ようです。
どうも SelectorでActionを指定するようなObjectはそうみたいです

再現しないコード

import UIKit

class ViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()

        var btn = UIButton.buttonWithType(.System) as UIButton
        btn.frame = CGRectMake(0, 0, 100, 100)
        btn.setTitle("TEST", forState: .Normal)
        btn.addTarget(self, action: "didTouchUpInside:", forControlEvents: .TouchUpInside)
        view.addSubview(btn)
    }

    func didTouchUpInside(sender: AnyObject) {
        println("called!")
    }
}

再現するコード

このようなコードだと再現しました(落ちました)。 Target Object(Hoge object)が問題みたいです。

import UIKit

class ViewController: UIViewController {
    var hoge: Hoge = Hoge()

    override func viewDidLoad() {
        super.viewDidLoad()

        var btn = UIButton.buttonWithType(.System) as UIButton
        btn.frame = CGRectMake(0, 0, 100, 100)
        btn.setTitle("TEST", forState: .Normal)
        btn.addTarget(self, action: "didTouchUpInside:", forControlEvents: .TouchUpInside)
        view.addSubview(btn)

        hoge.someMethod(btn)
    }

    func didTouchUpInside(sender: AnyObject) {
        println("called!")
    }
}

class Hoge {
    func someMethod(btn: UIButton) {
        btn.addTarget(self, action: "didTouchUpInside:", forControlEvents: .TouchUpInside)
    }

    func didTouchUpInside(sender: AnyObject) {
        println("222222222222")
    }
}

再現しないようにするには・その1

@IBAction を付ける

class Hoge {
    func someMethod(btn: UIButton) {
        btn.addTarget(self, action: "didTouchUpInside:", forControlEvents: .TouchUpInside)
    }

    @IBAction func didTouchUpInside(sender: AnyObject) {
        println("222222222222")
    }
}

再現しないようにするには・その2

HogeがNSObjectを継承する。

class Hoge : NSObject {
    func someMethod(btn: UIButton) {
        btn.addTarget(self, action: "didTouchUpInside:", forControlEvents: .TouchUpInside)
    }

    func didTouchUpInside(sender: AnyObject) {
        println("222222222222")
    }
}

再現しないようにするには・その3

@objc class Hoge とする。

@objc class Hoge {
    func someMethod(btn: UIButton) {
        btn.addTarget(self, action: "didTouchUpInside:", forControlEvents: .TouchUpInside)
    }

    func didTouchUpInside(sender: AnyObject) {
        println("222222222222")
    }
}

(2014/8/23) 追加検証・2

NSNotificationCenter だとどうなるのだろう、と思って2行追加しました。
やはりこれだと落ちるみたいです。ただ、上記のように NSObject@objcを付けると落ちませんでした。@objcを付けるのはclassの前でなくfuncのところでも良いようです。

Selector周りはそういう動作なのかもしれません。

    @objc func didTouchUpInside(sender: AnyObject) {
        println("222222222222")
    }
import UIKit

class ViewController: UIViewController {
    var hoge: Hoge = Hoge()

    override func viewDidLoad() {
        super.viewDidLoad()

        var btn = UIButton.buttonWithType(.System) as UIButton
        btn.frame = CGRectMake(0, 0, 100, 100)
        btn.setTitle("TEST", forState: .Normal)
        btn.addTarget(self, action: "didTouchUpInside:", forControlEvents: .TouchUpInside)
        view.addSubview(btn)

        hoge.someMethod(btn)

        // この2行を追加
        NSNotificationCenter.defaultCenter().addObserver(hoge, selector: "didTouchUpInside:", name: "Hoge", object: nil)
        NSNotificationCenter.defaultCenter().postNotificationName("Hoge", object: self)
    }

    func didTouchUpInside(sender: AnyObject) {
        println("called!")
    }
}

class Hoge {
    func someMethod(btn: UIButton) {
        btn.addTarget(self, action: "didTouchUpInside:", forControlEvents: .TouchUpInside)

    }

    func didTouchUpInside(sender: AnyObject) {
        println("222222222222")
    }
}
24
23
3

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
24
23