Skip to content

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.

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.)

  1. Prerequisites: You need a macOS machine with Xcode installed to build and run the iOS part.
  2. Create Project: flutter create ios_platform_channel_app.
  3. Dart Code (lib/main.dart): The Dart code remains exactly the same as in the Android example. The MethodChannel name (com.example.ios_platform_channel_app/browser - adjust to your org/app) and the method call (openBrowser with url argument) 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 ...
  1. iOS Native Code (Swift - Recommended):
    • Open the iOS module in Xcode: Right-click the ios folder in your Flutter project in Finder and choose Open in Xcode, or use open ios/Runner.xcworkspace in the terminal.
    • Navigate to Runner > Runner > AppDelegate.swift.
import UIKit
import 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))
}
}
}
  1. (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
  1. Configure Info.plist (If opening non-http/https URLs): If you needed to open URLs with custom schemes (like mailto: or app-specific schemes), you would need to add them to the LSApplicationQueriesSchemes array in ios/Runner/Info.plist. This isn’t strictly necessary for http/https.
  2. 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 to https://flutter.dev.
  • 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 (using FlutterError for 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.plist keys 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.