概述
本文不想写一个全篇步骤式的文章来描写怎么集成flutter,而是期望用一种探索的方式来追寻答案。
原理分析
我们首先看下flutter项目和一般原生项目的大概区别。
为了跳转方便,原生项目的入口一般是UINavigationController
。
而我们看下flutter默认给我们创建的模板为:
这里我们来看下flutter的引擎源码,看下这段代码做了什么工作,源码路径为:https://github.com/flutter/en...
我们首先看下`FlutterAppDelegate
https://github.com/flutter/en...
- (instancetype)init {
if (self = [super init]) {
_lifeCycleDelegate = [[FlutterPluginAppLifeCycleDelegate alloc] init];
}
return self;
}
....
- (BOOL)application:(UIApplication*)application
willFinishLaunchingWithOptions:(NSDictionary*)launchOptions {
return [_lifeCycleDelegate application:application willFinishLaunchingWithOptions:launchOptions];
}
....
所以这里可以看到,FlutterAppDelegate
完全是调用了FlutterPluginAppLifeCycleDelegate
的所有方法。假设你的项目原先就有一个AppDelegate的实现类,那么可以参考FlutterAppDelegate
的源码,创建一个FlutterPluginAppLifeCycleDelegate
,并在所有方法中调用这个类实例的方法。
原生项目中创建根ViewControler的方式可以使用StoryBoard,也可以使用代码创建。而flutter模板给我们创建的项目为StoryBoard的方式
从这里我们可以发现,flutter默认项目模板是将FlutterViewController作为根ViewController。
项目实战
创建项目
原理分析完毕,我们可以创建一个工程项目了.
我们这里选择创建一个最常见的SingleViewApp
改成不使用StoryBoard,而是代码创建根ViewController
为了演示方便,我们创建一个controller
修改一下启动代码:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.
self.window = [[UIWindow alloc]initWithFrame:[UIScreen mainScreen].bounds];
UIViewController* main = [[MainViewController alloc]initWithNibName:@"MainViewController" bundle:nil];
UINavigationController* root = [[UINavigationController alloc]initWithRootViewController:main];
self.window.backgroundColor = [UIColor whiteColor];
self.window.rootViewController = root;
[self.window makeKeyAndVisible];
return YES;
}
在MainViewController中,我们摆上两个按钮:
创建flutter模块
我们使用flutter自带命令创建一个flutter模块项目
flutter create -t module my_flutter
把创建出来的所有文件一起拷贝到上面ios原生项目的同一级目录中:
使用pod初始化一下项目:
cd myproject
pod init
这样就生成了Podfile
我们打开修改一下,以便将flutter包括在里面
platform :ios, '9.0'
target 'myproject' do
end
#新添加的代码
flutter_application_path = '../'
eval(File.read(File.join(flutter_application_path, '.ios', 'Flutter', 'podhelper.rb')), binding)
运行下pod安装
pod install
我们可以看到,与刚才相比,新增加了workspace文件,我们关掉原来的项目,并打开workspace
然后我们可以看到项目结构如下:
编译一下:
ld: '/Users/jzoom/SourceCode/myproject/myproject/DerivedData/myproject/Build/Products/Debug-iphoneos/FlutterPluginRegistrant/libFlutterPluginRegistrant.a(GeneratedPluginRegistrant.o)' does not contain bitcode. You must rebuild it with bitcode enabled (Xcode setting ENABLE_BITCODE), obtain an updated library from the vendor, or disable bitcode for this target. file '/Users/jzoom/SourceCode/myproject/myproject/DerivedData/myproject/Build/Products/Debug-iphoneos/FlutterPluginRegistrant/libFlutterPluginRegistrant.a' for architecture arm64
clang: error: linker command failed with exit code 1 (use -v to see invocation)
出现了这个错误
打开项目编译配置,并搜索bit,出现下面结果:
修改下Enable Bitcode为No
此时编译ok。
至此,在原生项目中配置flutter完毕,我们开始开发功能。
修改AppDelegate
由于我们的AppDelegate不是FlutterAppDelegate,所以我们按照前面分析的路子,改成如下:
//
// AppDelegate.m
// myproject
//
// Created by JZoom on 2019/4/9.
// Copyright © 2019 JZoom. All rights reserved.
//
#import "AppDelegate.h"
#import "GeneratedPluginRegistrant.h"
#import <Flutter/Flutter.h>
#import "MainViewController.h"
@interface AppDelegate()<FlutterPluginRegistry>
@end
@implementation AppDelegate{
FlutterPluginAppLifeCycleDelegate* _lifeCycleDelegate;
}
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.
self.window = [[UIWindow alloc]initWithFrame:[UIScreen mainScreen].bounds];
UIViewController* main = [[MainViewController alloc]initWithNibName:@"MainViewController" bundle:nil];
UINavigationController* root = [[UINavigationController alloc]initWithRootViewController:main];
self.window.backgroundColor = [UIColor whiteColor];
self.window.rootViewController = root;
[self.window makeKeyAndVisible];
[GeneratedPluginRegistrant registerWithRegistry:self];
return [_lifeCycleDelegate application:application didFinishLaunchingWithOptions:launchOptions];
}
- (instancetype)init {
if (self = [super init]) {
_lifeCycleDelegate = [[FlutterPluginAppLifeCycleDelegate alloc] init];
}
return self;
}
- (void)dealloc {
_lifeCycleDelegate = nil;
}
- (BOOL)application:(UIApplication*)application
willFinishLaunchingWithOptions:(NSDictionary*)launchOptions {
return [_lifeCycleDelegate application:application willFinishLaunchingWithOptions: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];
}
}
- (void)applicationDidEnterBackground:(UIApplication*)application {
[_lifeCycleDelegate applicationDidEnterBackground:application];
}
- (void)applicationWillEnterForeground:(UIApplication*)application {
[_lifeCycleDelegate applicationWillEnterForeground:application];
}
- (void)applicationWillResignActive:(UIApplication*)application {
[_lifeCycleDelegate applicationWillResignActive:application];
}
- (void)applicationDidBecomeActive:(UIApplication*)application {
[_lifeCycleDelegate applicationDidBecomeActive:application];
}
- (void)applicationWillTerminate:(UIApplication*)application {
[_lifeCycleDelegate applicationWillTerminate:application];
}
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
- (void)application:(UIApplication*)application
didRegisterUserNotificationSettings:(UIUserNotificationSettings*)notificationSettings {
[_lifeCycleDelegate application:application
didRegisterUserNotificationSettings:notificationSettings];
}
#pragma GCC diagnostic pop
- (void)application:(UIApplication*)application
didRegisterForRemoteNotificationsWithDeviceToken:(NSData*)deviceToken {
[_lifeCycleDelegate application:application
didRegisterForRemoteNotificationsWithDeviceToken:deviceToken];
}
- (void)application:(UIApplication*)application
didReceiveRemoteNotification:(NSDictionary*)userInfo
fetchCompletionHandler:(void (^)(UIBackgroundFetchResult result))completionHandler {
[_lifeCycleDelegate application:application
didReceiveRemoteNotification:userInfo
fetchCompletionHandler:completionHandler];
}
- (void)application:(UIApplication*)application
didReceiveLocalNotification:(UILocalNotification*)notification {
[_lifeCycleDelegate application:application didReceiveLocalNotification:notification];
}
- (void)userNotificationCenter:(UNUserNotificationCenter*)center
willPresentNotification:(UNNotification*)notification
withCompletionHandler:
(void (^)(UNNotificationPresentationOptions options))completionHandler
API_AVAILABLE(ios(10)) {
if (@available(iOS 10.0, *)) {
[_lifeCycleDelegate userNotificationCenter:center
willPresentNotification:notification
withCompletionHandler:completionHandler];
}
}
- (BOOL)application:(UIApplication*)application
openURL:(NSURL*)url
options:(NSDictionary<UIApplicationOpenURLOptionsKey, id>*)options {
return [_lifeCycleDelegate application:application openURL:url options:options];
}
- (BOOL)application:(UIApplication*)application handleOpenURL:(NSURL*)url {
return [_lifeCycleDelegate application:application handleOpenURL:url];
}
- (BOOL)application:(UIApplication*)application
openURL:(NSURL*)url
sourceApplication:(NSString*)sourceApplication
annotation:(id)annotation {
return [_lifeCycleDelegate application:application
openURL:url
sourceApplication:sourceApplication
annotation:annotation];
}
- (void)application:(UIApplication*)application
performActionForShortcutItem:(UIApplicationShortcutItem*)shortcutItem
completionHandler:(void (^)(BOOL succeeded))completionHandler NS_AVAILABLE_IOS(9_0) {
[_lifeCycleDelegate application:application
performActionForShortcutItem:shortcutItem
completionHandler:completionHandler];
}
- (void)application:(UIApplication*)application
handleEventsForBackgroundURLSession:(nonnull NSString*)identifier
completionHandler:(nonnull void (^)())completionHandler {
[_lifeCycleDelegate application:application
handleEventsForBackgroundURLSession:identifier
completionHandler:completionHandler];
}
- (void)application:(UIApplication*)application
performFetchWithCompletionHandler:(void (^)(UIBackgroundFetchResult result))completionHandler {
[_lifeCycleDelegate application:application performFetchWithCompletionHandler:completionHandler];
}
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 120000
- (BOOL)application:(UIApplication*)application
continueUserActivity:(NSUserActivity*)userActivity
restorationHandler:(void (^)(NSArray<id<UIUserActivityRestoring>>* __nullable
restorableObjects))restorationHandler {
#else
- (BOOL)application:(UIApplication*)application
continueUserActivity:(NSUserActivity*)userActivity
restorationHandler:(void (^)(NSArray* __nullable restorableObjects))restorationHandler {
#endif
return [_lifeCycleDelegate application:application
continueUserActivity:userActivity
restorationHandler:restorationHandler];
}
#pragma mark - FlutterPluginRegistry methods. All delegating to the rootViewController
- (NSObject<FlutterPluginRegistrar>*)registrarForPlugin:(NSString*)pluginKey {
UIViewController* rootViewController = _window.rootViewController;
if ([rootViewController isKindOfClass:[FlutterViewController class]]) {
return
[[(FlutterViewController*)rootViewController pluginRegistry] registrarForPlugin:pluginKey];
}
return nil;
}
- (BOOL)hasPlugin:(NSString*)pluginKey {
UIViewController* rootViewController = _window.rootViewController;
if ([rootViewController isKindOfClass:[FlutterViewController class]]) {
return [[(FlutterViewController*)rootViewController pluginRegistry] hasPlugin:pluginKey];
}
return false;
}
- (NSObject*)valuePublishedByPlugin:(NSString*)pluginKey {
UIViewController* rootViewController = _window.rootViewController;
if ([rootViewController isKindOfClass:[FlutterViewController class]]) {
return [[(FlutterViewController*)rootViewController pluginRegistry]
valuePublishedByPlugin:pluginKey];
}
return nil;
}
#pragma mark - FlutterAppLifeCycleProvider methods
- (void)addApplicationLifeCycleDelegate:(NSObject<FlutterPlugin>*)delegate {
[_lifeCycleDelegate addDelegate:delegate];
}
@end
创建FlutterViewController
编辑下MainViewController
- (IBAction)launchFlutter1:(id)sender {
FlutterViewController* c = [[FlutterViewController alloc]init];
[self.navigationController pushViewController:c animated:YES];
}
编译下,运行点击按钮调取flutter视图,发现一片空白,并出现如下错误:
2019-04-09 13:18:18.500285+0800 myproject[57815:1968395] [VERBOSE-1:callback_cache.cc(132)] Could not parse callback cache, aborting restore
2019-04-09 13:18:36.554643+0800 myproject[57815:1968395] Failed to find assets path for "Frameworks/App.framework/flutter_assets"
2019-04-09 13:18:36.658247+0800 myproject[57815:1969776] [VERBOSE-2:engine.cc(116)] Engine run configuration was invalid.
2019-04-09 13:18:36.659545+0800 myproject[57815:1969776] [VERBOSE-2:FlutterEngine.mm(294)] Could not launch engine with configuration.
2019-04-09 13:18:36.816199+0800 myproject[57815:1969793] flutter: Observatory listening on http://127.0.0.1:50167/
我们看看和flutter自己创建的项目比,还差了什么
如图:有三个地方,我们把这些文件copy一份放到我们的项目中,并且设置一下编译选项:
修改下项目的配置,增加一个脚本
/bin/sh "$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh" thin
放到Copy Bundle Resources下面
结果:
进行优化
在上面的步骤里面,我们通过直接文件拷贝将.ios目录下的flutter生成文件拷贝到了原生项目里面,显然我们不能每一次都手动这么做,我们可以添加一个命令来做这件事。
rm -rf ${SOURCE_ROOT}/Flutter/Generated.xcconfig
cp -ri ../.ios/Flutter/Generated.xcconfig ${SOURCE_ROOT}/Flutter/Generated.xcconfig
rm -rf ${SOURCE_ROOT}/Flutter/App.framework
cp -ri ../.ios/Flutter/App.framework ${SOURCE_ROOT}/Flutter/App.framework
我们把这个命令放到前面去
问题
Q : 如何调用flutter的不同页面?
A : 我们首先定义一下路由
然后我们可以这么调用
/// flutter的路由视图
FlutterViewController* c = [[FlutterViewController alloc]init];
[c setInitialRoute:@"page2"];
[self.navigationController pushViewController:c animated:YES];
Q : 如何在原生项目中调试flutter?
A : 首先在命令行启动flutter的监听
flutter attach
如果有多台设备,需要选择一下设备
flutter attach -d 设备标志
然后就可以在xcode中启动调试运行项目
改动代码之后按下键盘上面的r键就可以了。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。