Flutter - 编写 iOS 特定代码
Flutter - 编写 iOS 平台特定代码
Section titled “Flutter - 编写 iOS 平台特定代码”与 Android 类似,Flutter 允许使用平台通道(Platform Channels)与原生 iOS 代码(用 Swift 或 Objective-C 编写)进行通信。这使得可以访问 iOS 特定的 API、框架(frameworks)和第三方原生 SDK。
架构(客户端 Client/宿主 Host、MethodChannel、消息编解码器 Message Codec、异步通信 Async communication)与之前描述的 Android 实现相同。主要区别在于宿主(iOS)端使用的原生语言和 API。
示例:在 Safari 中打开 URL (iOS)
Section titled “示例:在 Safari 中打开 URL (iOS)”我们将实现相同的“打开浏览器”功能,这次针对 iOS。(同样,请记住 url_launcher 包简化了这个特定任务,但此示例是为了说明手动平台通道过程。)
- 先决条件: 你需要一台安装了 Xcode 的 macOS 机器来构建和运行 iOS 部分。
- 创建项目: 运行
flutter create ios_platform_channel_app。 - Dart 代码(
lib/main.dart): Dart 代码与 Android 示例中的完全相同。MethodChannel名称(com.example.ios_platform_channel_app/browser- 根据你的组织/应用进行调整)和方法调用(openBrowser带url参数)是平台无关的。
// --- Dart 代码与 Android 示例相同 ---// --- 确保 MethodChannel 名称与下方使用的名称匹配 ---// static const platform = MethodChannel('com.example.ios_platform_channel_app/browser');// ... rest of main.dart from Android example ...- iOS 原生代码 (Swift - 推荐):
-
- 在 Xcode 中打开 iOS 模块:在 Finder 中右键点击 Flutter 项目中的
ios文件夹,选择Open in Xcode(在 Xcode 中打开),或在终端中使用open ios/Runner.xcworkspace。
- 在 Xcode 中打开 iOS 模块:在 Finder 中右键点击 Flutter 项目中的
-
- 导航到
Runner>Runner>AppDelegate.swift。
- 导航到
import UIKitimport Flutter
@UIApplicationMain@objc class AppDelegate: FlutterAppDelegate { override func application( _ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? ) -> Bool {
// 1. 获取 FlutterViewController // 获取 controller 对于访问 binary messenger 至关重要 guard let controller = window?.rootViewController as? FlutterViewController else { fatalError("rootViewController is not type FlutterViewController") }
// 2. 定义与 Dart 中使用的相同通道名称 let channelName = "com.example.ios_platform_channel_app/browser" let browserChannel = FlutterMethodChannel(name: channelName, binaryMessenger: controller.binaryMessenger)
// 3. 设置 MethodCallHandler browserChannel.setMethodCallHandler({ [weak self] (call: FlutterMethodCall, result: @escaping FlutterResult) -> Void in // 当 Dart 调用 invokeMethod 时,此闭包被调用
// 4. 检查调用了哪个方法 guard call.method == "openBrowser" else { result(FlutterMethodNotImplemented) return }
// 5. 提取参数 // 参数通常以 Dictionary 形式传递 if let args = call.arguments as? [String: Any], let urlString = args["url"] as? String { // 6. 调用原生函数 self?.openBrowser(urlString: urlString, result: result) } else { result(FlutterError(code: "INVALID_ARGUMENT", message: "URL argument is missing or not a string", details: nil)) } })
GeneratedPluginRegistrant.register(with: self) return super.application(application, didFinishLaunchingWithOptions: launchOptions) }
// 7. 打开 URL 的原生函数 private func openBrowser(urlString: String, result: FlutterResult) { guard let url = URL(string: urlString) else { result(FlutterError(code: "INVALID_URL", message: "Could not parse URL: \(urlString)", details: nil)) return }
// 在尝试打开之前检查 URL 是否可以打开 if UIApplication.shared.canOpenURL(url) { UIApplication.shared.open(url, options: [:]) { (success) in // 打开尝试后将结果发送回 Dart result(success) } } else { result(FlutterError(code: "CANNOT_OPEN_URL", message: "Cannot open URL: \(urlString)", details: nil)) } }}- (备选方案) iOS 原生代码 (Objective-C): 如果你的项目使用 Objective-C,编辑
Runner>Runner>AppDelegate.m。
#import "AppDelegate.h"#import "GeneratedPluginRegistrant.h"
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// 1. 获取 FlutterViewController FlutterViewController* controller = (FlutterViewController*)self.window.rootViewController;
// 2. 定义与 Dart 中使用的相同通道名称 NSString* channelName = @"com.example.ios_platform_channel_app/browser"; FlutterMethodChannel* browserChannel = [FlutterMethodChannel methodChannelWithName:channelName binaryMessenger:controller.binaryMessenger];
// 3. 设置 MethodCallHandler __weak typeof(self) weakSelf = self; [browserChannel setMethodCallHandler:^(FlutterMethodCall* call, FlutterResult result) { // 当 Dart 调用 invokeMethod 时,此块被调用
// 4. 检查调用了哪个方法 if ([@"openBrowser" isEqualToString:call.method]) { // 5. 提取参数 // 参数通常以 Dictionary 形式传递 NSDictionary* args = call.arguments; NSString* urlString = args[@"url"];
if (urlString != nil && [urlString isKindOfClass:[NSString class]]) { // 6. 调用原生函数 [weakSelf openBrowserWithUrlString:urlString result:result]; } else { result([FlutterError errorWithCode:@"INVALID_ARGUMENT" message:@"URL argument is missing or not a string" details:nil]); } } else { result(FlutterMethodNotImplemented); } }];
[GeneratedPluginRegistrant registerWithRegistry:self]; // Override point for customization after application launch. return [super application:application didFinishLaunchingWithOptions:launchOptions];}
// 7. 打开 URL 的原生函数- (void)openBrowserWithUrlString:(NSString*)urlString result:(FlutterResult)result { NSURL* url = [NSURL URLWithString:urlString]; if (url == nil) { result([FlutterError errorWithCode:@"INVALID_URL" message:[NSString stringWithFormat:@"Could not parse URL: %@", urlString] details:nil]); return; }
UIApplication* application = [UIApplication sharedApplication]; if ([application canOpenURL:url]) { [application openURL:url options:@{} completionHandler:^(BOOL success) { // 打开尝试后将结果发送回 Dart result(@(success)); }]; } else { result([FlutterError errorWithCode:@"CANNOT_OPEN_URL" message:[NSString stringWithFormat:@"Cannot open URL: %@", urlString] details:nil]); }}
@end- 配置
Info.plist(如果打开非 http/https URL): 如果你需要打开自定义 scheme 的 URL(例如mailto:或应用特定的 scheme),你需要在ios/Runner/Info.plist中的LSApplicationQueriesSchemes数组中添加它们。对于http/https这不是必需的。 - 运行应用: 在 IDE 中选择一个 iOS 模拟器或连接的物理 iOS 设备。停止任何之前的运行并重新启动应用(
flutter run)。点击“Open Flutter Dev”按钮。它应该会启动 Safari 并导航到https://flutter.dev。
重要注意事项
Section titled “重要注意事项”- 语言选择: 通常推荐使用 Swift 进行新的 iOS 开发,因为它具有现代特性和安全性。
- 错误处理: 在 Dart (
PlatformException) 和原生端(使用FlutterError返回结果)都实现健壮的错误处理。 - 线程安全 (iOS): 平台通道处理程序在主线程(main thread)上执行。将长时间运行的任务转移到后台队列(background queues),使用 Grand Central Dispatch (GCD) 来避免阻塞 UI。
- API 可用性: 确保你调用的原生 API 在目标 iOS 版本上可用。
- 权限: 访问某些原生功能(如相机 camera、位置 location、联系人 contacts)需要通过
Info.plist键请求用户许可,并且可能需要使用平台通道或包来触发许可对话框。 - 包: 与 Android 一样,始终首先检查 pub.dev 是否存在可能已经提供所需原生功能的现有包(如
url_launcher)。