乐趣区

Flutter混合开发二iOS项目集成Flutter模块详细指南

本文首发于微信公众号「Android 开发之旅」,欢迎关注,获取更多技术干货

前言

前一篇文章讲解了 Android 原生工程如何集成 Flutter 项目的具体过程,Flutter 混合开发 (一):Android 项目集成 Flutter 模块详细指南 , 本篇将带着大家来一起学习原生 iOS 项目如何集成 Flutter。

因为每个版本的集成逻辑都是有差别的,所以这里交代下本篇文章的集成版本:

Flutter(channel dev,v1.10.16)
dart(v2.7.0)
Xcode(v11.2)
cocoapds(1.8.4)

创建 Flutter module

假如 iOS 项目的路径是这样的:flutter/flutter_hybrid/iOS Project, 那么我们需要在 iOS Project 上一层目录 flutter_hybrid 中创建 Flutter module。

cd flutter/flutter_hybrid/

flutter create -t module flutter_module

输入后控制台打印如下:

$ flutter create -t module flutter_module
Creating project flutter_module...
  flutter_module/test/widget_test.dart (created)
  flutter_module/flutter_module.iml (created)
  flutter_module/.gitignore (created)
  flutter_module/.metadata (created)
  flutter_module/pubspec.yaml (created)
  flutter_module/README.md (created)
  flutter_module/lib/main.dart (created)
  flutter_module/flutter_module_android.iml (created)
  flutter_module/.idea/libraries/Flutter_for_Android.xml (created)
  flutter_module/.idea/libraries/Dart_SDK.xml (created)
  flutter_module/.idea/modules.xml (created)
  flutter_module/.idea/workspace.xml (created)
Running "flutter pub get" in flutter_module...                      1.2s
Wrote 12 files.

All done!
Your module code is in flutter_module/lib/main.dart.

看到 All done 就表示我们项目创建好了。整个 module 目录和原生 Flutter 基本一样,主要就是 Android、iOS 的宿主工程和 lib 目录以及 pubspec.yaml 文件。

添加 Flutter module 依赖

为 iOS 项目添加依赖需要使用 CocoaPods,如果你还没有用到 CocoaPods,可以参考 https://cocoapods.org/ 上面的说明来安装 CocoaPods。

如果你的项目之前没有使用过 cocoapods,那么需要进行初始化生成 podfile 文件,进入 iOS 项目的根目录执行:

pod init

然后打开 podfile 文件,进行配置:

# 配置
flutter_application_path = '../flutter_module/'
load File.join(flutter_application_path, '.ios', 'Flutter', 'podhelper.rb')

target 'iOSFlutterHybrid' do
  # Comment the next line if you don't want to use dynamic frameworks
  use_frameworks!

  # Pods for iOSFlutterHybrid
  # 配置
  install_all_flutter_pods(flutter_application_path)

  target 'iOSFlutterHybridTests' do
    inherit! :search_paths
    # Pods for testing
  end

  target 'iOSFlutterHybridUITests' do
    # Pods for testing
  end

配置添加好后在项目根目录运行以下命令进行安装:

pod install

控制台输出:

Analyzing dependencies
Downloading dependencies
Installing Flutter (1.0.0)
Installing FlutterPluginRegistrant (0.0.1)
Installing flutter_module (0.0.1)
Generating Pods project
Integrating client project

[!] Please close any current Xcode sessions and use `iOSFlutterHybrid.xcworkspace` for this project from now on.
Pod installation complete! There are 3 dependencies from the Podfile and 3 total pods installed.

[!] Automatically assigning platform `iOS` with version `13.2` on target `iOSFlutterHybrid` because no platform was specified. Please specify a platform for this target in your Podfile. See `https://guides.cocoapods.org/syntax/podfile.html#platform`.

这里我们看到有三个依赖安装完成了。并提醒我们关闭当前项目,在根目录下面使用 iOSFlutterHybrid.xcworkspace 来打开运行项目。这里可能很多人在执行命令的时候会发现提示 0 个依赖完成。这里有可能是你的 Xcode 版本的问题。因为 Flutter 要求最低版本是 10.2 及以上。

当在 flutter_module/pubspec.yaml 中添加一个 Flutter 插件时,需要在 flutter_module 目录下运行:

flutter packages get

来刷新 podhelper.rb 脚本中的插件列表,然后在 iOS 目录下运行:

pod install

这样 podhelper.rb 脚本才能确保添加的插件和 Flutter.framework 能够添加到 iOS 项目中。

目前 Flutter 还不支持 Bitcode,所以集成了 Flutter 的 iOS 项目需要禁用 Bitcode。

在以下路径下找到 Bitcode 并禁用:

Build Settings->Build Options->Enable Bitcode 

flutter 以前的版本是需要添加 build phase 以构建 Dart 代码,但是最新的版本已经不需要添加了,可以自动构建。

调用 Flutter module

Flutter 为我们提供了两种调用方式:FlutterViewController 和 FlutterEngine,FlutterEngine 在使用的时候会有一些问题,将在下文进行说明。

FlutterViewController 方式:

我们打开 ViewController.m 文件,在里面添加一个加载 flutter 页面的方法并且添加一个按钮看来调用:


#import "ViewController.h"
#import <Flutter/Flutter.h>
#import "AppDelegate.h"

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {[super viewDidLoad];
     
    UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
    [button addTarget:self action:@selector(handleButtonAction) forControlEvents:UIControlEventTouchUpInside];
    
    [button setTitle:@"加载 Flutter" forState:UIControlStateNormal];
    [button setBackgroundColor:[UIColor blueColor]];
    button.frame = CGRectMake(100, 100, 160, 60);
    [self.view addSubview:button];
}

- (void)handleButtonAction{FlutterViewController *flutterViewController =[FlutterViewController new];
    // 设置路由参数
    [flutterViewController setInitialRoute:@"route2"];
    [self presentViewController:flutterViewController animated:false completion:nil];
     
}

@end

当我们运行项目点击加载 Flutetr 按钮时,将会调用 Flutter 页面。和 Android 项目集成一样,这里的 setInitialRoute 可以设置一个 json 数组来传递需要交互的参数。并在 Flutter 中使用 window.defaultRouteName 来获取传递的参数。

FlutterEngine 方式:

我们需要在 AppDelegate 中对 FlutterEngine 进行初始化。打开 AppDelegate.h 文件:

#import <UIKit/UIKit.h>
#import <Flutter/Flutter.h>

@interface AppDelegate : FlutterAppDelegate
@property (nonatomic,strong) FlutterEngine *flutterEngine;
@end

在打开 AppDelegate.m 文件:

// 如果你需要用到 Flutter 插件时
#import <FlutterPluginRegistrant/GeneratedPluginRegistrant.h> 
#include "AppDelegate.h"

@implementation AppDelegate


- (BOOL)application:(UIApplication *)application
    didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {self.flutterEngine = [[FlutterEngine alloc] initWithName:@"io.flutter" project:nil];
  [self.flutterEngine runWithEntrypoint:nil];
  [GeneratedPluginRegistrant registerWithRegistry:self.flutterEngine]; // 如果你需要用到 Flutter 插件时
  return [super application:application didFinishLaunchingWithOptions:launchOptions];
}
@end

然后在 ViewController.m 文件定义的 handleButtonAction 中调用:

- (void)handleButtonAction{FlutterEngine *flutterEngine = [(AppDelegate *)[[UIApplication sharedApplication] delegate] flutterEngine];
    FlutterViewController *flutterViewController = [[FlutterViewController alloc] initWithEngine:flutterEngine nibName:nil bundle:nil];
    [flutterViewController setInitialRoute:@"route2"];
    [self presentViewController:flutterViewController animated:false completion:nil];
     
}

当我们运行项目点击加载 Flutter 按钮时,将会调用 Flutter 页面。前文讲到使用 FlutterEngine 会有问题,就是我们 setInitialRoute 传递的参数在 flutter 中永远获取到的都是“/”,这个是 Fltter SDK 的一个 Bug,所以如果必须依赖 setInitialRoute,还是使用 FlutterViewController 的形式来加载 Flutter 模块。

热重启 / 重新加载

大家在写纯 Flutter 应用的时候,知道是有热重启 / 重新加载功能的,但是在做混合开发的过程中,你会发现热重启 / 重新加载功能失效了。那么如何在混合开发中开启热重启 / 重新加载功能呢?

  • 首先接入我们的设备或者模拟器
  • 将我们的 App 关闭,退出后台,在 terminal 中运行 flutter attach 命令
$ flutter attach
Waiting for a connection from Flutter on Android SDK built for x86...

复制代码此时就在等待设备的连接。这里要注意的是,如果电脑连接了多台设备需要使用 -d 命令来指定一台设备,参数为设备的 id。

flutter attach -d '你的设备 id'

然后启动我们的应用会看到控制台输出:

Done.
Syncing files to device Android SDK built for x86...             1,393ms

????  To hot reload changes while running, press "r". To hot restart (and rebuild state), press "R".
An Observatory debugger and profiler on Android SDK built for x86 is available at: http://127.0.0.1:59354/zRsDBfpesrk=/
For a more detailed help message, press "h". To detach, press "d"; to quit, press "q".

这样就表示我们连接成功了。在输出的日志中也告诉了我们如何使用热重启 / 重新加载功能。

在 Terminal 中输入以下命令​:​

r : 热加载;R : 热重启;h : 获取帮助;d : 断开连接;q : 退出;

这里的的 d 和 q 的命令都有退出调试,区别在于 d 命令只是单纯的断开而 q 命令会将应用退到后台。

调试 Dart 代码

同样在混合开发过程中我们如何调试 dart 代码呢?

  • 关闭我们的应用
  • 点击 Android Studio 工具栏上的 Flutter Attach 按钮 (需要安装 Flutter 与 Dart 插件)

  • 启动我们的应用

接下来就可以像调试普通 Flutter 项目一样来调试混合开发模式下的 Dart 代码了。

总结

本文主要是讲解了 iOS 集成 Flutter 项目的步骤,其中也遇到了一些问题,由于我的 Xcode 版本较低,在集成的过程中 iOS 项目的依赖一直失败。最后才发现是 Xcode 的版本问题。这里花费了很多时间去排查问题,所以大家在集成的过程中有问题可以关注公众号加我微信,给我留言,我会帮助大家解决问题。

全部 Demo 源码已经上传到后台,关注公众号回复「混合开发」即可获得下载链接。

如果你觉得文章还不错,请大家点赞分享下,你的肯定是对我最大的鼓励和支持。

推荐阅读

Flutter 开发必备 Dart 基础:Dart 快速入门

Flutter 混合开发 (一):Android 项目集成 Flutter 模块详细指南

扫描下方二维码关注公众号,获取更多技术干货。

退出移动版