概述本文不想写一个全篇步骤式的文章来描写怎么集成flutter,而是期望用一种探索的方式来追寻答案。原理分析我们首先看下flutter项目和一般原生项目的大概区别。为了跳转方便,原生项目的入口一般是UINavigationController。而我们看下flutter默认给我们创建的模板为:这里我们来看下flutter的引擎源码,看下这段代码做了什么工作,源码路径为:https://github.com/flutter/en…我们首先看下`FlutterAppDelegatehttps://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 myprojectpod init这样就生成了Podfile我们打开修改一下,以便将flutter包括在里面platform :ios, ‘9.0’target ‘myproject’ doend#新添加的代码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 arm64clang: 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*)applicationwillFinishLaunchingWithOptions:(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*)applicationdidRegisterUserNotificationSettings:(UIUserNotificationSettings*)notificationSettings { [_lifeCycleDelegate application:applicationdidRegisterUserNotificationSettings:notificationSettings];}#pragma GCC diagnostic pop- (void)application:(UIApplication*)applicationdidRegisterForRemoteNotificationsWithDeviceToken:(NSData*)deviceToken { [_lifeCycleDelegate application:applicationdidRegisterForRemoteNotificationsWithDeviceToken:deviceToken];}- (void)application:(UIApplication*)applicationdidReceiveRemoteNotification:(NSDictionary*)userInfofetchCompletionHandler:(void (^)(UIBackgroundFetchResult result))completionHandler { [_lifeCycleDelegate application:application didReceiveRemoteNotification:userInfo fetchCompletionHandler:completionHandler];}- (void)application:(UIApplication*)applicationdidReceiveLocalNotification:(UILocalNotification*)notification { [_lifeCycleDelegate application:application didReceiveLocalNotification:notification];}- (void)userNotificationCenter:(UNUserNotificationCenter*)center willPresentNotification:(UNNotification*)notification withCompletionHandler:(void (^)(UNNotificationPresentationOptions options))completionHandlerAPI_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*)applicationperformActionForShortcutItem:(UIApplicationShortcutItem*)shortcutItem completionHandler:(void (^)(BOOL succeeded))completionHandler NS_AVAILABLE_IOS(9_0) { [_lifeCycleDelegate application:application performActionForShortcutItem:shortcutItem completionHandler:completionHandler];}- (void)application:(UIApplication*)applicationhandleEventsForBackgroundURLSession:(nonnull NSString*)identifier completionHandler:(nonnull void (^)())completionHandler { [_lifeCycleDelegate application:applicationhandleEventsForBackgroundURLSession:identifier completionHandler:completionHandler];}- (void)application:(UIApplication*)applicationperformFetchWithCompletionHandler:(void (^)(UIBackgroundFetchResult result))completionHandler { [_lifeCycleDelegate application:application performFetchWithCompletionHandler:completionHandler];}#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 120000- (BOOL)application:(UIApplication*)applicationcontinueUserActivity:(NSUserActivity*)userActivity restorationHandler:(void (^)(NSArray<id<UIUserActivityRestoring>>* __nullable restorableObjects))restorationHandler {#else - (BOOL)application:(UIApplication*)applicationcontinueUserActivity:(NSUserActivity*)userActivityrestorationHandler:(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 restore2019-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.xcconfigcp -ri ../.ios/Flutter/Generated.xcconfig ${SOURCE_ROOT}/Flutter/Generated.xcconfigrm -rf ${SOURCE_ROOT}/Flutter/App.frameworkcp -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键就可以了。