Flutter 调用 iOS 代码实践篇

3,099 阅读4分钟

纸上得来终觉浅,绝知此事要躬行。

最近准备使用 Flutter 做点小东西,可能会涉及到与原生的交互,参考 使用平台通道编写平台特定的代码 这篇文章实践了一下,记录一下过程和其中遇到的问题。

实践环境说明
  • macOS Big Sur
  • Vistual Studio Code
  • Flutter 1.22.6
  • Dart 2.10.5
  • Xcode 12.4
创建工程
环境的搭建我略过了,如果有需要可以后续在写一篇实践篇。
  1. 按照文档,在想要创建工程的文件夹打开命令行,直接使用 flutter create -i swift -a kotlin batterylevel (因为想使用 swift 和 kotlin,如果使用 java 和 Objective-C 可以直接使用 flutter create batterylevel 命令 )创建一个显示设备电量的工程,这一步没有什么问题。
  2. 创建完成后在命令行先使用 open -a Simulator 打开一个模拟的手机。
  3. 在命令后内输入 flutter run ,可以使工程在模拟的手机内运行,可以看到一个简易的页面。

添加 Flutter 代码调用函数

  1. 首先声明一个 MethodChannel, MethodChannel 的参数名称为通道名称,在单个应用中应该唯一。
  static const platform = const MethodChannel("samples.flutter.io/battery");
  1. 声明显示电量的字符串
  String _batteryLevel = 'Unknown battery level.';
  1. 声明一个获取电量函数调用原生代码
  Future<Null> _getBatteryLevel() async {
    String batteryLevel;
    try {
      final int result = await platform.invokeMethod('getBatteryLevel');
      batteryLevel = 'Battery level at $result %.';
    } on PlatformException catch (e) {
      batteryLevel = "Failed to get battery level: '${e.message}'. ";
    }
    setState(() {
      _batteryLevel = batteryLevel;
    });
  }
  1. 定义控件,一个 RaisedButton 用于点击获取电量交互,一个 Text 用于显示电量。两个控件放到 Widget 的数组内。
            RaisedButton(
              child: new Text('Get Battery Level'),
              onPressed: _getBatteryLevel,
            ),
            Text(_batteryLevel)

ios 获取电池电量

  1. 使用 Xcode 打开项目内的 ios 文件夹。
  2. 打开 AppDelegate.swift 文件,修改 application 函数为
  override func application(
    _ application: UIApplication,
    didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
  ) -> Bool {
    GeneratedPluginRegistrant.register(with: self);
    let controller: FlutterBinaryMessenger = window?.rootViewController as! FlutterBinaryMessenger;
    let batteryChannel = FlutterMethodChannel.init(name: "samples.flutter.io/battery", binaryMessenger: controller);
    batteryChannel.setMethodCallHandler({
        (call: FlutterMethodCall, result: FlutterResult) -> Void in
        if ("getBatteryLevel" == call.method) {
            receiveBatteryLevel(result: result)
        } else {
            result(FlutterMethodNotImplemented)
        }
    })
    return super.application(application, didFinishLaunchingWithOptions: launchOptions);
  }

不知道是不是因为 Flutter 更新的原因, 这里我参照文档的时候发现文档有个地方有问题。FlutterMethodChannel.initbinaryMessenger 参数类型是 FlutterBinaryMessenger, 但是参考文档里面写的是 FlutterViewController, 所以我声明的 controllerFlutterBinaryMessenger.

然后在该文件的最后添加获取电池电量函数,参考文档里面 UIDeviceBatteryState 这个提示找不到,后来我查看了 UIDevice 的文档,发现应该是 UIDevice.BatteryState

private func receiveBatteryLevel(result: FlutterResult) {
    let device = UIDevice.current;
    device.isBatteryMonitoringEnabled = true;
    if (device.batteryState == UIDevice.BatteryState.unknown) {
        result(FlutterError.init(code: "UNAVAILABLE", message: "Battery info unavailable", details: nil))
    } else {
        result(Int(device.batteryLevel * 100))
    }
}

运行程序

在工程目录打开命令行,输入 flutter run,运行项目,稍等一会 app 会安装到模拟的手机中,点击 Get Battery Level 按钮,发现电量显示 Battery info unavailable。我发现是因为模拟器的 UIDevice.batteryState 就是为UIDevice.BatteryState.unknown,所以为了测试获取电量正确还得连接真机。

如果需要连接真机得设置 Signing & Capabilities , 点击 Runner ,在页面中选择 Signing & Capabilities,在 Team 的选择框中选择 Add an Account,输入自己的 Apply Id 用户名和密码就可以了,成功后确保没有报错,我遇到输入的 Bundle Identifier 已经存在,重新换一个就好了。如果有报错会在这个页面的 Status 里面显示,仔细阅读可以解决。我之前网上搜索说需要添加 iOS Development,但是我的 Xcode 是不需要的,只有一个 Apple Development。连接上真机后,继续 flutter run。然后点击 Get Battery Level 按钮,发现可以显示出实际的电量。到此 Flutter 调用 iOS 就完成了。

注意:需要在 iPhone 的设置中信任该 app

如果你有多个设备,可以先使用 flutter devices 查看设备的 id。
然后 flutter run -d 设备ID,指定运行的设备。

其他问题

  1. 遇到 Xcode 工程中的 Debug.xcconfigRelease.xcconfig 引用了 Generated.xcconfig,但是项目中没有 Generated.xcconfig,可以按照下面步骤解决
    • flutter clean (in terminal)
    • flutter build (in terminal)
    • In Xcode, then clean project
    • In Xcode, then build project
  2. 提示 Info.plist does not exist. The Flutter "Thin Binary" build phase must run after "Copy Bundle Resources". 我 clean 一下之后在重新 build 解决了。

参考资料

  1. 使用平台通道编写平台特定的代码
  2. UIDevice
  3. Could not find included file 'Generated.xcconfig' in search paths (in target 'Runner')