Help us understand the problem. What is going on with this article?

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

More than 1 year has passed since last update.

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

maeharin
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away