将Flutter Module集成到iOS工程中
2022-07-18 本文已影响0人
handsome丶亮
1. Flutter环境搭建
1.1. 环境
- 时间:2022.7.13
- 电脑:MacBook Pro (13-inch, M1, 2020)
- 系统:macOS Monterey 12.0.1 (21A559)
- Xcode版本:13.4.1 (13F100)
- Android Studio版本:Chipmunk | 2021.2.1 Patch 1
- Flutter插件版本:69.0.2
- Dart插件版本:212.5744
- Flutter SDK版本:3.0.4
- CocoaPods版本:1.11.3
- flutter_boost官方文档
- 默认已安装
Homebrew
和CocoaPods
,如果未安装可以参考:
1.2. 下载Flutter库文件
-
下载链接
下载最新的SDK,目前最新版本为3.0.4 -
将下载好的压缩包解压,并将解压后的flutter文件夹移动到想要安装的目录中
当前安装目录路径为:~/Documents/flutter
1.3. 下载安装Android Studio
- 直接点击Download Android Studio,弹出协议勾选同意,当前环境为M1,所以点击下载Mac with Apple chip,如果不是M系列处理器,则下载Mac with Intel chip。
- 安装好后,打开Android Studio,选择标准模式。
- 选择同意所有协议,点击Finish
- 开始下载相关组件
- 出现错误,经查阅应该是网络问题,点击Retry后成功
- 添加Flutter和Dart插件,Android Studio打开Preferences下载Dart和Flutter插件,如下如所示打开设置:
1.4. 配置环境变量:
- 打开终端,当前终端默认为
zsh
模式 - 在终端输入命令:
vim ~/.zshrc
(如果当前终端模式bash,则为vim ~/.bash_profile
,后续操作也是以~/.bash_profile
为准) - 按【i】进入编辑模式,将以下内容保存添加进去:
export PATH=~/Documents/flutter/bin:$PATH
export PUB_HOSTED_URL=https://pub.flutter-io.cn
export FLUTTER_STORAGE_BASE_URL=https://storage.flutter-io.cn
其中export PATH = 此处为flutter库文件安装路径
/bin:$PATH。
- 添加完以后按
esc
退出编辑模式,再输入:wq
进行保存。 - 然后再运行命令使.zshrc文件生效:
source ~/.zshrc
1.5. 测试Flutter环境
- 在终端运行命令:
flutter doctor
- 如果此时报错:env: bash: No such file or directory。则说明
export PATH
的路径设置不对,检查配置正确后即可。 - 如果配置正确,运行结果如下:
1.6. 解决flutter doctor(flutter运行环境检测)的问题
- 按提示的执行命令
flutter doctor --android-licenses
,出现报错 解决方法:在Android Studio的设置中勾选下图中的Android SDK Command-line Tools
,并应用 -
再次执行命令成功
一直选y即可
- 再次执行flutter doctor,全部正常。至此Flutter环境安装成功。
2. 创建Flutter模块项目
2.1. Flutter模块项目创建
- cd到指定目录,用命令创建项目:
flutter create -t module cmcc_test_module
注意:flutter项目中的所有文件命名包括工程名,默认不使用驼峰命名法(有些地方如果用驼峰会直接被禁止创建),一般全部小写,以下划线分割。 - 将项目文件夹拖动到Android Studio上,即可打开项目。打开项目后,引入相关依赖后,点击Pub get,其中ref一定要使用空安全版本。具体版本可以到:https://github.com/alibaba/flutter_boost进行查看
flutter_boost:
git:
url: 'https://github.com/alibaba/flutter_boost.git'
ref: 'v3.0-null-safety-release.2.1'
- 在工程的插件中,已经可以看到flutter boost了。
2.2. Flutter模块项目代码
- 新建home.dart,用于纯跳转
import 'package:flutter/material.dart';
/// flutter首页
class CMHomePage extends StatelessWidget {
const CMHomePage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: TextButton(
onPressed: () => _pushNativeViewController(),
child: const Text("首页跳转原生界面")
),
),
);
}
// 跳转原生界面
_pushNativeViewController() {
BoostNavigator.instance.push("TestViewController");
}
}
- 新建mine.dart,用于传参跳转
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
/// flutter我的
class CMMinePage extends StatefulWidget {
final String data;
const CMMinePage({Key? key, required this.data}) : super(key: key);
@override
State<CMMinePage> createState() => _CMMinePageState();
}
class _CMMinePageState extends State<CMMinePage> {
MethodChannel eventChannel = const MethodChannel('com.flutterToNative.test');
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Text("flutter我的,传输数据为:${widget.data}")
),
);
}
}
- 在main.dart中配置路由表
import 'package:cmcc_test_module/home.dart';
import 'package:flutter/material.dart';
import 'package:flutter_boost/flutter_boost.dart';
import 'mine.dart';
import 'home.dart';
// BoostFlutterBinding用于接管Flutter App的生命周期,必须得接入的
class CustomFlutterBinding extends WidgetsFlutterBinding with BoostFlutterBinding {
}
void main() {
CustomFlutterBinding();
runApp(const MyApp());
}
class MyApp extends StatefulWidget {
const MyApp({Key? key}) : super(key: key);
@override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
///路由表
static Map<String, FlutterBoostRouteFactory> routerMap = {
'homePage': (settings, uniqueId) {
return PageRouteBuilder<dynamic>(
settings: settings,
pageBuilder: (_, __, ___) {
return const CMHomePage();
});
},
'minePage': (settings, uniqueId) {
return PageRouteBuilder<dynamic>(
settings: settings,
pageBuilder: (_, __, ___) {
Map<String, dynamic>? map = settings.arguments as Map<String, dynamic>?;
String data = map?['data'] as String ?? "";
return CMMinePage(
data: data,
);
});
},
};
Route<dynamic>? routeFactory(RouteSettings settings, String? uniqueId) {
FlutterBoostRouteFactory? func = routerMap[settings.name];
if (func == null) {
return null;
}
return func(settings, uniqueId);
}
Widget appBuilder(Widget home) {
return MaterialApp(home: home, debugShowCheckedModeBanner: false);
}
@override
Widget build(BuildContext context) {
return FlutterBoostApp(
routeFactory,
appBuilder: appBuilder,
);
}
}
- 至此Flutter工程代码编写完毕。
3. 集成Flutter工程到iOS工程中
- 为了方便调试,先用新建的iOSDemo工程进行测试,这样方便排查问题,减少不必要的影响因素。把Flutter Module项目文件夹拖动到新建的iOSDemo工程的根目录中
- 配置Podfile文件,并在终端执行
pod install
platform:ios,'9.0'
flutter_application_path = './FlutterModule/cmcc_test_module'
load File.join(flutter_application_path, '.ios', 'Flutter', 'podhelper.rb')
target 'FlutterTest' do
install_all_flutter_pods(flutter_application_path)
end
- pod install执行成功后,打开工程目录,在pod中发现flutter_boost以及module已导入
- 在iOSDemo工程中新建:
BoostDelegate
类
BoostDelegate.h
//
// BoostDelegate.h
// FlutterTest
//
// Created by mac on 2022/7/13.
//
// 原生界面跳转Flutter界面代理
#import <Foundation/Foundation.h>
#import <flutter_boost/FlutterBoost.h>
NS_ASSUME_NONNULL_BEGIN
@interface BoostDelegate : NSObject<FlutterBoostDelegate>
// 设置导航控制器(外部必须设置,否则会导致无法跳转)
@property (nonatomic, strong) UINavigationController *navigationController;
// 创建单例
+ (instancetype)sharedInstance;
@end
NS_ASSUME_NONNULL_END
BoostDelegate.m
//
// BoostDelegate.m
// FlutterTest
//
// Created by mac on 2022/7/13.
//
#import "BoostDelegate.h"
@implementation BoostDelegate
// 创建单例
+ (instancetype)sharedInstance{
static BoostDelegate *myInstance = nil;
if(myInstance == nil){
myInstance = [[BoostDelegate alloc] init];
}
return myInstance;
}
// 跳转原生界面api,Flutter界面跳转原生界面,会自动调用此方法
-(void)pushNativeRoute:(NSString *)pageName arguments:(NSDictionary *)arguments {
// 是否有动画
BOOL animated = [arguments[@"animated"] boolValue];
// 弹出方式
BOOL present = [arguments[@"present"] boolValue];
// 这里根据pageName来判断生成哪个vc
UIViewController *vc;
if ([pageName isEqualToString:@"TestViewController"]) {
vc = [TestViewController new];
}else{
// 没有找到,则创建一个控制器作为容错
vc = [UIViewController new];
}
if (present) {
[self.navigationController presentViewController:vc animated:animated completion:^{
}];
} else {
[self.navigationController pushViewController:vc animated:YES];
}
}
// 当框架的withContainer为true的时候,会调用此方法来做原生的push
-(void)pushFlutterRoute:(FlutterBoostRouteOptions *)options {
// 创建Flutter控制器
FBFlutterViewContainer *vc = FBFlutterViewContainer.new;
// 设置页面名称,参数等信息
[vc setName:options.pageName uniqueId:options.uniqueId params:options.arguments opaque:options.opaque];
// 是否有动画
BOOL animated = [options.arguments[@"animated"] boolValue];
// 弹出方式
BOOL present = [options.arguments[@"present"] boolValue] || !options.opaque;
if (present) {
[self.navigationController presentViewController:vc animated:animated completion:^{
options.completion(YES);
}];
} else {
[self.navigationController pushViewController:vc animated:animated];
options.completion(YES);
}
}
// 当pop调用涉及到原生容器的时候,此方法将会被调用
-(void)popRoute:(FlutterBoostRouteOptions *)options {
// 取出控制器
FBFlutterViewContainer *vc = (id)self.navigationController.presentedViewController;
// 判断控制器是否为Flutter控制器
if ([vc isKindOfClass:FBFlutterViewContainer.class] && [vc.uniqueIDString isEqual:options.uniqueId]) {
if (vc.modalPresentationStyle == UIModalPresentationFullScreen) {
[self.navigationController.topViewController beginAppearanceTransition:YES animated:NO];
[vc dismissViewControllerAnimated:YES completion:^{
[self.navigationController.topViewController endAppearanceTransition];
}];
} else {
[vc dismissViewControllerAnimated:YES completion:^{
}];
}
} else {
[self.navigationController popViewControllerAnimated:YES];
}
}
@end
- 在
appDelegate
中注册FlutterBoost
AppDelegate.m
//
// AppDelegate.m
// FlutterTest
//
// Created by mac on 2022/7/13.
//
#import "AppDelegate.h"
#import <FlutterPluginRegistrant/FlutterPluginRegistrant-umbrella.h>
#import "BoostDelegate.h"
#import <flutter_boost/FlutterBoost.h>
#import "ViewController.h"
@interface AppDelegate ()
@end
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// 初始化window
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
ViewController *vc = [ViewController new];
UINavigationController *navigation = [[UINavigationController alloc] initWithRootViewController:vc];
self.window.rootViewController = navigation;
[self.window makeKeyAndVisible];
self.window.backgroundColor = [UIColor whiteColor];
// 初始化FlutterBoost
BoostDelegate *delegate = [BoostDelegate sharedInstance];
delegate.navigationController = navigation;
[[FlutterBoost instance] setup:application delegate:delegate callback:^(FlutterEngine *engine) {
}];
return YES;
}
@end
- 在根控制器ViewController中进行跳转
ViewController.m
//
// ViewController.m
// FlutterTest
//
// Created by mac on 2022/7/13.
//
#import "ViewController.h"
#import <flutter_boost/FlutterBoost.h>
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor whiteColor];
UIButton *pushHomePageButton = [UIButton buttonWithType:UIButtonTypeSystem];
pushHomePageButton.frame = CGRectMake(100, 200, 200, 100);
[pushHomePageButton setTitleColor:[UIColor redColor] forState:UIControlStateNormal];
[pushHomePageButton setTitle:@"跳转到Flutter首页" forState:UIControlStateNormal];
[pushHomePageButton addTarget:self action:@selector(pushFlutterHomePage) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:pushHomePageButton];
UIButton *pushMinePageButton = [UIButton buttonWithType:UIButtonTypeSystem];
pushMinePageButton.frame = CGRectMake(100, 100, 200, 100);
[pushMinePageButton setTitleColor:[UIColor blueColor] forState:UIControlStateNormal];
[pushMinePageButton setTitle:@"跳转到Flutter我的" forState:UIControlStateNormal];
[pushMinePageButton addTarget:self action:@selector(pushFlutterMinePage) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:pushMinePageButton];
}
// 跳转Flutter首页
- (void)pushFlutterHomePage {
FlutterBoostRouteOptions *options = [FlutterBoostRouteOptions new];
// 此处填写的页面名称,需要在Flutter Module项目中main的路由表中有对应的路由名称,否则会导致匹配不上跳转失败
options.pageName = @"homePage";
options.arguments = @{@"animated": @(YES)};
options.completion = ^(BOOL completion) {
};
[[FlutterBoost instance] open:options];
options.onPageFinished = ^(NSDictionary *dic) {
NSLog(@"%@", dic);
};
}
// 跳转Flutter我的
- (void)pushFlutterMinePage {
FlutterBoostRouteOptions *options = [FlutterBoostRouteOptions new];
options.pageName = @"minePage";
options.arguments = @{@"animated": @(YES), @"data": @"原生界面参数传递"};
options.completion = ^(BOOL completion) {
};
[[FlutterBoost instance] open:options];
options.onPageFinished = ^(NSDictionary *dic) {
NSLog(@"%@", dic);
};
}
@end
- 新建测试页面,用于Flutter界面跳转原生界面。
TestViewController.m
//
// TestViewController.m
// FlutterTest
//
// Created by mac on 2022/7/18.
//
#import "TestViewController.h"
@interface TestViewController ()
@end
@implementation TestViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor redColor];
}
@end
- 运行项目,跳转成功。 Flutter界面跳转到原生界面