FlutterでiOSのネイティブコードを呼び出す


FlutterでMethodChannelを使うとAndroid/iOSのネイティブコードとやりとりができます。Androidはプログラミング経験があるのでMethodChannelもすんなり理解できましたが、iOSは全然経験がなかったので、とりあえず入門書を読んだりしつつHello World的なことをしてみました。

今更Objective-Cは触りたくないのでSwiftでやりました。Kotlinと書き方似てるから親しみやすい。
FlutterのプロジェクトでiOSのネイティブ処理をSwiftで行うためには、プロジェクト作成時に以下のように指定します。

flutter create -i swift my_app

以下のようなコードでDartからSwiftのコードを呼び出し、結果を返してもらうことができました。iOSの方でいじる必要があったのはAppDelegate.swiftだけです。

lib/main.dart
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  final _channel = MethodChannel('hello_ios');
  String _message = 'Push Button';

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Hello iOS'),
      ),
      body: Center(
        child: Text(_message),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () async {
          final message = await _channel.invokeMethod('getMessage');
          setState(() {
            _message = message;
          });
        },
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ),
    );
  }
}
ios/Runner/AppDelegate.swift
import UIKit
import Flutter

@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
  override func application(
    _ application: UIApplication,
    didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?
  ) -> Bool {
    GeneratedPluginRegistrant.register(with: self)

    let controller: FlutterViewController = window?.rootViewController as! FlutterViewController
    let methodChannel = FlutterMethodChannel(name: "hello_ios", binaryMessenger: controller)

    methodChannel.setMethodCallHandler({
      (call: FlutterMethodCall, result: FlutterResult) -> Void in

      if call.method == "getMessage" {
        result("Hello from iOS")
      } else {
        result(FlutterMethodNotImplemented)
      }
    })

    return super.application(application, didFinishLaunchingWithOptions: launchOptions)
  }
}

AndroidではFlutterActivityとFlutterViewが存在していましたが、iOSではFlutterViewControllerが両方を兼ね備えたようなクラスっぽいです。あとはMethodChannelを使う方法も同じ。結果の返し方がちょっとAndroidと違いますが、たぶんよりSwiftっぽい書き方になっているのだと思います。渡せる値の種類とかは同じです。

受け渡し可能なデータ型は以下のとおりです。

Dart Android iOS
null null nil (NSNull when nested)
bool java.lang.Boolean NSNumber numberWithBool:
int java.lang.Integer NSNumber numberWithInt:
int, if 32 bits not enough java.lang.Long NSNumber numberWithLong:
double java.lang.Double NSNumber numberWithDouble:
String java.lang.String NSString
Uint8List byte[] FlutterStandardTypedData typedDataWithBytes:
Int32List int[] FlutterStandardTypedData typedDataWithInt32:
Int64List long[] FlutterStandardTypedData typedDataWithInt64:
Float64List double[] FlutterStandardTypedData typedDataWithFloat64:
List java.util.ArrayList NSArray
Map java.util.HashMap NSDictionary

FlutterではUIの部分はAndroidと完全に共通のコードでDartで実装できるため、Storyboardとか使う必要はありません。SwiftのAPIの呼び出し方さえわかれば、AppDelegateの中にちょっと書くだけで簡単にネイティブ処理が書けそうだということがわかりました。

VSCodeでのSwiftのコード補完さえしっかりできるようになれば、XCodeのエディターを使わなくともVSCodeだけでiOSのネイティブ処理が作れそうな気がする。素晴らしい。

Flutterはいいぞ。