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

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

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

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

Q&A

解決済

1回答

3380閲覧

Swift3 ジェスチャーを登録したビューの子ビュー内にあるUISliderが働かないのですが

mitci

総合スコア37

Swift

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

0グッド

0クリップ

投稿2017/11/05 09:20

編集2017/11/07 11:07

###実現したいこと
親ビューにaddしたUITapGestureRecognizer親ビューでのみ有効にし、子ビューはジェスチャーを全く無効化し、UISliderなどがきちんと動くようにしたいです。

親ビューのUITapGestureRecognizerを子ビュー内では働かないようにする
-> UISliderの微小なツマミ移動とタップジェスチャーが競合し、タップジェスチャーが優先されてしまう
何の対策もしない
-> 子ビューのUIButton部分以外の部分(UISliderのツマミなども含む)をタップするとジェスチャーメソッドが呼ばれてしまう
となってしまいます。

とにかく、**親ビューにaddしたジェスチャーメソッドを子ビューに完全に反映しない**ようにしたいです。

###前提
Swift3で親ビューにUITapGestureRecognizerをaddして、
親ビューをタップすると子ビューが表示<-->非表示を入れ替える動作を作りました。
子ビューにはUIButtonUISliderが置いてあり、
UIButtonを押した場合はそちらが優先され、addしたUITapGestureRecognizer#selectorのメソッドは呼び出されないのですが、
UISliderの場合、ツマミをタップした瞬間#selectorのメソッドが呼ばれてしまい、子ビューが非表示になってしまいます。
もちろん、子ビューのUIButtonなどの置いていない部分をタップしても非表示になってしまうため、UIButtonも正確にタップする必要があります。

 
そこで、あらかじめ、なんの処理もしないメソッドを呼び出すUITapGestureRecognizerを子ビューにaddしておき、子ビューの何もないところに触れても、UISliderのツマミに触れても、何もしないメソッドが呼び出されて子ビューが消えなくなりました。

###発生している問題・エラーメッセージ
ですが、今度はUISliderのツマミを動かした際、
ツマミの動きが微小な場合、UISliderに紐づけたtouchUpInsideメソッドが呼び出されず、UITapGestureRecognizerの「何もしないメソッド」のみが呼び出されてしまいます。

ツマミを大きく動かした場合はタップジェスチャーと判断されずにちゃんとUISlidertouchUpInsideメソッドが呼び出されるのですが・・・

###試したこと
「何もしないメソッド」のaddではなく、shouldReceiveTouchメソッドを使って子ビューでのジェスチャーを排除してみても、同じ結果でした。
またネットで調べてみたところ、ジェスチャーについての記載の大方がObjective-Cで、残念ながらうまく理解できません。shouldReceiveTouchメソッドについても自分なりにSwiftに変換しているので正確なのかと言われれば自信はありません・・・。

###追記 shouldReceiveTouchメソッド
shouldReceiveTouchメソッドについては以下のように導入しました。
こちらを参照しました。

swift

1class ViewController: UIViewController, UIGestureRecognizerDelegate { 2 //デリゲートに批准して、 3 override func viewDidLoad() { 4 super.viewDidLoad() 5 //省略 6 tapGesture.cancelsTouchesInView = false 7 tapGesture.delegate = self 8 //デリゲートを指定し、 9 } 10 11 func gestureRecognizer(gestureRecognizer: UIGestureRecognizer, shouldReceiveTouch touch: UITouch) -> Bool { 12 if (touch.view?.isDescendant(of: subView))! { 13 return false 14 } 15 return true 16 } 17 //メソッドを導入

これ以外にはコードはいじっていません・・・
tapGesture.cancelsTouchesInView = falsetouch.view?.isDescendant(of: subView)の当たりなども理解ができていないまま、ただ書いてあった通りに導入したところがあるので、何が間違いか正直わかっていないところです・・・。

ただ、上記コードを書き加えても、子ビューに触れるとジェスチャーメソッドが呼ばれてしまい、状況によってはUISliderのツマミに触れるとアプリがクラッシュしてしまうこともありました。
###補足情報(言語/FW/ツール等のバージョンなど)
swift3 xcode8

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

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

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

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

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

u39ueda

2017/11/06 15:54

shouldReceiveTouchを実装すれば解決しそうに思いますが、どのように実装したのでしょうか?試してみたコードを提示していただければ問題がわかるかもしれません。あとはgestureのdelegateは設定していますか?
mitci

2017/11/07 11:45

コードを追記させていただきました。以前、shouldReceiveTouchを実装した時は、「何もしないメソッド」を呼び出すジェスチャーをaddしたときと同じような動きになった気がするのですが、もう一度実装してみたところ、ちゃんとshouldReceiveTouchが働いていないのか、そもそも子ビューをタップしても普通に親ビューのジェスチャーメソッドが呼び出されてしまいました・・・
guest

回答1

0

ベストアンサー

まず以下を前提にしています。

  • shouldReceiveTouch の中で使っているsubViewがどこにも定義されていないが、これは質問文でいう親ビューのこと
  • tapGestureはちゃんとaddGestureRecognizerしている
  • 今自分はSwift3環境がなくてSwift4でしか動作を見てません

以上を前提にして回答します。

コードを見たところ2つ問題があると思います。

問題1. shouldReceiveTouchの書き方がSwift2時代のもの

参照先の記事を見ると、

Swiftは2.1, Xcodeは7.1.1で書きました。

とあるので、このメソッドの書き方はたぶんSwift2時代のものです。
Swift2から3の際に書き方が大きく変わったので、そのまま持ってくると大抵うまく動きません。
自分で書き換える力がない場合はできるだけSwift3と書いてあるものを参考にした方がいいと思います。

Swift3以降はこちらの書き方じゃないとダメなはずです。

Swift

1// Swift3以降(少なくともSwift4はこれ) 2func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool { 3}

問題2. shouldReceiveTouchの返り値が常にfalseになる

if文のtouch.view?.isDescendant(of: subView)は、touch.viewがsubViewの子や孫ビュー、またはsubView自身だった場合にtrueを返します。
この場合は常にtrueを返すようなtouch.viewが渡ってくるはずなので、必ずif文の中を通ることになってreturn falseすることになります。

返り値がfalseになると、tapGestureはジェスチャを処理せず、#selectorのメソッドが呼ばれません。

回答

目的の親ビューにaddしたジェスチャーメソッドを子ビューに完全に反映しないという動作は、言い換えると親ビュー(subView)以外の子ビューがタップされた時はジェスチャーを処理しないで、親ビューがタップされた時のみ処理するという動作になると思います。

ということで、shouldReceiveTouchメソッドをこうすればいいんじゃないでしょうか。

Swift

1func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool { 2 if touch.view != subView { 3 // 親ビュー(subView)以外の子ビューがタップされた 4 return false 5 } 6 // 親ビューがタップされた 7 return true 8}

投稿2017/11/07 17:28

u39ueda

総合スコア950

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

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

mitci

2017/11/08 10:34

丁寧なご回答ありがとうございました。 shouldReceiveメソッドに対しての理解は深まったのですが、 こちらはviewController内のどこかに記載すればいいのでしょうか? viewDidLoad内で tapGesture.cancelsTouchesInView = false tapGesture.delegate = self と書き込み、指定していただいたコードも書き加えたのですが、 今度は親ビューでも#selectorメソッドが呼ばれなくなってしまいました・・・ https://qiita.com/ruwatana/items/16997b1b416512c20fb6 を参照すると、デリゲートのメソッドはclass ViewController内ではなく、 extension ViewController内に記載するとのことですが、そうするとclass ViewController内でデリゲートを指定するとエラーになりますし・・・
u39ueda

2017/11/08 15:20

shouldReceiveメソッドはclass 〜{} 内でもextension 〜{} 内でもどちらに書いても構いません。 ただ一般的にはUIGestureRecognizerDelegateの適合と一緒に書くのが多いと思います。 つまり、 class ViewController: UIViewController, UIGestureRecognizerDelegate {} と書いたならその中に、 extension ViewController: UIGestureRecognizerDelegate {} と書いたならその中にshouldReceiveメソッドを書きます。 #selectorメソッドが呼び出されないのはそれだけではちょっとわからないです。 `なんの処理もしないメソッドを呼び出すUITapGestureRecognizer` が残ってたりしないですよね? まずshouldReceiveメソッドが呼ばれているか確認してみてはどうでしょうか? ……ちょっと質問を読み返してみたんですが、もしかしてsubView=親ビューではなくてsubView=子ビューではないですか? そうすると私の回答の問題2節と回答節が全く見当はずれになるんですけども。
mitci

2017/11/12 12:16 編集

遅くなってすいません。 class ViewController: UIViewController, UIGestureRecognizerDelegate {}内に、 func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool { if touch.view != subView { print("親ビューじゃないです") return false } print("親ビューです") return true } とprint()関数を付け加えてみたところ、親ビューをタップしても、子ビューをタップしても"親ビューじゃないです"が表示されてしまいます。 そのため、やはり親ビューにaddしたタップジェスチャーで指定した#selectorメソッドが呼び出されず、つまりは、どこをタップしても何も起きないという状況になってしまいます。 subViewについては、確かに子ビューを意味していたのですが、「親ビュー(subView)」との記述でしたので、取り急ぎ、subViewの部分を親ビューの名前に変えて試しています。 詳しい状況としては、 子ビューの表示、非表示をタップジェスチャーで操作したいので、 親ビューにaddした#selectorメソッドの中で、子ビューの位置を親ビューの外側へ追いやって、さらに表示するためのタップジェスチャーも宣言しています。 また、子ビューはUIVisualEffectViewで、その中には複数のUIButtonやUISliderが配置してあります。 ジェスチャーはviewDidLoad内でletで子ビュー非表示用のジェスチャーを宣言して親ビューにaddしています。 そして#selectorメソッド内で今度は表示用のジェスチャーをやはりletで宣言して親ビューにadd、 表示用のジェスチャーで呼び出す#selectorメソッド内でもう一度letで子ビュー非表示用のジェスチャーを宣言して親ビューにaddし直しています。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.50%

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

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

質問する

関連した質問