Saltar al contenido principal

Añadir una pantalla de Flutter a una aplicación macOS

Aprende cómo añadir una única pantalla de Flutter a tu aplicación macOS existente.

Esta guía describe cómo añadir una única pantalla de Flutter a una aplicación macOS existente.

Iniciar un FlutterEngine y un FlutterViewController

#

Para lanzar una pantalla de Flutter desde una aplicación macOS existente, inicias un FlutterEngine y un FlutterViewController.

El FlutterEngine puede tener la misma vida útil que tu FlutterViewController o sobrevivir a tu FlutterViewController.

Consulta Secuencia de carga y rendimiento para obtener más análisis sobre las compensaciones de latencia y memoria al preparar previamente un motor.

Crear un FlutterEngine

#

El lugar donde crees un FlutterEngine depende de tu aplicación anfitriona.

En este ejemplo, creamos un objeto FlutterEngine dentro de un objeto SwiftUI Observable llamado FlutterDependencies. Prepara previamente el motor llamando a run() y luego inyecta este objeto en una ContentView usando el modificador de vista environment().

MyApp.swift
swift
import SwiftUI
import FlutterMacOS
// The following library connects plugins with macOS platform code to this app.
import FlutterPluginRegistrant

@Observable
class FlutterDependencies {
 let flutterEngine = FlutterEngine(name: "my flutter engine", project: nil)
 init() {
   // Runs the default Dart entrypoint with a default Flutter route.
   flutterEngine.run(withEntrypoint: nil)
   // Connects plugins with macOS platform code to this app.
   RegisterGeneratedPlugins(registry: self.flutterEngine)
 }
}

@main
struct MyApp: App {
   // flutterDependencies will be injected through the view environment.
   @State var flutterDependencies = FlutterDependencies()
   var body: some Scene {
     WindowGroup {
       ContentView()
         .environment(flutterDependencies)
     }
   }
}

Como ejemplo, demostramos la creación de un FlutterEngine, expuesto como una propiedad, al inicio de la aplicación en el app delegate.

AppDelegate.swift
swift
import Cocoa
import FlutterMacOS
// The following library connects plugins with macOS platform code to this app.
import FlutterPluginRegistrant

@main
class AppDelegate: FlutterAppDelegate {
  lazy var flutterEngine = FlutterEngine(name: "my flutter engine", project: nil)

  override func applicationDidFinishLaunching(_ aNotification: Notification) {
    flutterEngine.run(withEntrypoint: nil)
    RegisterGeneratedPlugins(registry: self.flutterEngine)
  }
}

Mostrar un FlutterViewController con tu FlutterEngine

#

El siguiente ejemplo muestra una ContentView genérica con un NavigationLink conectado a una pantalla de Flutter. Primero, crea un FlutterViewControllerRepresentable para representar el FlutterViewController. El constructor de FlutterViewController toma el FlutterEngine previamente preparado como argumento, el cual se inyecta a través del entorno de la vista.

ContentView.swift
swift
import SwiftUI
import FlutterMacOS

struct FlutterViewControllerRepresentable: NSViewControllerRepresentable {
  // Flutter dependencies are passed in through the view environment.
  @Environment(FlutterDependencies.self) var flutterDependencies

  func makeNSViewController(context: Context) -> FlutterViewController {
    return FlutterViewController(
      engine: flutterDependencies.flutterEngine,
      nibName: nil,
      bundle: nil
    )
  }

  func updateNSViewController(_ nsViewController: FlutterViewController, context: Context) {}
}

struct ContentView: View {
  var body: some View {
    NavigationStack {
      NavigationLink("My Flutter Feature") {
        FlutterViewControllerRepresentable()
      }
    }
  }
}

Ahora tienes una pantalla de Flutter embebida en tu aplicación macOS.

El siguiente ejemplo muestra un ViewController genérico con un NSButton conectado para presentar un FlutterViewController. El FlutterViewController utiliza la instancia de FlutterEngine creada en el AppDelegate.

ViewController.swift
swift
import Cocoa
import FlutterMacOS

class ViewController: NSViewController {

  override func viewDidLoad() {
    super.viewDidLoad()

    // Make a button to call the showFlutter function when pressed.
    let button =  NSButton(title: "Show Flutter!", target: self, action: #selector(showFlutter))
    button.frame = CGRect(x: 202, y: 187, width: 160.0, height: 40.0)
    self.view.addSubview(button)
  }

  @objc func showFlutter() {
    let flutterEngine = (NSApplication.shared.delegate as! AppDelegate).flutterEngine
    let flutterViewController =
        FlutterViewController(engine: flutterEngine, nibName: nil, bundle: nil)
    self.addChild(flutterViewController)
    flutterViewController.view.frame = self.view.bounds
    presentAsModalWindow(flutterViewController)
  }
}

Ahora tienes una pantalla de Flutter embebida en tu aplicación macOS.

Alternativa - Crear un FlutterViewController con un FlutterEngine implícito

#

Como alternativa al ejemplo anterior, puedes dejar que el FlutterViewController cree implícitamente su propio FlutterEngine sin preparar uno con anticipación.

Por lo general, esto no se recomienda porque crear un FlutterEngine bajo demanda podría introducir una latencia notable entre el momento en que se presenta el FlutterViewController y cuando renderiza su primer frame. Sin embargo, esto podría ser útil si la pantalla de Flutter se muestra raras veces, cuando no hay buenas heurísticas para determinar cuándo debe iniciarse la Dart VM, y cuando Flutter no necesita persistir el estado entre view controllers.

Para permitir que el FlutterViewController se presente sin un FlutterEngine existente, omite la construcción de FlutterEngine, y crea el FlutterViewController sin una referencia a un motor.

ContentView.swift
swift
// Existing code omitted.
func makeNSViewController(context: Context) -> FlutterViewController {
  return FlutterViewController()
}
ViewController.swift
swift
// Existing code omitted.
func showFlutter() {
  let flutterViewController = FlutterViewController()
  self.addChild(flutterViewController)
  flutterViewController.view.frame = self.view.bounds
  presentAsModalWindow(flutterViewController)
}

Consulta Secuencia de carga y rendimiento para obtener más exploraciones sobre la latencia y el uso de memoria.

Usar el FlutterAppDelegate

#

Se recomienda, aunque no es obligatorio, que la subclase UIApplicationDelegate de tu aplicación herede de FlutterAppDelegate.

El FlutterAppDelegate realiza funciones tales como:

Crear una subclase de FlutterAppDelegate

#

La creación de una subclase de FlutterAppDelegate en aplicaciones UIKit se mostró en la sección Iniciar un FlutterEngine y un FlutterViewController. En una aplicación SwiftUI, puedes crear una subclase de FlutterAppDelegate y anotarla con la macro Observable() de la siguiente manera:

MyApp.swift
swift
import SwiftUI
import FlutterMacOS

@Observable
class AppDelegate: FlutterAppDelegate {
  let flutterEngine = FlutterEngine(name: "my flutter engine", project: nil)

  override func applicationDidFinishLaunching(_ aNotification: Notification) {
    // Runs the default Dart entrypoint with a default Flutter route.
    flutterEngine.run(withEntrypoint: nil)
    // Used to connect plugins (only if you have plugins
    // with macOS platform code).
    RegisterGeneratedPlugins(registry: self.flutterEngine)
  }
}

@main
struct MyApp: App {
  // Use this property wrapper to tell SwiftUI
  // it should use the AppDelegate class for the application delegate
  @NSApplicationDelegateAdaptor(AppDelegate.self) var appDelegate

  var body: some Scene {
    WindowGroup {
      ContentView()
    }
  }
}

Luego, en tu vista, el AppDelegate es accesible a través del entorno de la vista.

ContentView.swift
swift
import SwiftUI
import FlutterMacOS

struct FlutterViewControllerRepresentable: NSViewControllerRepresentable {
  // Access the AppDelegate through the view environment.
  @Environment(AppDelegate.self) var appDelegate

  func makeNSViewController(context: Context) -> FlutterViewController {
    return FlutterViewController(
      engine: appDelegate.flutterEngine,
      nibName: nil,
      bundle: nil
    )
  }

  func updateNSViewController(_ nsViewController: FlutterViewController, context: Context) {}
}

struct ContentView: View {
  var body: some View {
    NavigationStack {
      NavigationLink("My Flutter Feature") {
        FlutterViewControllerRepresentable()
      }
    }
  }
}