質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

89.21%

UIBarButtonItemのactionメソッドが呼ばれないケースがある

解決済

回答 2

投稿 編集

  • 評価
  • クリップ 1
  • VIEW 761

Capibara

score 19

発生している問題・知りたいこと

UIBarButtonItemの初期化タイミングによって、actionメソッドが呼ばれないケースがある事がわかりました。
なぜそのケースだけactionメソッドが呼ばれないのか理由を知りたいです。

問題の現象

実行画面
上のイメージのようにナビゲーションバーに配置された2つのボタンがあり、タップすると各々”OK”、”NG”とデバッグ出力する処理を組み込みましたが、なぜかNGボタンの方だけデバッグ出力されません。

再現方法

Xcode Version 10.3 で試しています。

storyboard

![該当のStoryboard
storyboardで実装したのはデフォルトのViewControllerにNavigationControllerを追加しただけです。

ソースコード

import UIKit

class ViewController: UIViewController {

    var okButton: UIBarButtonItem!

    // NGボタンの初期化
    var ngButton = UIBarButtonItem(title: "NG", style: .plain, target: self, action: #selector(ngButtonTapped(_:)))

    override func viewDidLoad() {
        super.viewDidLoad()

        // OKボタンの初期化
        okButton = UIBarButtonItem(title: "OK", style: .plain, target: self, action: #selector(okButtonTapped(_:)))

        // navigationItemに2つのボタンをセット
        self.navigationItem.rightBarButtonItems = [ngButton, okButton]
    }

    // OKボタンをタップした時のアクション
    @objc func okButtonTapped(_ sender: UIBarButtonItem) {
        print("OK!")
    }

    // NGボタンをタップした時のアクション
    @objc func ngButtonTapped(_ sender: UIBarButtonItem) {
        print("NG!")
    }
}


ご覧のようにOKボタンとNGボタンは初期化の場所を変えています。
NGボタンもviewDidLoad()で初期化すれば想定通り動作するのですが、なぜ既存の位置の初期化ではダメなのか理由を知りたいです。

試したこと

storyboard上でUINavigationControllerを使用せず、Navigation BarとNavigation Item部品を手動で配置して試した所、上記のような初期化位置でも両方デバッグ出力される事を確認しました。

NavigationController配下で自動生成される UIViewController.navigationItemを利用しているのが原因だろうなぁと、薄々感じています。リファレンスにも何かそれらしい事が書いてあるように見えるのですが、google翻訳さんの力を借りても理解できませんでした。

追記情報です

比較の為にNavigationControllerを使わないケースの再現方法を追記します。

storyboard

イメージ説明
デフォルトのViewControllerにNavigation Bar部品を追加しました。
UINavigationItemはmyNavigationItemという名前でOutlet接続します。

ソースコード

import UIKit

class ViewController: UIViewController {

    @IBOutlet weak var myNavigationItem: UINavigationItem!

    var okButton: UIBarButtonItem!

    // NGボタンの初期化
    var ngButton = UIBarButtonItem(title: "NG", style: .plain, target: self, action: #selector(ngButtonTapped(_:)))

    override func viewDidLoad() {
        super.viewDidLoad()

        // OKボタンの初期化
        okButton = UIBarButtonItem(title: "OK", style: .plain, target: self, action: #selector(okButtonTapped(_:)))

        // navigationItemに2つのボタンをセット
        myNavigationItem.rightBarButtonItems = [ngButton, okButton]
    }

    // OKボタンをタップした時のアクション
    @objc func okButtonTapped(_ sender: UIBarButtonItem) {
        print("OK!")
    }

    // NGボタンをタップした時のアクション
    @objc func ngButtonTapped(_ sender: UIBarButtonItem) {
        print("NG!")
    }
}

解説

前のケースと違うのは、NavigationControllerによって自動生成されたNavigationItemではなく、storyboard上で手動で付加したものを使った事だけです。
ところが、こちらのケースでは"OK"ボタン"NG"ボタン共に、正しくデバッグ表示されます。

なぜ、最初のケースの"NG"ボタンのactionメソッドだけが機能しないのか知りたいです。

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 2

check解決した方法

0

自己解決できたので、以下にまとめます。

NGボタンの初期化について

インスタンスプロパティの初期化中はまだインスタンス自体が生成されていない為、selfが使えない。但し、エラーにはならずにnilとして扱われる。

確認のため、
viewDidLoad()内に次のコードを追加

print(ngButton)


出力結果

<UIBarButtonItem: 0x7f84e7008c40> target=0x0 action=ngButtonTapped: title='NG'


targetには0x0(=nil)が設定されている。

これは、NavigationControllerを使ったケースでも、使わないケースでも動きは同じ。

targetにnilを設定した時の動き

UIBarButtonItemのリファレンスを確認した所、targetプロパティについて次のように書かれている。

If nil, the action message is passed up the responder chain where it may be handled by any object implementing a method corresponding to the selector held by the action property. The default value is nil.

これはtargetにnilを設定した場合は、responder chainに従ってView階層を遡り、対応するアクションメソッドを実装するオブジェクトによって処理されるという事を言っているらしい。

Xcodeのビューデバッガーを使ってViewの階層を見ると、ビューコントローラーの下にナビゲーションバーがある事がわかる。
イメージ説明
NGボタンがタップされるとView階層を遡り、ビューコントローラにアクションメソッドngButtonTapped()を見つけると、それが実行される。

ナビゲーションバーはビューコントローラーの下では無く、ナビゲーションコントローラーの下にある。
イメージ説明
NGボタンがタップされてView階層を遡っても、ビューコントローラにはたどり着かずアクションメソッドngButtonTapped()が見つからない為、何もアクションがおこらない。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

0

調べてみました。「初期化 ストアドプロパティ UIButton」で検索。
一番上のqiitaの記事ですが、その中(追記2016/06/29)にそれっぽいことが書いてあります。
下記を参照。

https://qiita.com/taji-taji/items/fd46d9a04ef50386fcca

targetの中の、selfがまだ初期化されていないので、使えないということだと思います。
qiitaのページのようにlazyをつければ大丈夫なのかもしれません。
自分では試してませんが、、、

検索力大事です。必要な用語を知ってるかどうかですかね。

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2019/08/24 15:07

    御回答いただきありがとうございます。
    lazy var(遅延ストアドプロパティ)というものの存在を知ることができました。
    また、lazyを付けた事で、正しく動く事を確認致しました。
    これは初期化のタイミングをviewDidLoad内にずらしたという認識ですが、あっていますでしょうか?

    実は、なぜviewDidLoad内の初期化にしないといけないのかが、まだ理解できておりません。

    「selfがまだ初期化されていない」のselfはViewControllerの事だと思うのですが、そうだとすると質問に追記しました「NavigationControllerを使わないケース」となぜ動きが違うのかという疑問が残ります。

    キャンセル

  • 2019/08/24 15:25 編集

    自分の環境でやってみたら、エラーで動きませんでした。
    なので、検証できませんが、
    ここそこまで突き詰めないといけないとこですか?
    そういうもんかと納得して次に進んだ方がいいと思いますが、、、

    キャンセル

  • 2019/08/24 16:44

    検証していただきありがとうございます。

    とりあえず、前には進めるので、あまりこだわる必要はないかなという気持ちも少なからずありますが、
    理屈がわかればもう少し理解も深まる事もあるかと思い投稿させていただきました。
    理由がわかる方がおられましたらご指導いただきたく思います。

    参考までに、追加ケースの動作確認動画をこちらに置いておきます。
    https://youtu.be/hNFzUVmIwag

    キャンセル

  • 2019/08/24 17:04

    ちなみに追記は違う質問になりますので、一旦終了し、
    別で立てるべきですが、、、

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 89.21%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる