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

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

新規登録して質問してみよう
ただいま回答率
85.48%
Xcode

Xcodeはソフトウェア開発のための、Appleの統合開発環境です。Mac OSXに付随するかたちで配布されています。

Swift

Swiftは、アップルのiOSおよびOS Xのためのプログラミング言語で、Objective-CやObjective-C++と共存することが意図されています

Q&A

解決済

2回答

1721閲覧

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

Capibara

総合スコア34

Xcode

Xcodeはソフトウェア開発のための、Appleの統合開発環境です。Mac OSXに付随するかたちで配布されています。

Swift

Swiftは、アップルのiOSおよびOS Xのためのプログラミング言語で、Objective-CやObjective-C++と共存することが意図されています

0グッド

1クリップ

投稿2019/08/23 13:45

編集2019/08/24 05:49

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

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

問題の現象

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

再現方法

Xcode Version 10.3 で試しています。

storyboard

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

ソースコード

swift

1import UIKit 2 3class ViewController: UIViewController { 4 5 var okButton: UIBarButtonItem! 6 7 // NGボタンの初期化 8 var ngButton = UIBarButtonItem(title: "NG", style: .plain, target: self, action: #selector(ngButtonTapped(_:))) 9 10 override func viewDidLoad() { 11 super.viewDidLoad() 12 13 // OKボタンの初期化 14 okButton = UIBarButtonItem(title: "OK", style: .plain, target: self, action: #selector(okButtonTapped(_:))) 15 16 // navigationItemに2つのボタンをセット 17 self.navigationItem.rightBarButtonItems = [ngButton, okButton] 18 } 19 20 // OKボタンをタップした時のアクション 21 @objc func okButtonTapped(_ sender: UIBarButtonItem) { 22 print("OK!") 23 } 24 25 // NGボタンをタップした時のアクション 26 @objc func ngButtonTapped(_ sender: UIBarButtonItem) { 27 print("NG!") 28 } 29} 30

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

試したこと

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

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

追記情報です

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

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メソッドだけが機能しないのか知りたいです。

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

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

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

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答2

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階層を遡り、対応するアクションメソッドを実装するオブジェクトによって処理されるという事を言っているらしい。

NavigationControllerを使わない(後者の)ケース

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

NavigationControllerを使ったケース(前者の)ケース

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

投稿2019/08/30 12:45

Capibara

総合スコア34

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

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

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

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

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

投稿2019/08/24 01:31

編集2019/08/24 01:36
hameji

総合スコア1380

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

Capibara

2019/08/24 06:07

御回答いただきありがとうございます。 lazy var(遅延ストアドプロパティ)というものの存在を知ることができました。 また、lazyを付けた事で、正しく動く事を確認致しました。 これは初期化のタイミングをviewDidLoad内にずらしたという認識ですが、あっていますでしょうか? 実は、なぜviewDidLoad内の初期化にしないといけないのかが、まだ理解できておりません。 「selfがまだ初期化されていない」のselfはViewControllerの事だと思うのですが、そうだとすると質問に追記しました「NavigationControllerを使わないケース」となぜ動きが違うのかという疑問が残ります。
hameji

2019/08/24 07:08 編集

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

2019/08/24 07:44

検証していただきありがとうございます。 とりあえず、前には進めるので、あまりこだわる必要はないかなという気持ちも少なからずありますが、 理屈がわかればもう少し理解も深まる事もあるかと思い投稿させていただきました。 理由がわかる方がおられましたらご指導いただきたく思います。 参考までに、追加ケースの動作確認動画をこちらに置いておきます。 https://youtu.be/hNFzUVmIwag
hameji

2019/08/24 08:04

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

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問