Flutter-已有iOS工程中加入Flutter之Cocoapods+Flutter环境方式集成

相关链接,如有需要请移步:
Flutter-已有iOS工程中加入Flutter之Cocoapods+Flutter环境方式集成
Flutter-已有iOS工程中加入Flutter之远程Pod产物方式集成
Flutter-远程Pod集成Flutter脚本优化

一、环境

Mac OS:10.14.6
Xcode:11.3
Flutter:1.12.13+hotfix.5 • channel stable • Dart 2.7.0
已经安装有Cocoapods,如果没有安装请移驾这里

如果集成方式有更新,请看最新官方文档

Demo地址

Note: 下载好demo后,请在kk_flutter项目中分别运行flutter pub getflutter build ios --debug后,再在Xcode上运行项目。否则可能会遇见Command PhaseScriptExecution failed with a nonzero exit code错误,或者Flutter/Flutter.h' file not found错误

Note:应用程序将无法在Release模式下运行到模拟器上,因为Flutter尚不支持Dart代码的输出x86预编译(AOT)二进制文件。 您可以在模拟器或真实设备上以Debug模式运行,而在真实设备上以Release模式运行。

二、集成方式

官方说有两种集成方式,分别是:

  1. 使用CocoaPods依赖性管理器和已安装的Flutter SDK(官方推荐)。
  2. 为Flutter引擎,已编译的Dart代码和所有Flutter插件创建Framework。手动嵌入Framework,并在Xcode中更新现有应用程序的构建设置。

三、创建Flutter_Module

  1. 进入到和我们iOS项目同级别的文件夹目录下
KKdeMacBook-Pro:~ kaye$ cd Desktop/NativeFultter/
  1. 创建flutter_module,这里将flutter_module命名为“kk_flutter”
    Note:flutter_module的命名要符合dart package name规范,要以小写单词和_相连,否则创建不成功

先给一个不规范的例子:

Last login: Wed Dec 25 09:05:21 on console
KKdeMacBook-Pro:~ kaye$ cd Desktop/NativeFultter/
KKdeMacBook-Pro:NativeFultter kaye$ flutter create -t module KKFlutter
╔════════════════════════════════════════════════════════════════════════════╗
  ║                 Welcome to Flutter! - https://flutter.dev                  ║
  ║                                                                            ║
  ║ The Flutter tool uses Google Analytics to anonymously report feature usage ║
  ║ statistics and basic crash reports. This data is used to help improve      ║
  ║ Flutter tools over time.                                                   ║
  ║                                                                            ║
  ║ Flutter tool analytics are not sent on the very first run. To disable      ║
  ║ reporting, type 'flutter config --no-analytics'. To display the current    ║
  ║ setting, type 'flutter config'. If you opt out of analytics, an opt-out    ║
  ║ event will be sent, and then no further information will be sent by the    ║
  ║ Flutter tool.                                                              ║
  ║                                                                            ║
  ║ By downloading the Flutter SDK, you agree to the Google Terms of Service.  ║
  ║ Note: The Google Privacy Policy describes how data is handled in this      ║
  ║ service.                                                                   ║
  ║                                                                            ║
  ║ Moreover, Flutter includes the Dart SDK, which may send usage metrics and  ║
  ║ crash reports to Google.                                                   ║
  ║                                                                            ║
  ║ Read about data we send with crash reports:                                ║
  ║ https://github.com/flutter/flutter/wiki/Flutter-CLI-crash-reporting        ║
  ║                                                                            ║
  ║ See Google's privacy policy:                                               ║
  ║ https://www.google.com/intl/en/policies/privacy/                           ║
  ╚════════════════════════════════════════════════════════════════════════════╝

"KKFlutter" is not a valid Dart package name.

From the [Pubspec format
description](https://www.dartlang.org/tools/pub/pubspec.html):

**DO** use `lowercase_with_underscores` for package names.

Package names should be all lowercase, with underscores to separate words,
`just_like_this`.  Use only basic Latin letters and Arabic digits: [a-z0-9_].
Also, make sure the name is a valid Dart identifier -- that it doesn't start
with digits and isn't a reserved word.

然后再来个正常的流程:

KKdeMacBook-Pro:NativeFultter kaye$ flutter create -t module kk_flutter
Creating project kk_flutter... androidx: true
  kk_flutter/test/widget_test.dart (created)
  kk_flutter/kk_flutter.iml (created)
  kk_flutter/.gitignore (created)
  kk_flutter/.metadata (created)
  kk_flutter/pubspec.yaml (created)
  kk_flutter/README.md (created)
  kk_flutter/lib/main.dart (created)
  kk_flutter/kk_flutter_android.iml (created)
  kk_flutter/.idea/libraries/Flutter_for_Android.xml (created)
  kk_flutter/.idea/libraries/Dart_SDK.xml (created)
  kk_flutter/.idea/modules.xml (created)
  kk_flutter/.idea/workspace.xml (created)
Running "flutter pub get" in kk_flutter...                          2.9s
Wrote 12 files.

All done!
Your module code is in kk_flutter/lib/main.dart.
KKdeMacBook-Pro:NativeFultter kaye$ 

这个时候,我们的目录结构是这样的:


创建flutter_module后的目录结构.png

进入到kk_flutter中,目录结构类似于这样:

kk_flutter/
├─.ios/ #隐藏文件,可以使用Command+shift+. 显示隐藏文件
│ ├─Runner.xcworkspace
│ └─Flutter/podhelper.rb
├─lib/ #我们的代码都写在这个文件夹中
│ └─main.dart
├─test/
└─pubspec.yaml #flutter依赖的库,都写在这里
flutter_module目录.png

四、将已经创建的flutter_module集成到现有的iOS项目中

4.1 手动导入

如果你想直接手动形式集成Framewok,可以在kk_flutter路径下,使用flutter build ios-framework --output=你想要导出的Framework路径(比如: /Users/kaye/Desktop/ios_flutter/NativeFlutter_module/Flutterframeworks),此时在你的指定目录下,会自动生成三个文件件分别是Debug、Profile、Release,三个文件夹中分别有App.frameworkFlutter.frameworkFlutterPluginRegistrant.framework等多个Framework,命令行界面如下:

KKdeMacBook-Pro:kk_flutter kaye$ flutter build ios-framework --output=/Users/kaye/Desktop/ios_flutter/NativeFlutter_module/Flutterframeworks 
Building framework for com.example.kkFlutter in debug mode...
 ├─Populating Flutter.framework...                                 172ms
 ├─Add placeholder App.framework for debug...                      177ms
 ├─Assembling Flutter resources for App.framework...                6.0s
 ├─Building plugins...                                              3.2s
 └─Moving to ../NativeFlutter_module/Flutterframeworks/Debug         0.0s
Building framework for com.example.kkFlutter in profile mode...
 ├─Populating Flutter.framework...                                 124ms
 ├─Building Dart AOT for App.framework...                          20.0s
 ├─Assembling Flutter resources for App.framework...                0.1s
 ├─Building plugins...                                             10.0s
 └─Moving to ../NativeFlutter_module/Flutterframeworks/Profile         0.0s
Building framework for com.example.kkFlutter in release mode...
 ├─Populating Flutter.framework...                                 672ms
 ├─Building Dart AOT for App.framework...                          67.0s
 ├─Assembling Flutter resources for App.framework...                0.1s
 ├─Building plugins...                                              7.9s
 └─Moving to ../NativeFlutter_module/Flutterframeworks/Release         0.0s
Frameworks written to /Users/kaye/Desktop/ios_flutter/NativeFlutter_module/Flutterframeworks.
编译后生成的Framework.png

将生成的三种模式下的Framework拖拽到项目中,并修改Xcode如下配置:


导入flutterFramework.png

然后就可以在Xcode运行了。

4.2 podfile导入

首先,此方法要求在您的项目上工作的每个开发人员都必须具有本地安装的Flutter SDK版本。 只需在Xcode中构建应用程序即可自动运行脚本以嵌入Dart和插件代码。 这允许使用Flutter模块的最新版本进行快速迭代,而无需在Xcode之外运行其他命令。

其次,你的原生项目要有Podfile文件,如果没有你可以通过命令行,进入到原生应用目录中,然后创建Podfile:

KKdeMacBook-Pro:~ kaye$ cd Desktop/NativeFultter/
KKdeMacBook-Pro:NativeFultter kaye$ ls
NativeFlutter_module    kk_flutter
KKdeMacBook-Pro:NativeFultter kaye$ cd NativeFlutter_module/
KKdeMacBook-Pro:NativeFlutter_module kaye$ ls
NativeFlutter_module        NativeFlutter_moduleTests
NativeFlutter_module.xcodeproj  NativeFlutter_moduleUITests
KKdeMacBook-Pro:NativeFlutter_module kaye$ pod init

目前我们的现有应用程序和flutter_module位于同级目录中。 如果您使用其他目录结构,则可能需要调整相对路径。

some/path/
├── kk_flutter/ #flutter_module
│   └── .ios/
│       └── Flutter/
│         └── podhelper.rb
└── NativeFlutter_module/ #原生工程
    └── Podfile
  1. 打开我们刚才在原生项目中创建的Podfile文件,将下面两句代码添加到里面:
flutter_application_path = '../kk_flutter' #这里是我们创建的flutter_module的路径
load File.join(flutter_application_path, '.ios', 'Flutter', 'podhelper.rb')
  1. 对于需要嵌入Flutter的每个Podfile目标,请调用install_all_flutter_pods(flutter_application_path)。整个Podfile文件内容如下:
# Uncomment the next line to define a global platform for your project
platform :ios, '9.0'

flutter_application_path = '../kk_flutter' #这里是我们创建的flutter_module的路径
load File.join(flutter_application_path, '.ios', 'Flutter', 'podhelper.rb')

target 'NativeFlutter_module' do
  # Uncomment the next line if you're using Swift or would like to use dynamic frameworks
  # use_frameworks!

  # Pods for NativeFlutter_module

  install_all_flutter_pods(flutter_application_path)

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

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

end
  1. 运行pod install
KKdeMacBook-Pro:NativeFlutter_module kaye$ pod install
Analyzing dependencies
Fetching podspec for `Flutter` from `../kk_flutter/.ios/Flutter/engine`
Fetching podspec for `FlutterPluginRegistrant` from `../kk_flutter/.ios/Flutter/FlutterPluginRegistrant`
Fetching podspec for `kk_flutter` from `../kk_flutter/.ios/Flutter`
Downloading dependencies
Installing Flutter (1.0.0)
Installing FlutterPluginRegistrant (0.0.1)
Installing kk_flutter (0.0.1)
Generating Pods project
Integrating client project

[!] Please close any current Xcode sessions and use `NativeFlutter_module.xcworkspace` for this project from now on.
Sending stats
Pod installation complete! There are 3 dependencies from the Podfile and 3 total pods installed.
KKdeMacBook-Pro:NativeFlutter_module kaye$ 

Note:当您在kk_flutter/pubspec.yaml中更改Flutter插件的依赖性时,请在flutter_module目录中运flutter pub get来刷新podhelper.rb脚本读取的插件列表。 然后,从您的应用程序的目录下需要再次运行pod install
podhelper.rb脚本将您的插件Flutter.frameworkApp.framework嵌入到您的项目中。
Flutter.framework是Flutter引擎的捆绑软件,而App.framework是该项目的已编译Dart代码。

  1. 打开生成的.workspace文件,Command+B,进行build一下,发现貌似没有什么问题。
    我们再来看一下目前的Xcode目录:
    pod中生成的Frameworks.png

五、开始使用,启动FlutterEngine和FlutterViewController

要从iOS应用中启动一个Flutter界面,我们必须先创建FlutterEngine和FlutterViewController。

FlutterEngine充当Dart VM和Flutter运行时的主机,FlutterViewController依附于FlutterEngine,以将UIKit输入事件传递到Flutter中并显示FlutterEngine渲染的帧。

FlutterEngine的生存时间大于等于FlutterViewController的生存时间。

通常建议为应用程序预先创建一个长生命周期的FlutterEngine,因为:
显示FlutterViewController时,第一帧显示会更快。
您的Flutter和Dart状态将超过一个FlutterViewController的生存时间。
在显示UI之前,您的应用程序和插件可以与Flutter和Dart逻辑进行交互。

5.1 创建FlutterEngine

考虑到上面所说,预先创建FlutterEngine,我们先在AppDelegate.h中创建一个引擎,并且暴露出来一个属性。然后在AppDelegate.m中注册引擎。

// Appdelegate.h
#import <UIKit/UIKit.h>
@import Flutter; // 导入Flutter

@interface AppDelegate : FlutterAppDelegate //UIResponder <UIApplicationDelegate>

//@property (strong, nonatomic) UIWindow *window;
@property (nonatomic, strong) FlutterEngine *flutterEngine;

@end

// AppDelegate.m
#import "AppDelegate.h"
#import <FlutterPluginRegistrant/FlutterPluginRegistrant-umbrella.h> //Used to connect plugins.

@interface AppDelegate ()
@end

@implementation AppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    // Override point for customization after application launch.
    
    self.flutterEngine = [[FlutterEngine alloc] initWithName:@"MyFlutterEngine"];
    // 使用默认的Flutter路由运行默认的Dart入口点。
    // 当在AppDelegate中创建的FlutterEngine上调用run时,默认Dart库的默认main()入口点函数将运行。
    [self.flutterEngine run];
    [GeneratedPluginRegistrant registerWithRegistry:self.flutterEngine];
    
    return [super application:application didFinishLaunchingWithOptions:launchOptions];;
}

5.2 创建FlutterViewController

进入我们原生iOS Demo工程中,打开ViewController.h,写入如下代码,测试我们原生打开FlutterViewController。

#import "ViewController.h"
#import "AppDelegate.h"
@import Flutter;

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    
    UIButton *flutterButton = [[UIButton alloc] initWithFrame:CGRectMake(100, 200, 120, 40)];
    [flutterButton setTitle:@"show flutter" forState:UIControlStateNormal];
    [flutterButton setTitleColor:[UIColor blueColor] forState:UIControlStateNormal];
    [flutterButton addTarget:self action:@selector(flutterButtonAction) forControlEvents:UIControlEventTouchUpInside];
    [self.view addSubview:flutterButton];
    
    [self.navigationController setNavigationBarHidden:YES];
}

- (void)flutterButtonAction {
    // 获取引擎
    FlutterEngine *flutterEngine = [(AppDelegate *)[UIApplication sharedApplication].delegate flutterEngine];
    // 创建FlutterViewController
    FlutterViewController *flutterVC = [[FlutterViewController alloc] initWithEngine:flutterEngine nibName:nil bundle:nil];
    // 跳转
    [self.navigationController pushViewController:flutterVC animated:YES];
    // 当然你也可以使用下面的方法跳转
    //[self presentViewController:flutterVC animated:YES completion:nil];
}

至此,我们就已经可以完成原生集成Flutter,并且完成跳转的工作了,效果如下:


效果图.gif

另外,官方还给了另外一种使用隐式FlutterEngine的方式创建FlutterViewController,这种方式,我们不需要预先创建FlutterEngine,而去按需创建,尤其是我们Flutter界面很少,而且不知道什么时候会启动Flutter界面的时候,会合适些,但是也牺牲了一些,按照官方说的就是在首次显示FlutterUI的时候,会有些延迟,所以官方不是很推荐隐式创建FlutterEngine的方式。具体代码如下:

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    
    UIButton *flutterButton = [[UIButton alloc] initWithFrame:CGRectMake(100, 200, 120, 40)];
    [flutterButton setTitle:@"show flutter" forState:UIControlStateNormal];
    [flutterButton setTitleColor:[UIColor blueColor] forState:UIControlStateNormal];
    [flutterButton addTarget:self action:@selector(flutterButtonAction) forControlEvents:UIControlEventTouchUpInside];
    [self.view addSubview:flutterButton];
    
    UIButton *flutterButton2 = [[UIButton alloc] initWithFrame:CGRectMake(100, 300, 200, 40)];
    [flutterButton2 setTitle:@"隐式创建引擎" forState:UIControlStateNormal];
    [flutterButton2 setTitleColor:[UIColor blueColor] forState:UIControlStateNormal];
    [flutterButton2 addTarget:self action:@selector(createFlutterViewControllerWithImplicitFlutterEngine) forControlEvents:UIControlEventTouchUpInside];
    [self.view addSubview:flutterButton2];
    
    [self.navigationController setNavigationBarHidden:YES];
}

- (void)createFlutterViewControllerWithImplicitFlutterEngine {
    // 创建FlutterViewController
    FlutterViewController *flutterVC = [[FlutterViewController alloc] initWithProject:nil nibName:nil bundle:nil];
    // 跳转
    [self.navigationController pushViewController:flutterVC animated:YES];
}

六、相关内容的一些说明

6.1 FlutterAppDelegate

有的应用可能不能像我们的demo中那样,直接让AppDelegate继承自FlutterAppDelegate,这种方法是官方推荐,好处就是可以监听到诸如点击状态栏回到顶部此类操作。
但是这并不是强制的,我们在不能直接继承的情况下,为了能够让我们的Flutter能够响应一部分的App生命周期事件,我们可以在AppDelegate.h中遵循FlutterAppLifeCycleProvider,并在AppDelegate.m中实现代理,以确保能够监听到相应的事件行为:

// AppDelegate2.h
@import Flutter;
@import UIKit;
@import FlutterPluginRegistrant;

@interface AppDelegate : UIResponder <UIApplicationDelegate, FlutterAppLifeCycleProvider>
@property (strong, nonatomic) UIWindow *window;
@property (nonatomic,strong) FlutterEngine *flutterEngine;
@end

// AppDelegate2.m
#import "AppDelegate2.h"

@interface AppDelegate2 ()

@property (nonatomic, strong) FlutterPluginAppLifeCycleDelegate* lifeCycleDelegate;

@end

@implementation AppDelegate2

- (instancetype)init {
    if (self = [super init]) {
        _lifeCycleDelegate = [[FlutterPluginAppLifeCycleDelegate alloc] init];
    }
    return self;
}

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    // Override point for customization after application launch.
    
    self.flutterEngine = [[FlutterEngine alloc] initWithName:@"MyFlutterEngine"];
    // 使用默认的Flutter路由运行默认的Dart入口点。
    // 当在AppDelegate中创建的FlutterEngine上调用run时,默认Dart库的默认main()入口点函数将运行。
    [self.flutterEngine run];
    [GeneratedPluginRegistrant registerWithRegistry:self.flutterEngine];
    
    return [_lifeCycleDelegate application:application didFinishLaunchingWithOptions:launchOptions];
}

// Returns the key window's rootViewController, if it's a FlutterViewController.
// Otherwise, returns nil.
- (FlutterViewController*)rootFlutterViewController {
    UIViewController* viewController = [UIApplication sharedApplication].keyWindow.rootViewController;
    if ([viewController isKindOfClass:[FlutterViewController class]]) {
        return (FlutterViewController*)viewController;
    }
    return nil;
}

- (void)touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event {
    [super touchesBegan:touches withEvent:event];
    
    // Pass status bar taps to key window Flutter rootViewController.
    if (self.rootFlutterViewController != nil) {
        [self.rootFlutterViewController handleStatusBarTouches:event];
    }
}
//....
// 这里是生命周期代码,具体的可以参考Demo中AppDelegate2.m
@end

6.2 Dart 入口(Dart EntryPoint)

FlutterEngine调用run方法,默认情况下,运行的是lib/main.dart文件中的main()入口,我们也可以运行不同的入口,通过使用runWithEntrypoint,传入一个字符串参数,以使用其他的dart入口(入口在lib/main.dart文件中),但是请注意,除main()之外的Dart入口点函数必须使用以下注释:

// flutter
@pragma('vm:entry-point')
void myOtherEntryPoint() { ... };

// oc
[self.flutterEngine runWithEntrypoint:@"myOtherEntryPoint"];
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,839评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,543评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,116评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,371评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,384评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,111评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,416评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,053评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,558评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,007评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,117评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,756评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,324评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,315评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,539评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,578评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,877评论 2 345