Hosting native iOS views in your Flutter app with Platform Views
Platform views allow you to embed native views in a Flutter app, so you can apply transforms, clips, and opacity to the native view from Dart.
This allows you, for example, to use the native Google Maps from the Android and iOS SDKs directly inside your Flutter app.
iOS only uses Hybrid composition,
which means that the native
UIView
is appended to the view hierarchy.
To create a platform view on iOS, use the following instructions:
On the Dart side
On the Dart side, create a Widget
and add the following build implementation,
as shown in the following steps.
In your Dart file, for example
do the following in native_view_example.dart
:
-
Add the following imports:
import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart';
-
Implement a
build()
method:Widget build(BuildContext context) { // This is used in the platform side to register the view. const String viewType = '<platform-view-type>'; // Pass parameters to the platform side. final Map<String, dynamic> creationParams = <String, dynamic>{}; return UiKitView( viewType: viewType, layoutDirection: TextDirection.ltr, creationParams: creationParams, creationParamsCodec: const StandardMessageCodec(), ); }
For more information, see the API docs for:
UIKitView
.
On the platform side
On the platform side, you use the either Swift or Objective-C:
Implement the factory and the platform view.
The FLNativeViewFactory
creates the platform view,
and the platform view provides a reference to the UIView
.
For example, FLNativeView.swift
:
import Flutter
import UIKit
class FLNativeViewFactory: 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 FLNativeView(
frame: frame,
viewIdentifier: viewId,
arguments: args,
binaryMessenger: messenger)
}
}
class FLNativeView: NSObject, FlutterPlatformView {
private var _view: UIView
init(
frame: CGRect,
viewIdentifier viewId: Int64,
arguments args: Any?,
binaryMessenger messenger: FlutterBinaryMessenger?
) {
_view = UIView()
super.init()
// iOS views can be created here
createNativeView(view: _view)
}
func view() -> UIView {
return _view
}
func createNativeView(view _view: UIView){
_view.backgroundColor = UIColor.blue
let nativeLabel = UILabel()
nativeLabel.text = "Native text from iOS"
nativeLabel.textColor = UIColor.white
nativeLabel.textAlignment = .center
nativeLabel.frame = CGRect(x: 0, y: 0, width: 180, height: 48.0)
_view.addSubview(nativeLabel)
}
}
Finally, register the platform view. This can be done in an app or a plugin.
For app registration,
modify the App’s AppDelegate.swift
:
import Flutter
import UIKit
@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]?
) -> Bool {
GeneratedPluginRegistrant.register(with: self)
weak var registrar = self.registrar(forPlugin: "plugin-name")
let factory = FLNativeViewFactory(messenger: registrar!.messenger())
self.registrar(forPlugin: "<plugin-name>")!.register(
factory,
withId: "<platform-view-type>")
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
}
For plugin registration,
modify the plugin’s main file
(for example, FLPlugin.swift
):
import Flutter
import UIKit
class FLPlugin: NSObject, FlutterPlugin {
public static func register(with registrar: FlutterPluginRegistrar) {
let factory = FLNativeViewFactory(messenger: registrar.messenger)
registrar.register(factory, withId: "<platform-view-type>")
}
}
Add the headers for the factory and the platform view.
For example, FLNativeView.h
:
#import <Flutter/Flutter.h>
@interface FLNativeViewFactory : NSObject <FlutterPlatformViewFactory>
- (instancetype)initWithMessenger:(NSObject<FlutterBinaryMessenger>*)messenger;
@end
@interface FLNativeView : NSObject <FlutterPlatformView>
- (instancetype)initWithFrame:(CGRect)frame
viewIdentifier:(int64_t)viewId
arguments:(id _Nullable)args
binaryMessenger:(NSObject<FlutterBinaryMessenger>*)messenger;
- (UIView*)view;
@end
Implement the factory and the platform view.
The FLNativeViewFactory
creates the platform view,
and the platform view provides a reference to the
UIView
. For example, FLNativeView.m
:
#import "FLNativeView.h"
@implementation FLNativeViewFactory {
NSObject<FlutterBinaryMessenger>* _messenger;
}
- (instancetype)initWithMessenger:(NSObject<FlutterBinaryMessenger>*)messenger {
self = [super init];
if (self) {
_messenger = messenger;
}
return self;
}
- (NSObject<FlutterPlatformView>*)createWithFrame:(CGRect)frame
viewIdentifier:(int64_t)viewId
arguments:(id _Nullable)args {
return [[FLNativeView alloc] initWithFrame:frame
viewIdentifier:viewId
arguments:args
binaryMessenger:_messenger];
}
@end
@implementation FLNativeView {
UIView *_view;
}
- (instancetype)initWithFrame:(CGRect)frame
viewIdentifier:(int64_t)viewId
arguments:(id _Nullable)args
binaryMessenger:(NSObject<FlutterBinaryMessenger>*)messenger {
if (self = [super init]) {
_view = [[UIView alloc] init];
}
return self;
}
- (UIView*)view {
return _view;
}
@end
Finally, register the platform view. This can be done in an app or a plugin.
For app registration,
modify the App’s AppDelegate.m
:
#import "AppDelegate.h"
#import "FLNativeView.h"
#import "GeneratedPluginRegistrant.h"
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
[GeneratedPluginRegistrant registerWithRegistry:self];
NSObject<FlutterPluginRegistrar>* registrar =
[self registrarForPlugin:@"plugin-name"];
FLNativeViewFactory* factory =
[[FLNativeViewFactory alloc] initWithMessenger:registrar.messenger];
[[self registrarForPlugin:@"<plugin-name>"] registerViewFactory:factory
withId:@"<platform-view-type>"];
return [super application:application didFinishLaunchingWithOptions:launchOptions];
}
@end
For plugin registration,
modify the main plugin file
(for example, FLPlugin.m
):
#import <Flutter/Flutter.h>
#import "FLNativeView.h"
@interface FLPlugin : NSObject<FlutterPlugin>
@end
@implementation FLPlugin
+ (void)registerWithRegistrar:(NSObject<FlutterPluginRegistrar>*)registrar {
FLNativeViewFactory* factory =
[[FLNativeViewFactory alloc] initWithMessenger:registrar.messenger];
[registrar registerViewFactory:factory withId:@"<platform-view-type>"];
}
@end
For more information, see the API docs for:
Putting it together
When implementing the build()
method in Dart,
you can use defaultTargetPlatform
to detect the platform, and decide what widget to use:
Widget build(BuildContext context) {
// This is used in the platform side to register the view.
const String viewType = '<platform-view-type>';
// Pass parameters to the platform side.
final Map<String, dynamic> creationParams = <String, dynamic>{};
switch (defaultTargetPlatform) {
case TargetPlatform.android:
// return widget on Android.
case TargetPlatform.iOS:
// return widget on iOS.
default:
throw UnsupportedError('Unsupported platform view');
}
}
Performance
Platform views in Flutter come with performance trade-offs.
For example, in a typical Flutter app, the Flutter UI is composed on a dedicated raster thread. This allows Flutter apps to be fast, as the main platform thread is rarely blocked.
While a platform view is rendered with hybrid composition, the Flutter UI is composed from the platform thread, which competes with other tasks like handling OS or plugin messages.
Prior to Android 10, hybrid composition copied each Flutter frame out of the graphic memory into main memory, and then copied it back to a GPU texture. As this copy happens per frame, the performance of the entire Flutter UI might be impacted. In Android 10 or above, the graphics memory is copied only once.
Virtual display, on the other hand, makes each pixel of the native view flow through additional intermediate graphic buffers, which cost graphic memory and drawing performance.
For complex cases, there are some techniques that can be used to mitigate these issues.
For example, you could use a placeholder texture while an animation is happening in Dart. In other words, if an animation is slow while a platform view is rendered, then consider taking a screenshot of the native view and rendering it as a texture.
For more information, see: