往期文章
寻找IOS相册中相似图片
NSNotification与类对象,实例对象
iCloud-Documents存储
CocoaPods私有源搭建
Swarm区块链分布式存储使用
MacOS流编辑器sed
创建Flutter模块
生成模板工程
首先切换到一个存放Flutter模块的目录。在使用命令flutter create --template module moduleName创建一个模板工程
cd some/path/
flutter create --template module moduleName
moduleName是我们生成模块的名称。
命令执行完毕成功创建了一个my_flutter的模板工程
模板工程结构
模板工程创建完毕之后,我们来看看模板工程的结构。
可以看到flutter模块目录与flutter工程目录结构差不多相同,各个目录的作用如下
| 目录 | 作用 |
|---|---|
| .android | 存放安卓工程目录(用于单独运行flutter模块壳工程) |
| .ios | 存放ios工程目录(用于单独运行flutter模块壳工程) |
| lib | 存放flutter源代码 |
| pubspec.yaml | flutter配置文件 |
.ios目录和ios目录
从模板结构中我们可以看到,没有android 和ios目录。只有.ios和.android目录。那.ios目录和ios目录的区别是什么呢?
.ios目录结构
ios目录结构
在flutter工程中,ios目录存放的是flutter工程根据ios平台编译的产物,用于发布flutter应用(ios端)或者使用xcode中的特定功能(例如:开启推送,icloud容器,定位,权限等等问题)
在flutter模块工程中,.ios目录是用于单独运行flutter模块的壳工程,比方说你的flutter模块更新的功能,但你又不想重新构建整个原生工程,那么你就可以使用这个壳工程来运行测试你的flutter模块。
此外.ios目录中有App.framework,这里面存放着我们flutter模块中的dart源代码编译后的产物
FlutterPluginRegistrant目录用于,存放我们的flutter第三方插件
还有engin目录,用于在ios环境中运行我们的flutter模块。
以及flutter模块安装帮组脚本podhelper.rb
注意 ios平台的代码是写在我们的ios原生工程中,而不是.ios目录下的flutter模块的壳工程中。在.ios目录下的flutter模块壳工程,有可能被flutter重写,并且不会集成到你现有的xcode工程中,不要放ios代码,不要放ios代码,不要放ios代码
安装Flutter模块到Xcode工程中
CocoaPod安装
首先创建一个Podfile
cd path
vim Podfile
配置Podfile内容
flutter_application_path = '/xxxxxxx/my_flutter'
load File.join(flutter_application_path, '.ios', 'Flutter', 'podhelper.rb')
platform :ios, '9.1'
target 'myFlutterModule' do
pod 'Masonry'
install_all_flutter_pods(flutter_application_path)
end
flutter_application_path 代表你flutter模块工程的目录路径 找到flutter模块工程之后,找到.ios目录,并在.ios目录中找到Flutter目录下的安装脚本podhelper.rb
flutter模块安装脚本流程如下图所示
通过查看podhelper脚本代码我们可以看到它主要打包了.ios中的三个目录
def install_flutter_engine_pod
current_directory = File.expand_path('..', __FILE__)
engine_dir = File.expand_path('engine', current_directory)
framework_name = 'Flutter.xcframework'
copied_engine = File.expand_path(framework_name, engine_dir)
if !File.exist?(copied_engine)
# Copy the debug engine to have something to link against if the xcode backend script has not run yet.
# CocoaPods will not embed the framework on pod install (before any build phases can generate) if the dylib does not exist.
release_framework_dir = File.join(flutter_root, 'bin', 'cache', 'artifacts', 'engine', 'ios-release')
unless Dir.exist?(release_framework_dir)
# iOS artifacts have not been downloaded.
raise "#{release_framework_dir} must exist. Make sure \"flutter build ios\" has been run at least once"
end
FileUtils.cp_r(File.join(release_framework_dir, framework_name), engine_dir)
end
# Keep pod path relative so it can be checked into Podfile.lock.
# Process will be run from project directory.
engine_pathname = Pathname.new engine_dir
# defined_in_file is set by CocoaPods and is a Pathname to the Podfile.
project_directory_pathname = defined_in_file.dirname
relative = engine_pathname.relative_path_from project_directory_pathname
pod 'Flutter', :path => relative.to_s, :inhibit_warnings => true
end
此方法用于打包上面我们提到的engine目录中的Flutter.xcframework,这个就是我们flutter运行的引擎
def install_flutter_plugin_pods(flutter_application_path)
flutter_application_path ||= File.join('..', '..')
# Keep pod path relative so it can be checked into Podfile.lock.
# Process will be run from project directory.
ios_project_directory_pathname = Pathname.new File.expand_path(File.join('..', '..'), __FILE__)
# defined_in_file is set by CocoaPods and is a Pathname to the Podfile.
project_directory_pathname = defined_in_file.dirname
relative = ios_project_directory_pathname.relative_path_from project_directory_pathname
pod 'FlutterPluginRegistrant', :path => File.join(relative, 'Flutter', 'FlutterPluginRegistrant'), :inhibit_warnings => true
symlinks_dir = File.join(relative, '.symlinks', 'plugins')
FileUtils.mkdir_p(symlinks_dir)
plugins_file = File.expand_path('.flutter-plugins-dependencies', flutter_application_path)
plugin_pods = flutter_parse_dependencies_file_for_ios_plugin(plugins_file)
plugin_pods.each do |plugin_hash|
plugin_name = plugin_hash['name']
plugin_path = plugin_hash['path']
if (plugin_name && plugin_path)
symlink = File.join(symlinks_dir, plugin_name)
FileUtils.rm_f(symlink)
File.symlink(plugin_path, symlink)
pod plugin_name, :path => File.join(symlink, 'ios'), :inhibit_warnings => true
end
end
end
此方法用于打包FlutterPluginRegistrant目录,这里面存放了我们在flutter模块中使用到的第三方插件
def install_flutter_application_pod(flutter_application_path)
current_directory_pathname = Pathname.new File.expand_path('..', __FILE__)
app_framework_dir = File.expand_path('App.framework', current_directory_pathname.to_path)
app_framework_dylib = File.join(app_framework_dir, 'App')
if !File.exist?(app_framework_dylib)
# Fake an App.framework to have something to link against if the xcode backend script has not run yet.
# CocoaPods will not embed the framework on pod install (before any build phases can run) if the dylib does not exist.
# Create a dummy dylib.
FileUtils.mkdir_p(app_framework_dir)
`echo "static const int Moo = 88;" | xcrun clang -x c -dynamiclib -o "#{app_framework_dylib}" -`
end
# Keep pod and script phase paths relative so they can be checked into source control.
# Process will be run from project directory.
# defined_in_file is set by CocoaPods and is a Pathname to the Podfile.
project_directory_pathname = defined_in_file.dirname
relative = current_directory_pathname.relative_path_from project_directory_pathname
pod 'my_flutter', :path => relative.to_s, :inhibit_warnings => true
flutter_export_environment_path = File.join('${SRCROOT}', relative, 'flutter_export_environment.sh');
script_phase :name => 'Run Flutter Build my_flutter Script',
:script => "set -e\nset -u\nsource \"#{flutter_export_environment_path}\"\nexport VERBOSE_SCRIPT_LOGGING=1 && \"$FLUTTER_ROOT\"/packages/flutter_tools/bin/xcode_backend.sh build",
:execution_position => :before_compile
end
此方法用于打包flutter模块源代码编译后的产物
然后运行Pod install 将flutter模块添加进我们的项目中。
这里需要注意的是,一个Pod的target对应一个install_all_flutter_pods。本例子中我们只在myFlutterModule这个target中导入了install_all_flutter_pods,如果有其他target需要使用flutter模块,需要在该target下导入install_all_flutter_pods。
Pod intall执行完毕之后,我们看到成功安装了三个模块
在CocoaPod生成的工程中,我们可以到这三个模块分别为flutter模块源码编译产物,以及flutter运行引擎和flutter模块使用的插件
在 Xcode 中打开我们的项目,现在可以使用 ⌘B 编译项目了。
注意
当我们在pubspec.yaml修改了flutter模块依赖的插件,我们就需要在flutter模块的目录中使用flutter pub get,来更新会被podhelper.rb 脚本用到的 plugin 列表,然后再次在我们的Xcode工程中使用Pod install
此外对于flutter模块lib源代码文件中的修改,会在每次Xcode构造IOS工程的时候,通过脚本自动更新。因此我们不需要在使用Pod install来重新安装Pod,在flutter模块中直接修改模块样式或功能,然后直接在Xcode中构建项目。
在IOS工程调用flutter页面
AppDelegate设置
这里推荐直接让我们的AppDelegate继承FlutterAppDelegate,或者你也可以选择不继承,如果不继承的话,你就需要在AppDelegate中实现FlutterAppLifeCycleProvider 协议,来确保 Flutter plugins 接收到必要的回调
为了方便起见,这里我选择的是让AppDelegate继承FlutterAppDelegate
AppDelegate.h文件
@import UIKit;
@import Flutter;
@interface AppDelegate : FlutterAppDelegate
@property (nonatomic,strong) FlutterEngine *flutterEngine;
@end
AppDelegate.m文件
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
self.flutterEngine = [[FlutterEngine alloc] initWithName:@"coderjun flutter engine"];
// Runs the default Dart entrypoint with a default Flutter route.
[self.flutterEngine run];
// Used to connect plugins (only if you have plugins with iOS platform code).
[GeneratedPluginRegistrant registerWithRegistry:self.flutterEngine];
return [super application:application didFinishLaunchingWithOptions:launchOptions];;
}
现在我已经在AppDelegate中创建了我们自己的flutter引擎。
接下来我们就可以通过这个flutter引擎在ios环境中来运行我们的flutter模块了
使用FlutterEngin展示FlutterViewController
这里我们简单的创建一个ViewController,然后让这个ViewController切换到FlutterViewController
- (void)viewDidLoad {
UIButton* btn = [UIButton buttonWithType:UIButtonTypeContactAdd];
btn.center = self.view.center;
[btn addTarget:self action:@selector(btnClick:) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:btn];
[super viewDidLoad];
// Do any additional setup after loading the view.
}
- (void) btnClick:(UIButton*) sender{
NSLog(@"tag is %ld",(long)index);
FlutterEngine *flutterEngine =
((AppDelegate *)UIApplication.sharedApplication.delegate).flutterEngine;
FlutterViewController *flutterViewController =
[[FlutterViewController alloc] initWithEngine:flutterEngine nibName:nil bundle:nil];
[self presentViewController:flutterViewController animated:YES completion:nil];
}
当我们点击按钮,ViewController通过模态视图的方式,将我们的FlutterViewControler推到了当前窗口之上。效果如下
结尾
如果文章有什么地方写错了或者有问题,欢迎在评论区留言,我会及时回复。
我的GitHub主页GitHub
会不定期做一些开源项目供大家交流学习