Flutter UiKitView嵌套iOS原生MKMapView问题

2,642 阅读2分钟

场景描述:

  • Flutter中嵌入一个地图页,并叠加一个底部操作栏,呈现悬浮状态,地图页可拖动、缩放等一些列功能,底部操作栏的一些列操作不影响地图页的操作

简化复现:

  • 使用Flutter中使用Stack布局利用UiKitView嵌套原生MKMapView,再堆叠一个button (或者其他widget

问题描述

  • 第一次拖动、缩放原生部分map一切正常,但是再点击Flutter部分button(或者其他widget相同)后,原生map部分就失去所有响应

补充:
1、UiKitView是Flutter一种widget,相当于一个容器,可以嵌入iOS平台原生View,自带点击相关手势、参数传递、参数编码等功能。
2、基本功能需要在指定viewType,这是一个String类型的参数,是iOSView类型的唯一标识,两端必须保持一致,这个id需要注册到一个遵循FlutterPlatformViewFactory协议的类上。
3、除此之外,还需要一个遵守FlutterPlatformView协议的类,就是在这个类里,有你需要的原生View,不管是LabelButtonUIScrollView或者是MKMapView

import Flutter
import UIKit

@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
    override func application(
        _ application: UIApplication,
        didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
    ) -> Bool {
        GeneratedPluginRegistrant.register(with: self)
        
        // withId就是flutter那边的viewType参数,需要保持一致!!!
        // 将MapViewFactory类实例注册注册这个id,MapViewFactory类需要实现FlutterPlatformViewFactory协议
        registrar(forPlugin: "bug.demo.UiKitView")?.register(MapViewFactory(),
            withId: "bug.demo.MapView")

        return super.application(application, didFinishLaunchingWithOptions: launchOptions)
    }
} 

class MapViewFactory: NSObject, FlutterPlatformViewFactory {
    func create(withFrame frame: CGRect, viewIdentifier viewId: Int64, arguments args: Any?) -> FlutterPlatformView {
    	// 实例化一个MapPlatformView
        MapPlatformView(frame, viewId, args)
    }
}

class MapPlatformView: NSObject, FlutterPlatformView {
    var frame: CGRect
    var viewId: Int64
    var args: Any?

    var mapView: MKMapView //地图view

    init(_ frame: CGRect, _ viewId: Int64, _ args: Any?) {
        self.frame = frame
        self.viewId = viewId
        self.args = args

        mapView = MKMapView(frame: frame)
    }

    func view() -> UIView {
        mapView
    }
}

简单代码还原

Flutter端:
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatefulWidget {
  @override
  createState() => _MyApp();
}

class _MyApp extends State<MyApp> {
  @override
  void initState() {
    // TODO: implement initState
    super.initState();
  }

  @override
  Widget build(BuildContext context) => MaterialApp(
        title: 'UiKitView Bug',
        theme: ThemeData(
          primarySwatch: Colors.blue,
          visualDensity: VisualDensity.adaptivePlatformDensity,
        ),
        // 使用stack布局
        home: _byStack(context),
      );

// stack布局
  Widget _byStack(BuildContext context) => Scaffold(
        body: Stack(
          alignment: AlignmentDirectional.bottomCenter,
          children: [
            UiKitView(
            // viewType是一个String
              viewType: "bug.demo.MapView",
            ),
            _buildButton(context),
          ],
        ),
      );

// column布局
  Widget _byColumn(BuildContext context) => Scaffold(
        body: Column(
          children: [
            Expanded(
              child: UiKitView(
                viewType: "bug.demo.MapView",
              ),
            ),
            _buildButton(context),
          ],
        ),
      );

  Widget _buildButton(BuildContext context) => MaterialButton(
        color: Colors.blue,
        onPressed: () {},
        child: Text("点我进行下一步操作!"),
      );
}

经过测试,使用Column布局不会出现上述问题,下面分别是StackColumn布局的模拟器截图:

Stack布局Column布局

解决

  • AppDelegate中注册的时候,加上下面一个PolicyFlutterPlatformViewGestureRecognizersBlockingPolicyWaitUntilTouchesEnded,这个政策意味着,在原生View方经过一个完整的响应者链后(也就是touchesEnded方法调用后),Flutter才会阻止原生View上所有手势识别。
  • 与之对应的还有一个FlutterPlatformViewGestureRecognizersBlockingPolicyEager,它意味着只有touchesBegan才会被识别,除此之外都会被Flutter方阻止。
FlutterPlatformViewGestureRecognizersBlockingPolicyWaitUntilTouchesEnded
  registrar(forPlugin: "bug.demo.UiKitView")?.register(MapViewFactory(),
                                                             withId: "bug.demo.MapView", gestureRecognizersBlockingPolicy: FlutterPlatformViewGestureRecognizersBlockingPolicyWaitUntilTouchesEnded)