本文不想写一个全篇步骤式的文章来描写怎么集成flutter,而是指望用一种探索的方式来追寻答案。ios
咱们首先看下flutter项目和通常原生项目的大概区别。git
为了跳转方便,原生项目的入口通常是UINavigationController
。github
而咱们看下flutter默认给咱们建立的模板为:shell
这里咱们来看下flutter的引擎源码,看下这段代码作了什么工做,源码路径为:https://github.com/flutter/en...xcode
咱们首先看下`FlutterAppDelegateapp
https://github.com/flutter/en...iphone
- (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
,并在全部方法中调用这个类实例的方法。ide
原生项目中建立根ViewControler的方式可使用StoryBoard,也可使用代码建立。而flutter模板给咱们建立的项目为StoryBoard的方式fetch
从这里咱们能够发现,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 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不是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
编辑下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键就能够了。