关于Swift Flutter交互随记

763 阅读4分钟
  • Swift版本:5.0
  • Xcode版本:13.2.1
  • Flutter版本:3.3.9
  • 设备:Mac mini(M1 2020)
  • MacOS版本:12.2.1

环境安装时提示 zsh: command not found: flutter

我是直接把 .zshrc 和 .bash_profile 文件都创建在了/User目录下。.zshrc文件的内容复制的是.bash_profile内容

[[ -s "$HOME/.profile" ]] && source "$HOME/.profile" # Load the default .profile
[[ -s "$HOME/.rvm/scripts/rvm" ]] && source "$HOME/.rvm/scripts/rvm" # Load RVM into > a shell session *as a function*
export LC_ALL=en_US.UTF-8
export LANG=en_US.UTF-8
export PATH=/Users/xxx/Desktop/flutter/bin:$PATH
export PUB_HOSTED_URL=https://pub.flutter-io.cn
export FLUTTER_STORAGE_BASE_URL=https://storage.flutter-io.cn

唯一区别是在 .zshrc 文件的底部还加了 'source ~/.bash_profile',保存直接重启终端就可以直接使用flutter相关命令了,而不用每次都要 cd 和 source ~/.bash_profile

集成Flutter我采用的是CocoaPods的方式,毕竟原生项目本身就有所以快速方便。

  • 首先创建一个Flutter module用来实现Flutter的内容

cd路径自定义

然后终端执行: flutter create --template module xxx(项目名称自定义)

随后会在相关目录下生成一个flutter项目文件

  • 在Podfile中添加相关集成信息
source 'https://github.com/CocoaPods/Specs.git'
platform :ios, '11.0'
inhibit_all_warnings!
use_frameworks!
#此处根据自己Flutter项目实际路径填写
flutter_application_path = 'xxx'
#此句不可缺少
load File.join(flutter_application_path, '.ios', 'Flutter', 'podhelper.rb')
target 'FrogHunter' do
  install_all_flutter_pods(flutter_application_path)

  pod 'SnapKit'
end

post_install do |installer|
flutter_post_install(installer) if defined?(flutter_post_install)
end

关于 platform 一开始设置的是10.0,单pod isntall后一直给我报版本过低的错误,所以就试了试11.0结果就成功了

flutter_application_path : 生成的flutter module 路径

随后执行pod install 提示成功就OK了

  • 添加相关代码

在 AppDelegate 中添加相关头文件

import Flutter
import FlutterPluginRegistrant

创建 FlutterEngine

lazy var flutterEngine = FlutterEngine(name: "my flutter engine")

在didFinishLaunchingWithOptions方法中添加相关代码

flutterEngine.run()
GeneratedPluginRegistrant.register(with: self.flutterEngine)
  • 传值

添加 import Flutter 后就能得到一个FlutterViewController 相当于相当于 Flutter 端的一个 ViewController

但 FlutterViewController 的初始化需要传入一个 FlutterEngine,可以使用在 AppDelegate 创建的 FlutterEngine

let flutterEngine = (UIApplication.shared.delegate as! AppDelegate).flutterEngine
flutterViewController = FlutterViewController(engine: flutterEngine, nibName: nil, bundle: nil)

这时就已经可以直接使用 pushViewController 或者 present 来显示 FlutterViewController

传值有两种,一种是直接传值:即直接传值给Flutter端,还有种则是间接性传值:需要触发某种条件后传值

  • 直接传值:

Swfit端:

var channel : FlutterEventChannel?
var eventSink : FlutterEventSink?

self.channel = FlutterEventChannel(name: "swift.value", binaryMessenger: flutterViewController as! FlutterBinaryMessenger)
self.channel?.setStreamHandler(self)

"swift.value" 可以被定义为和Flutter建立通信的唯一标识

实现 FlutterStreamHandler 协议

func onListen(withArguments arguments: Any?, eventSink events: @escaping FlutterEventSink) -> FlutterError? {
    self.eventSink = events
    return nil
}

func onCancel(withArguments arguments: Any?) -> FlutterError? {
    return nil
 }

随后如果我们想要在 pushViewController 或者 present 时把值传递时,可以在相应的触发方法中添加如下代码:

self.eventSink?("hahahahh")    
self.navigationController?.pushViewController(flutterViewController!, animated: true)

eventSink()中所带的内容则是需要传递的值

Flutter端:

设置对应标识并接收值

static const EventChannel eventChannel = EventChannel('swift.value');

@override
 void initState() {
   super.initState();

   eventChannel.receiveBroadcastStream("init").listen((event) {
     _context = event.toString();
     setState(() {});
   });
 }

完整代码如下

import UIKit
import Flutter

class FHEntranceViewController: UIViewController {

    var channel : FlutterEventChannel?
    var eventSink : FlutterEventSink?
    var flutterViewController : FlutterViewController? = nil

    override func viewDidLoad() {
        super.viewDidLoad()

        self.view.backgroundColor = .white

        let flutterEngine = (UIApplication.shared.delegate as! AppDelegate).flutterEngine
        flutterViewController = FlutterViewController(engine: flutterEngine, nibName: nil, bundle: nil)
        self.channel = FlutterEventChannel(name: "swift.value", binaryMessenger: flutterViewController as! FlutterBinaryMessenger)
        self.channel?.setStreamHandler(self)

    }

    func mobileEntranceLadders(_ mobilestr : String) {
        self.eventSink?("hahahahh")
        self.navigationController?.pushViewController(flutterViewController!, animated: true)
    }

}

extension FHEntranceViewController : FlutterStreamHandler {

    func onListen(withArguments arguments: Any?, eventSink events: @escaping FlutterEventSink) -> FlutterError? {
        self.eventSink = events
        return nil
    }


    func onCancel(withArguments arguments: Any?) -> FlutterError? {
        return nil
    }
}

关于间接传值是指当触发了Flutter中的某个方法,从而把Swift端的值传递到Flutter端。 例: 在Flutter中设置一个按钮和一个Text用来触发传值请求和现实传递过来的值

Flutter端:

 Widget build(BuildContext context) {
    return Column(
      mainAxisAlignment: MainAxisAlignment.spaceBetween,
      children: <Widget>[
        Center(
          child: TextButton(
            onPressed: () {
              _communicateFunction('传值给Swift');
              setState(() {});
            },
            child: const Text("点击传值"),
          ),
        ),
       Center(
         child: Text(value),
        ),
      ],
    );
  }

_communicateFunction()内的内容是传递给Swift的值

添加相应的传值和接收方法

  static const communicateChannel = MethodChannel('ReceiveValue');
  //异步执行调用原生方法,保持页面不卡住,因为调用原生的方法可能没实现会抛出异常,所以trycatch包住
  Future<void> _communicateFunction(flutterPara) async {
    try {
      //原生方法名为callNativeMethond,flutterPara为flutter调用原生方法传入的参数,await等待方法执行
      final result = await communicateChannel.invokeMethod(
          'callNativeMethond', flutterPara);
      //如果原生方法执行回调传值给flutter,那下面的代码才会被执行
      value = result;
      setState(() {});
    } on PlatformException catch (e) {
      //抛出异常
      //flutter: PlatformException(001, 进入异常处理, 进入flutter的trycatch方法的catch方法)
      print(e);
    }
  }

Swift端:

let methodChannel = FlutterMethodChannel(name: "ReceiveValue", binaryMessenger: >flutterViewController as! FlutterBinaryMessenger)

methodChannel.setMethodCallHandler { call, result in
   print("call =====\(call),result ===== \(String(describing: result))")
   if call.method == "callNativeMethond" {
      print(call.arguments!)
      result("我是原生返回数据")
   }
}

result()中的内容是传递给Flutter的值

而 call.arguments 则是Flutter传递给Swfit的内容

如此就实现了Swift和Flutter的交互,当然传递值的类型还有报错等都是需要在实际项目中进行处理的