You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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端修改

  1. 创建原生视图实现类,继承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
    }
}
  1. 创建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()
    }
}
  1. 在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.21 01:41:08