在 Flutter 應用中使用整合平台檢視託管你的原生 iOS 檢視
整合平台檢視 (後稱為平台檢視) 允許將原生檢視嵌入到 Flutter 應用中,所以你可以透過 Dart 將變換、裁剪和不透明度等效果應用到原生檢視。
例如,這使你可以透過使用平台檢視直接在 Flutter 應用內部使用 Android 和 iOS SDK 中的 Google Maps。
iOS 只支援混合整合模式,這意味著原生的 UIView
會被加入檢視層級中。
要在 iOS 中建立平台檢視,需要如下步驟:
在 Dart 端
在 Dart 端,建立一個 Widget
並新增如下的實現,具體如下:
在 Dart 檔案中,例如 native_view_example.dart
,請執行下列操作:
-
新增如下匯入程式碼:
import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart';
-
實現
build()
方法: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(), ); }
更多訊息,請檢視 API 文件:UIKitView
。
在平台端
在平台端,你可以使用 Swift 或是 Objective-C:
實現工廠和平台檢視。
FLNativeViewFactory
建立一個關聯了 UIView
的平台檢視。舉個例子,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)
}
/// Implementing this method is only necessary when the `arguments` in `createWithFrame` is not `nil`.
public func createArgsCodec() -> FlutterMessageCodec & NSObjectProtocol {
return FlutterStandardMessageCodec.sharedInstance()
}
}
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)
}
}
最後,註冊這個平台檢視。這一步可以在應用中,也可以在外掛中。
要在應用中進行註冊,修改應用中的
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)
}
}
要在外掛中進行註冊,修改外掛的主類
(例如 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>")
}
}
使用 Objective-C 時,你需要在工廠類別和平台檢視的檔案頭部新增以下內容。用 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
實現工廠類別和平台檢視。
FLNativeViewFactory
建立一個關聯了 UIView
的平台檢視。用 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];
}
/// Implementing this method is only necessary when the `arguments` in `createWithFrame` is not `nil`.
- (NSObject<FlutterMessageCodec>*)createArgsCodec {
return [FlutterStandardMessageCodec sharedInstance];
}
@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
最後,註冊這個平台檢視。這一步可以在應用中,也可以在外掛中。
要在應用中進行註冊,修改應用中的 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
要在外掛中進行註冊,修改外掛主檔案
(例如 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
更多訊息,請檢視 API 文件:
整合起來
在 Dart 中實現 build()
方法時,你可以使用 defaultTargetPlatform
來檢測當前的平台,並且決定如何使用這個 widget:
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.
When a platform view is rendered with hybrid composition, the Flutter UI is composed from the platform thread. The platform thread competes with other tasks like handling OS or plugin messages.
When an iOS PlatformView is on screen, the screen refresh rate is capped at 80fps to avoid rendering janks.
For complex cases, there are some techniques that can be used to mitigate performance 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.
Composition limitations
There are some limitations when composing iOS Platform Views.
- The
ShaderMask
andColorFiltered
widgets are not supported. - The
BackdropFilter
widget is supported, but there are some limitations on how it can be used. For more details, check out the iOS Platform View Backdrop Filter Blur design doc.