如何通过平台通道在iOS原生代码中销毁Flutter页面?
我正在学习将Flutter集成到iOS原生应用中,目前已实现从iOS View跳转到Flutter页面,但在使用官方平台通道的方法销毁Flutter页面时遇到问题。
官方示例基于Flutter app与原生平台通信的场景,在AppDelegate.swift中通过FlutterMethodChannel和setMethodCallHandler接收方法调用并执行原生逻辑。但我是按照Flutter Module集成iOS文档进行集成的,这种场景下默认用OC创建AppDelegate.h和AppDelegate.m,没有AppDelegate.swift来处理通道和方法调用。
我尝试手动创建AppDelegate.swift,但出现import Flutter提示“无此模块”的错误。后来在SwiftUI测试应用中创建该文件,导入Flutter不再报错,但解包window?.rootViewController时触发Thread 1: Swift runtime failure: force unwrapped a nil value异常。
我的目标是调用controller.dismiss(animated:true)关闭Flutter页面,Swift不太熟练,希望找出问题所在。
Flutter侧代码
通知Swift的工具类
import 'package:flutter/services.dart'; class Utilities { static void closeFlutterView() { const MethodChannel channel = MethodChannel('com.example/close_flutter_view'); channel.invokeMethod('close'); } }
Flutter页面代码
import 'dart:io'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_module/utilities.dart'; @pragma('vm:entry-point') void screenAEntrypoint() { runApp(const MaterialApp( home: ScreenA(), )); } class ScreenA extends StatefulWidget { const ScreenA({super.key}); @override State<ScreenA> createState() => _ScreenAState(); } class _ScreenAState extends State<ScreenA> { dynamic backButton = Platform.isIOS ? CupertinoIcons.back : Icons.arrow_back; @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text("Screen A"), leading: IconButton( icon: Icon(backButton), color: Colors.redAccent, onPressed: () { Utilities.closeFlutterView(); }), ), body: const Text("Screen A entry point"), ); } }
iOS侧代码
Swift主文件
import SwiftUI import Flutter import FlutterPluginRegistrant class FlutterDependencies: ObservableObject { let flutterEngine = FlutterEngine(name: "my flutter engine"); let flutterEngine2 = FlutterEngine(name: "my flutter engine 2"); init(){ flutterEngine.run(withEntrypoint: "screenAEntrypoint", libraryURI: "package:flutter_module/screen_a.dart"); flutterEngine2.run(withEntrypoint: "screenBEntrypoint", libraryURI: "package:flutter_module/screen_b.dart"); GeneratedPluginRegistrant.register(with: self.flutterEngine); GeneratedPluginRegistrant.register(with: self.flutterEngine2); } } @main struct flutter_integration_testApp: App { @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate @StateObject var flutterDependencies = FlutterDependencies() var body: some Scene { WindowGroup { ContentView().environmentObject(flutterDependencies) } } }
Swift主视图
import SwiftUI import CoreData import Flutter struct ContentView: View { @EnvironmentObject var flutterDependencies: FlutterDependencies var body: some View { Text("iOS View") Button("Show Flutter!") { showFlutter() } Button("Show Flutter2!") { showFlutter2() } } func showFlutter() { guard let windowScene = UIApplication.shared.connectedScenes .first(where: { $0.activationState == .foregroundActive && $0 is UIWindowScene }) as? UIWindowScene, let window = windowScene.windows.first(where: \.isKeyWindow), let rootViewController = window.rootViewController else { return } let flutterViewController = FlutterViewController( engine: flutterDependencies.flutterEngine, nibName: nil, bundle: nil) flutterViewController.modalPresentationStyle = .overCurrentContext flutterViewController.isViewOpaque = false rootViewController.present(flutterViewController, animated: true) } func showFlutter2() { guard let windowScene = UIApplication.shared.connectedScenes .first(where: { $0.activationState == .foregroundActive && $0 is UIWindowScene }) as? UIWindowScene, let window = windowScene.windows.first(where: \.isKeyWindow), let rootViewController = window.rootViewController else { return } let flutterViewController = FlutterViewController( engine: flutterDependencies.flutterEngine2, nibName: nil, bundle: nil) flutterViewController.modalPresentationStyle = .overCurrentContext flutterViewController.isViewOpaque = false rootViewController.present(flutterViewController, animated: true) } }
AppDelegate.swift
import Foundation import UIKit import Flutter class AppDelegate: NSObject, UIApplicationDelegate { func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool { print("Your code here") let windowScene = UIApplication.shared.connectedScenes .first(where: { $0.activationState == .foregroundActive && $0 is UIWindowScene }) as? UIWindowScene; let window = windowScene?.windows.first(where: \.isKeyWindow); let controller : FlutterViewController = window?.rootViewController as! FlutterViewController; let flutterChannel = FlutterMethodChannel(name: "com.example/close_flutter_view", binaryMessenger: controller.binaryMessenger) flutterChannel.setMethodCallHandler({ (call: FlutterMethodCall, result: @escaping FlutterResult) -> Void in guard call.method == "close" else { result(FlutterMethodNotImplemented) return } controller.dismiss(animated: true); }) return true } }
问题分析与解决步骤
1. 崩溃原因:AppDelegate中强制解包错误
在application(_:didFinishLaunchingWithOptions:)执行时,SwiftUI的根视图是ContentView而非FlutterViewController,直接强制解包window?.rootViewController as! FlutterViewController必然触发崩溃。
2. 正确实现:创建FlutterViewController时绑定通道
不要在AppDelegate中处理通道,而是在showFlutter()和showFlutter2()方法创建控制器时,直接为当前实例设置MethodChannel处理器。修改后的ContentView代码:
import SwiftUI import CoreData import Flutter struct ContentView: View { @EnvironmentObject var flutterDependencies: FlutterDependencies var body: some View { Text("iOS View") Button("Show Flutter!") { showFlutter() } Button("Show Flutter2!") { showFlutter2() } } func showFlutter() { guard let windowScene = UIApplication.shared.connectedScenes .first(where: { $0.activationState == .foregroundActive && $0 is UIWindowScene }) as? UIWindowScene, let window = windowScene.windows.first(where: \.isKeyWindow), let rootViewController = window.rootViewController else { return } let flutterViewController = FlutterViewController( engine: flutterDependencies.flutterEngine, nibName: nil, bundle: nil) flutterViewController.modalPresentationStyle = .overCurrentContext flutterViewController.isViewOpaque = false // 绑定MethodChannel到当前控制器 let channel = FlutterMethodChannel(name: "com.example/close_flutter_view", binaryMessenger: flutterViewController.binaryMessenger) channel.setMethodCallHandler { call, result in if call.method == "close" { flutterViewController.dismiss(animated: true) result(nil) } else { result(FlutterMethodNotImplemented) } } rootViewController.present(flutterViewController, animated: true) } func showFlutter2() { guard let windowScene = UIApplication.shared.connectedScenes .first(where: { $0.activationState == .foregroundActive && $0 is UIWindowScene }) as? UIWindowScene, let window = windowScene.windows.first(where: \.isKeyWindow), let rootViewController = window.rootViewController else { return } let flutterViewController = FlutterViewController( engine: flutterDependencies.flutterEngine2, nibName: nil, bundle: nil) flutterViewController.modalPresentationStyle = .overCurrentContext flutterViewController.isViewOpaque = false // 绑定MethodChannel到当前控制器 let channel = FlutterMethodChannel(name: "com.example/close_flutter_view", binaryMessenger: flutterViewController.binaryMessenger) channel.setMethodCallHandler { call, result in if call.method == "close" { flutterViewController.dismiss(animated: true) result(nil) } else { result(FlutterMethodNotImplemented) } } rootViewController.present(flutterViewController, animated: true) } }
3. 简化AppDelegate
移除无效的通道代码,修改后的AppDelegate.swift:
import Foundation import UIKit import Flutter class AppDelegate: NSObject, UIApplicationDelegate { func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool { print("App started") return true } }
4. 解决import Flutter无模块问题
- 确保
Podfile已正确集成Flutter模块,执行过pod install - 在Xcode中打开
.xcworkspace文件而非.xcodeproj - 清理Xcode缓存(
Cmd+Shift+K)并重新构建
内容的提问来源于stack exchange,提问作者Vincenzo

