本文不想寫一個全篇步驟式的文章來描寫怎麼集成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鍵就能夠了。