Flutter - Writing IOS Specific Code
Flutter - Writing iOS Platform-Specific Code
Section titled “Flutter - Writing iOS Platform-Specific Code”Similar to Android, Flutter allows communication with native iOS code (written in Swift or Objective-C) using Platform Channels. This enables access to iOS-specific APIs, frameworks, and third-party native SDKs.
The architecture (Client/Host, MethodChannel, Message Codec, Async communication) is identical to the Android implementation described previously. The primary difference lies in the native language and APIs used on the host (iOS) side.
Example: Opening a URL in Safari (iOS)
Section titled “Example: Opening a URL in Safari (iOS)”We’ll implement the same ‘open browser’ functionality, this time targeting iOS. (Again, remember the url_launcher package simplifies this specific task, but this example illustrates the manual platform channel process.)
- Prerequisites: You need a macOS machine with Xcode installed to build and run the iOS part.
- Create Project:
flutter create ios_platform_channel_app. - Dart Code (
lib/main.dart): The Dart code remains exactly the same as in the Android example. TheMethodChannelname (com.example.ios_platform_channel_app/browser- adjust to your org/app) and the method call (openBrowserwithurlargument) are platform-agnostic.
// --- Dart code is identical to the Android example ---// --- Ensure MethodChannel name matches the one used below ---// static const platform = MethodChannel('com.example.ios_platform_channel_app/browser');// ... rest of main.dart from Android example ...- iOS Native Code (Swift - Recommended):
-
- Open the iOS module in Xcode: Right-click the
iosfolder in your Flutter project in Finder and chooseOpen in Xcode, or useopen ios/Runner.xcworkspacein the terminal.
- Open the iOS module in Xcode: Right-click the
-
- Navigate to
Runner>Runner>AppDelegate.swift.
- Navigate to
import UIKitimport Flutter
@UIApplicationMain@objc class AppDelegate: FlutterAppDelegate { override func application( _ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? ) -> Bool {
// 1. Get the FlutterViewController // It's essential to get the controller to access the binary messenger guard let controller = window?.rootViewController as? FlutterViewController else { fatalError("rootViewController is not type FlutterViewController") }
// 2. Define the same channel name used in Dart let channelName = "com.example.ios_platform_channel_app/browser" let browserChannel = FlutterMethodChannel(name: channelName, binaryMessenger: controller.binaryMessenger)
// 3. Set the MethodCallHandler browserChannel.setMethodCallHandler({ [weak self] (call: FlutterMethodCall, result: @escaping FlutterResult) -> Void in // This closure is invoked when Dart calls invokeMethod
// 4. Check which method was called guard call.method == "openBrowser" else { result(FlutterMethodNotImplemented) return }
// 5. Extract arguments // Arguments are often passed as a Dictionary if let args = call.arguments as? [String: Any], let urlString = args["url"] as? String { // 6. Call the native function 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. Native function to open the 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 }
// Check if the URL can be opened before attempting if UIApplication.shared.canOpenURL(url) { UIApplication.shared.open(url, options: [:]) { (success) in // Send result back to Dart after the open attempt result(success) } } else { result(FlutterError(code: "CANNOT_OPEN_URL", message: "Cannot open URL: \(urlString)", details: nil)) } }}- (Alternative) iOS Native Code (Objective-C): If your project uses Objective-C, edit
Runner>Runner>AppDelegate.m.
#import "AppDelegate.h"#import "GeneratedPluginRegistrant.h"
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// 1. Get the FlutterViewController FlutterViewController* controller = (FlutterViewController*)self.window.rootViewController;
// 2. Define the same channel name used in Dart NSString* channelName = @"com.example.ios_platform_channel_app/browser"; FlutterMethodChannel* browserChannel = [FlutterMethodChannel methodChannelWithName:channelName binaryMessenger:controller.binaryMessenger];
// 3. Set the MethodCallHandler __weak typeof(self) weakSelf = self; [browserChannel setMethodCallHandler:^(FlutterMethodCall* call, FlutterResult result) { // This block is invoked when Dart calls invokeMethod
// 4. Check which method was called if ([@"openBrowser" isEqualToString:call.method]) { // 5. Extract arguments // Arguments are often passed as a Dictionary NSDictionary* args = call.arguments; NSString* urlString = args[@"url"];
if (urlString != nil && [urlString isKindOfClass:[NSString class]]) { // 6. Call the native function [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. Native function to open the 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) { // Send result back to Dart after the open attempt result(@(success)); }]; } else { result([FlutterError errorWithCode:@"CANNOT_OPEN_URL" message:[NSString stringWithFormat:@"Cannot open URL: %@", urlString] details:nil]); }}
@end- Configure
Info.plist(If opening non-http/https URLs): If you needed to open URLs with custom schemes (likemailto:or app-specific schemes), you would need to add them to theLSApplicationQueriesSchemesarray inios/Runner/Info.plist. This isn’t strictly necessary forhttp/https. - Run the App: Select an iOS Simulator or a connected physical iOS device in your IDE. Stop any previous runs and restart the application (
flutter run). Click the ‘Open Flutter Dev’ button. It should launch Safari and navigate tohttps://flutter.dev.
Important Considerations
Section titled “Important Considerations”- Language Choice: Swift is generally preferred for new iOS development due to its modern features and safety.
- Error Handling: Implement robust error handling on both the Dart (
PlatformException) and native sides (usingFlutterErrorfor results). - Thread Safety (iOS): Platform channel handlers are executed on the main thread. Offload long-running tasks to background queues using Grand Central Dispatch (GCD) to avoid blocking the UI.
- API Availability: Ensure the native APIs you’re calling are available on the target iOS versions.
- Permissions: Accessing certain native features (like camera, location, contacts) requires requesting user permission via
Info.plistkeys and potentially using platform channels or packages to trigger the permission dialogs. - Packages: As with Android, always check pub.dev first for existing packages (like
url_launcher) that might already provide the required native functionality.