Saltar al contenido principal

Alojar vistas nativas de iOS en tu aplicación Flutter con platform views

Aprende cómo alojar vistas nativas de iOS en tu aplicación Flutter con platform views.

Las Platform Views te permiten incrustar vistas nativas en una aplicación de Flutter, de modo que puedes aplicar transformaciones, recortes y opacidad a la vista nativa desde Dart.

Esto te permite, por ejemplo, usar Google Maps nativo desde los SDK de Android e iOS directamente dentro de tu aplicación Flutter.

iOS solo utiliza composición híbrida, lo que significa que la vista nativa UIView se añade a la jerarquía de vistas.

Para crear una platform view en iOS, sigue estas instrucciones:

En el lado de Dart

#

Del lado de Dart, crea un Widget y agrega la implementación de build, como se muestra en los siguientes pasos.

En el archivo de Widget de Dart, realiza cambios similares a los mostrados en native_view_example.dart:

  1. Añade las siguientes importaciones:

    dart
    import 'package:flutter/foundation.dart';
    import 'package:flutter/services.dart';
    
  2. Implementa un método build():

    dart
    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(),
      );
    }
    

Para obtener más información, consulta la documentación de la API para: UIKitView.

En el lado de la plataforma

#

En el lado de la plataforma, usa Swift u Objective-C:

Implementa la factory y la platform view. La FLNativeViewFactory crea la platform view, y la platform view proporciona una referencia a la UIView.

Por ejemplo, implementa la clase factory:

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()
    }
}

Dependiendo del framework de interfaz de usuario que estés utilizando, elige una de las siguientes implementaciones para FLNativeView:

Para UIKit, implementa la clase FLNativeView:

swift
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)
    }
}

Para mostrar vistas de SwiftUI dentro de una platform view en iOS, envuelve la vista de SwiftUI dentro de un UIHostingController. Dado que UIHostingController es un controlador de vistas, puedes recuperar su vista usando la propiedad view, y retornarla desde el método view() de la platform view.

Para evitar una liberación prematura de memoria, almacena la instancia de UIHostingController como una propiedad de FLNativeView. Usa el frame pasado al inicializador para establecer el frame de la vista del hosting controller para que Flutter le dé el tamaño correcto.

For SwiftUI, implement the FLNativeView class and its SwiftUI view:

swift
import SwiftUI

class FLNativeView: NSObject, FlutterPlatformView {
    private let hostingController: UIHostingController<MySwiftUIView>

    init(
        frame: CGRect,
        viewIdentifier viewId: Int64,
        arguments args: Any?,
        binaryMessenger messenger: FlutterBinaryMessenger?
    ) {
        hostingController = UIHostingController(rootView: MySwiftUIView())
        super.init()
        hostingController.view.frame = frame
    }

    func view() -> UIView {
        return hostingController.view
    }
}

struct MySwiftUIView: View {
    var body: some View {
        Text("Native text from iOS (SwiftUI)")
            .frame(maxWidth: .infinity, maxHeight: .infinity)
            .background(Color.blue)
            .foregroundColor(.white)
    }
}

Finally, register the platform view. This can be done in an app or a plugin.

For app registration, modify the App's AppDelegate.swift:

swift
import Flutter
import UIKit

@main
@objc class AppDelegate: FlutterAppDelegate, FlutterImplicitEngineDelegate {

    func didInitializeImplicitFlutterEngine(_ engineBridge: FlutterImplicitEngineBridge) {
        GeneratedPluginRegistrant.register(with: engineBridge.pluginRegistry)

        guard let pluginRegistrar = engineBridge.pluginRegistry.registrar(forPlugin: "plugin-name") else { return }

        let factory = FLNativeViewFactory(messenger: pluginRegistrar.messenger())
        pluginRegistrar.register(
            factory,
            withId: "<platform-view-type>")
    }
}

For plugin registration, modify the plugin's main file (for example, FLPlugin.swift):

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>")
    }
}

In Objective-C, add the headers for the factory and the platform view. For example, as shown in FLNativeView.h:

objc
#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:

objc
#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

Finally, register the platform view. This can be done in an app or a plugin.

For app registration, modify the App's AppDelegate.m:

objc
#import "AppDelegate.h"
#import "FLNativeView.h"
#import "GeneratedPluginRegistrant.h"

@implementation AppDelegate

- (void)didInitializeImplicitFlutterEngine:(NSObject<FlutterImplicitEngineBridge>*)engineBridge {
  [GeneratedPluginRegistrant registerWithRegistry:engineBridge.pluginRegistry];

  NSObject<FlutterPluginRegistrar>* registrar =
      [engineBridge.pluginRegistry registrarForPlugin:@"plugin-name"];

  FLNativeViewFactory* factory =
      [[FLNativeViewFactory alloc] initWithMessenger:registrar.messenger];

  [registrar registerViewFactory:factory withId:@"<platform-view-type>"];
}

@end

For plugin registration, modify the main plugin file (for example, FLPlugin.m):

objc
#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

Para obtener más información, consulta la documentación de la API de:

Juntando todo

#

When implementing the build() method in Dart, you can use defaultTargetPlatform to detect the platform, and decide which widget to use:

dart
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.
    case TargetPlatform.macOS:
    // return widget on macOS.
    default:
      throw UnsupportedError('Unsupported platform view');
  }
}

Rendimiento

#

Platform Views en Flutter conllevan compromisos de rendimiento.

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.

Limitaciones de composición

#

Existen algunas limitaciones al componer Platform Views de iOS.