LoginSignup
3
1

More than 3 years have passed since last update.

FlutterViewControllerからiOSのUIViewControllerをpresentした際、下のFlutterがイベントを拾ってしまう件

Posted at

FlutterでiOSのネイティブの画面をモーダル表示したい時、FlutterViewControllerからiOSのUIViewControllerをpresent()していたのだが、この際ハンドラが定義されてない部分タップすると後ろに隠れてるFlutter側のイベントが発火する挙動に困ってた。↓こういうの

Jul-27-2019 12-01-04.gif

問題の再現

原因調査

FlutterViewControllerの実装(engineのv1.5.4-hotfixesブランチ)を見ると、以下のようにtouchesBeganメソッド等の4メソッドをoverrideしており、ここからFlutter側にイベントを伝播させていることがわかる
https://github.com/flutter/engine/blob/v1.5.4-hotfixes/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm#L649-L663

touchesBeganがどう呼び出されるかについては以下のappleのドキュメントを参照。iOS側のtouchイベントはResponder Chainを駆け上っていくようになっている
https://developer.apple.com/documentation/uikit/touches_presses_and_gestures/using_responders_and_the_responder_chain_to_handle_events

今回の挙動に関係ある部分をまとめると

  • タッチイベントなら、タッチされたビューがファーストレスポンダとなる
  • 各ビューのnextプロパティにresponder chain上の次のレスポンダが入っているので、それで確認できる
  • UIViewオブジェクト
    • ビューがView Controllerのルートビューの場合、次のレスポンダはView Controller
    • それ以外の場合、次のレスポンダはビューのスーパービュー
  • UIViewController オブジェクト
    • View Controllerのビューがウィンドウのルートビューの場合、次のレスポンダはウィンドウオブジェクト
    • View Controllerが別のView Controllerによって表示されている場合、次のレスポンダは表示側のView Controller

つまり、今回のケースは以下のようになっていた

  • イベントハンドラがないUIの部分をタップする
  • タップイベントがiOSのResponder Chainを駆け上っていき、FlutterViewControllerのtouchesBegan()まで伝播
  • Flutter側のイベントハンドラが呼び出される

スクリーンショット 2019-07-27 11.31.09.png

解決策

FlutterViewControllerのtouchesBegan()等が呼び出されないようにすればよい。以下2つの方法が考えられる

1. FlutterViewControllerからpresent()したUIViewControllerのtouchesBegan等の4メソッドをoverrideする

FlutterViewControllerからpresentされるUIViewControllerに以下を追記する。これにより、FlutterViewControllerにアンハンドルなイベントを伝播しないようにする

override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {}
override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {}

参考:UIKitのヘッダファイルの該当箇所

// Generally, all responders which do custom touch handling should override all four of these methods.
// Your responder will receive either touchesEnded:withEvent: or touchesCancelled:withEvent: for each
// touch it is handling (those touches it received in touchesBegan:withEvent:).
// *** You must handle cancelled touches to ensure correct behavior in your application.  Failure to
// do so is very likely to lead to incorrect behavior or crashes.
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;
- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;
- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;
- (void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;

2. FlutterViewControllerからpresent()するのをやめる

iOS側のrootViewControllerをFlutterViewControllerではなくNavigationControllerにするなどして、FlutterViewControllerからUIViewControllerを直接present()しないようにする。
rootViewControllerを変更する方法はこちらの記事が参考になる
https://qiita.com/najeira/items/fa52ca4a9f544ae08300

3
1
2

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
3
1