Flutter集成iOS原生页面时保留BottomNavigationBar的实现问题
问题解决:Flutter底部导航栏下嵌入iOS原生页面
当前问题核心是iOS端直接替换window.rootViewController导致Flutter底部导航栏被覆盖,需要让原生页面作为Flutter页面的一部分嵌入到Scaffold的body区域,保留底部导航栏的控制权。
方案一:使用PlatformView嵌入原生视图(官方推荐)
这种方式符合Flutter混合开发规范,原生视图会被直接渲染在Flutter的Widget树中,自然适配底部导航栏。
Flutter端修改
将MoreClass改为使用UiKitView嵌入iOS原生视图:
class MoreClass extends StatelessWidget { const MoreClass({super.key}); @override Widget build(BuildContext context) { return UiKitView( viewType: 'com.example.native_more_view', // 需与iOS端注册的ID一致 creationParams: {}, // 可传递参数到原生(可选) creationParamsCodec: const StandardMessageCodec(), ); } }
iOS端修改
- 创建原生视图实现类,继承
NSObject并实现FlutterPlatformView:
import Flutter import UIKit class NativeMoreView: NSObject, FlutterPlatformView { private let frame: CGRect private let viewController: MyViewController init(frame: CGRect, viewIdentifier: Int64, arguments: Any?) { self.frame = frame // 初始化你的原生ViewController let storyboard = UIStoryboard(name: "Main", bundle: nil) self.viewController = storyboard.instantiateViewController(withIdentifier: "ViewC") as! MyViewController super.init() setupViewFrame() } func view() -> UIView { return viewController.view } private func setupViewFrame() { viewController.view.frame = frame } }
- 创建PlatformView工厂类:
class NativeMoreViewFactory: NSObject, FlutterPlatformViewFactory { private let messenger: FlutterBinaryMessenger init(messenger: FlutterBinaryMessenger) { self.messenger = messenger super.init() } func create( withFrame frame: CGRect, viewIdentifier viewId: Int64, arguments args: Any? ) -> FlutterPlatformView { return NativeMoreView(frame: frame, viewIdentifier: viewId, arguments: args) } func createArgsCodec() -> FlutterMessageCodec & NSObjectProtocol { return StandardMessageCodec() } }
- 在
AppDelegate中注册PlatformView:
override func application( _ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? ) -> Bool { let controller = window?.rootViewController as! FlutterViewController // 注册原生视图 let viewFactory = NativeMoreViewFactory(messenger: controller.binaryMessenger) controller.register(viewFactory, withId: "com.example.native_more_view") GeneratedPluginRegistrant.register(with: self) return super.application(application, didFinishLaunchingWithOptions: launchOptions) }
方案二:通过MethodChannel将原生视图添加到Flutter容器(兼容原有逻辑)
如果需要保留原有的MethodChannel调用逻辑,可以将原生视图添加到Flutter页面的指定容器中:
Flutter端修改
修改MoreClass,获取容器视图的ID并传递给原生:
class MoreClass extends StatefulWidget { const MoreClass({super.key}); @override State<MoreClass> createState() => _MoreClassState(); } class _MoreClassState extends State<MoreClass> { static const platform = MethodChannel('com.example.flutter_ios_channel'); final GlobalKey _containerKey = GlobalKey(); @override void initState() { super.initState(); // 等待视图渲染完成后调用原生方法 WidgetsBinding.instance.addPostFrameCallback((_) => openNativeScreen()); } void openNativeScreen() async { try { final RenderBox renderBox = _containerKey.currentContext!.findRenderObject() as RenderBox; await platform.invokeMethod('openNativeScreen', { 'containerViewId': renderBox.view.debugID, }); } catch (e) { print('Error opening native screen: $e'); } } @override Widget build(BuildContext context) { return Container( key: _containerKey, width: double.infinity, height: double.infinity, ); } }
iOS端修改
修改MethodChannel的处理逻辑,将原生视图添加到Flutter容器中:
override func application( _ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? ) -> Bool { let controller = window?.rootViewController as! FlutterViewController let channel = FlutterMethodChannel(name: "com.example.flutter_ios_channel", binaryMessenger: controller.binaryMessenger) channel.setMethodCallHandler({ (call, result) in guard call.method == "openNativeScreen" else { result(FlutterMethodNotImplemented) return } guard let args = call.arguments as? [String: Any], let viewId = args["containerViewId"] as? Int64, let flutterContainer = controller.view.viewWithTag(Int(viewId)) else { result(FlutterError(code: "INVALID_PARAM", message: "参数错误或容器视图未找到", details: nil)) return } // 初始化原生ViewController let storyboard = UIStoryboard(name: "Main", bundle: nil) let nativeVC = storyboard.instantiateViewController(withIdentifier: "ViewC") as! MyViewController // 将原生VC添加为FlutterVC的子控制器 controller.addChild(nativeVC) nativeVC.view.frame = flutterContainer.bounds flutterContainer.addSubview(nativeVC.view) nativeVC.didMove(toParent: controller) result(nil) }) GeneratedPluginRegistrant.register(with: self) return super.application(application, didFinishLaunchingWithOptions: launchOptions) }
关键注意事项
- 绝对不要替换
window.rootViewController,否则会完全覆盖Flutter的整个界面(包括底部导航栏)。 - 两种方案的核心都是让原生视图成为Flutter Scaffold body的一部分,这样底部导航栏始终由Flutter控制,切换Tab时可正常切换内容。
内容的提问来源于stack exchange,提问作者Tauheed Ahmad
相关产品推荐
相关产品推荐

