I have create a Flutter application which has BottomNavigationBar. Suppose this bottom bar has 3 BottomNavigationBarItem. 2 of them flutter screen but 3rd screen I want to launch as iOS native screen. I can do by passing the the method channel on iOS native code to launch ViewController. But iOS native screen hiding flutter bottom tab bar. I want it should place under the bottom bar so that I can switch in between all other tabs.
Flutter Code:
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
useMaterial3: true,
),
home: BottomNavigationBarExample(),
);
}
}
class BottomNavigationBarExample extends StatefulWidget {
const BottomNavigationBarExample({super.key});
@override
State<BottomNavigationBarExample> createState() =>
_BottomNavigationBarExampleState();
}
class _BottomNavigationBarExampleState
extends State<BottomNavigationBarExample> {
int _selectedIndex = 0;
static const TextStyle optionStyle =
TextStyle(fontSize: 30, fontWeight: FontWeight.bold);
static const List<Widget> _widgetOptions = <Widget>[
Text(
'Index 0: Home',
style: optionStyle,
),
BusinessClass(),
MoreClass()
];
void _onItemTapped(int index) {
setState(() {
_selectedIndex = index;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('BottomNavigationBar Sample'),
),
body: Center(
child: _widgetOptions.elementAt(_selectedIndex),
),
bottomNavigationBar: BottomNavigationBar(
type: BottomNavigationBarType.fixed, // This is all you need!
items: const <BottomNavigationBarItem>[
BottomNavigationBarItem(
icon: Icon(Icons.home),
label: 'Home',
),
BottomNavigationBarItem(
icon: Icon(Icons.business),
label: 'Business',
),
BottomNavigationBarItem(
icon: Icon(Icons.more_horiz),
label: 'More',
),
],
currentIndex: _selectedIndex,
selectedItemColor: Colors.amber[800],
onTap: _onItemTapped,
),
);
}
}
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');
@override
void initState() {
// TODO: implement initState
openNativeScreen();
super.initState();
}
void openNativeScreen() async {
try {
await platform.invokeMethod('openNativeScreen');
} catch (e) {
print('Error opening native screen: $e');
}
}
@override
Widget build(BuildContext context) {
return Container(
height: 400,
color: Colors.green,
);
}
}
Now iOS Native Code in AppDelegate
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
let controller : FlutterViewController = window?.rootViewController as! FlutterViewController
let channel = FlutterMethodChannel(name: "com.example.flutter_ios_channel", binaryMessenger: controller.binaryMessenger)
channel.setMethodCallHandler({
(call: FlutterMethodCall, result: @escaping FlutterResult) -> Void in
// Handle method calls from Flutter here
if call.method == "openNativeScreen" {
// Replace with code to open your native iOS screen
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let viewController = storyboard.instantiateViewController(withIdentifier: "ViewC") as! MyViewController
self.window.rootViewController = viewController
self.window.makeKeyAndVisible()
result(nil) // Optional: Sending a result back to Flutter
} else {
result(FlutterMethodNotImplemented)
}
})
GeneratedPluginRegistrant.register(with: self)
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
You need to embed the native screen within the Flutter app rather than replacing the root view controller. This can be done by using a FlutterViewController and presenting it on top of the current Flutter view. You can try this:
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
let controller : FlutterViewController = window?.rootViewController as! FlutterViewController
let channel = FlutterMethodChannel(name: "com.example.flutter_ios_channel", binaryMessenger: controller.binaryMessenger)
channel.setMethodCallHandler { (call: FlutterMethodCall, result: @escaping FlutterResult) -> Void in
if call.method == "openNativeScreen" {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let viewController = storyboard.instantiateViewController(withIdentifier: "ViewC") as! MyViewController
// Present the view controller modally
controller.present(viewController, animated: true, completion: nil)
result(nil)
} else {
result(FlutterMethodNotImplemented)
}
}
GeneratedPluginRegistrant.register(with: self)
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
Update: Ok, than you can use PlatformView.
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
class NativeView extends StatelessWidget {
const NativeView({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
// This is used in the platform side to register the view.
const String viewType = 'native-view';
// Pass parameters to the platform side.
final Map<String, dynamic> creationParams = <String, dynamic>{};
return Platform.isIOS
? UiKitView(
viewType: viewType,
layoutDirection: TextDirection.ltr,
creationParams: creationParams,
creationParamsCodec: const StandardMessageCodec(),
)
: Text('This platform is not supported');
}
}
You need to register platform view right before your app initializes.
void main() {
if (Platform.isIOS) {
// Register the platform view for iOS
final Map<String, dynamic> creationParams = <String, dynamic>{};
// This uses the `uiKitView` for iOS.
UiKitView(viewType: 'native-view', creationParams: creationParams, creationParamsCodec: const StandardMessageCodec());
}
runApp(const MyApp());
}
Your NativeView class will look like this:
import Flutter
import UIKit
class NativeViewFactory: NSObject, FlutterPlatformViewFactory {
private var messenger: FlutterBinaryMessenger
init(messenger: FlutterBinaryMessenger) {
self.messenger = messenger
super.init()
}
func create(
withFrame frame: CGRect,
viewIdentifier viewId: Int64,
arguments args: Any?
) -> FlutterPlatformView {
return NativeView(
frame: frame,
viewIdentifier: viewId,
arguments: args,
binaryMessenger: messenger)
}
}
class NativeView: NSObject, FlutterPlatformView {
private var _view: UIView
init(
frame: CGRect,
viewIdentifier viewId: Int64,
arguments args: Any?,
binaryMessenger messenger: FlutterBinaryMessenger?
) {
_view = UIView()
super.init()
createNativeView(view: _view)
}
func view() -> UIView {
return _view
}
private func createNativeView(view _view: UIView) {
// Customize your native view here
_view.backgroundColor = UIColor.red
let label = UILabel()
label.text = "Native iOS View"
label.textAlignment = .center
label.frame = _view.bounds
label.autoresizingMask = [.flexibleWidth, .flexibleHeight]
_view.addSubview(label)
}
}
And here is your AppDelegate:
import UIKit
import Flutter
@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
let controller: FlutterViewController = window?.rootViewController as! FlutterViewController
let factory = NativeViewFactory(messenger: controller.binaryMessenger)
registrar(forPlugin: "NativeView")?.register(factory, withId: "native-view")
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
}