2020年之前有些教程創(chuàng)建方式已經(jīng)不適用,要么缺文件客燕、要么原生項目運行報錯鸳劳,折騰幾天半個月都不行,還是官方文檔教程比較靠譜也搓。
目前原生App和Flutter混合開發(fā)有兩種模式:
- 統(tǒng)一管理模式:將原生工程作為Flutter工程的子工程赏廓,由Flutter進(jìn)行統(tǒng)一管理。
- 三端分離模式:將Flutter工程作為原生工程的子模塊傍妒,維持原有的原生工程管理方式不變幔摸。
一、基于原有iOS項目集成Flutter框架
前提要擁有CocoaPods和Flutter環(huán)境配置
Flutter官方文檔
1.1颤练、創(chuàng)建項目文件夾
新建總項目文件夾既忆,存放三端項目文件如:carry_sniper
1.2、創(chuàng)建Flutter模塊
在總項目文件夾內(nèi)嗦玖,創(chuàng)建Flutter模塊工程如:flutter_module
cd xxx/carry_sniper 你的文件夾路徑
flutter create --template module flutter_module
執(zhí)行結(jié)果:
Creating project flutter_module...
flutter_module/test/widget_test.dart (created)
flutter_module/flutter_module.iml (created)
flutter_module/.gitignore (created)
flutter_module/.metadata (created)
flutter_module/pubspec.yaml (created)
flutter_module/README.md (created)
flutter_module/lib/main.dart (created)
flutter_module/flutter_module_android.iml (created)
flutter_module/.idea/libraries/Dart_SDK.xml (created)
flutter_module/.idea/modules.xml (created)
flutter_module/.idea/workspace.xml (created)
Running "flutter pub get" in flutter_module... 0.8s
Wrote 11 files.
All done!
Your module code is in flutter_module/lib/main.dart.
1.3患雇、創(chuàng)建iOS項目工程
在總項目文件夾,使用Xcode創(chuàng)建iOS項目工程如:CarrySniperiOS
1.4宇挫、關(guān)聯(lián)操作
1.4.1為iOS工程添加CocoaPods依賴庆亡,生成Proflie文件
終端指令執(zhí)行:
cd xxx/carry_sniper/CarrySniperiOS 你的iOS工程項目路徑
pod init
1.4.2打開Proflie文件添加、修改內(nèi)容
主要2段3行代碼捞稿,注意代碼存放位置又谋,和flutter_module
名稱一致
flutter_application_path = '../flutter_module'
load File.join(flutter_application_path, '.ios', 'Flutter', 'podhelper.rb')
install_all_flutter_pods(flutter_application_path)
配置結(jié)果如下:
platform :ios, '11.0'
# 1拼缝、Flutter模塊加入
flutter_application_path = '../flutter_module'
load File.join(flutter_application_path, '.ios', 'Flutter', 'podhelper.rb')
target 'CarrySniperiOS' do
use_frameworks!
# 2、安裝嵌入Flutter模塊
install_all_flutter_pods(flutter_application_path)
# Pods for CarrySniperiOS
end
1.4.3執(zhí)行指令彰亥,完成Flutter模塊的添加和CocoaPods依賴
pod install
執(zhí)行結(jié)果:
要看到Installing Flutter相關(guān)依賴的安裝咧七,否則運行報錯
關(guān)閉當(dāng)前Xcode項目,從此使用CarrySniperiOS.xcworkspac運行工程
/System/Library/Frameworks/Ruby.framework/Versions/2.6/usr/lib/ruby/2.6.0/universal-darwin19/rbconfig.rb:229: warning: Insecure world writable dir /Users/Macbook/Documents/FlutterSDK/flutter/bin in PATH, mode 040777
Analyzing dependencies
Downloading dependencies
Installing Flutter (1.0.0)
Installing FlutterPluginRegistrant (0.0.1)
Installing flutter_module (0.0.1)
Generating Pods project
Integrating client project
[!] Please close any current Xcode sessions and use `CarrySniperiOS.xcworkspace` for this project from now on.
Pod installation complete! There are 3 dependencies from the Podfile and 3 total pods installed.
1.5任斋、完成基礎(chǔ)配置
可以直接打開項目運行继阻,沒問題就說明配置成功。部分目錄如下:
carry_sniper/
├── CarrySniperiOS/
│ ├── CarrySniperiOS/
│ ├── Pods/
│ ├── Podfile
│ ├── Podfile.lock
│ ├── CarrySniperiOS.xcodeproj
│ └── CarrySniperiOS.xcworkspace
├── flutter_module/
│ ├── .android/
│ ├── .ios/
│ │ ├── Runner.xcworkspace
│ │ └── Flutter/podhelper.rb
│ ├── lib/
│ └── main.dart
│ ├── flutter_module_android.iml
│ ├── flutter_module.iml
│ ├── pubspec.lock
│ ├── test/
│ └── pubspec.yaml
二废酷、原生iOS項目調(diào)用Flutter
根據(jù)官方Flutter文檔瘟檩,進(jìn)行簡單的原生app調(diào)用Flutter,列舉2種方法澈蟆,這里是Objective-C代碼墨辛,文檔有Swift代碼。更高級的調(diào)用方法可以繼續(xù)看文檔趴俘。
2.1方式一:使用FlutterViewController
直接在ViewController.m文件編寫代碼睹簇,運行項目即可:
#import "ViewController.h"
#import <Flutter/Flutter.h>
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
[button addTarget:self action:@selector(showFlutter)
forControlEvents:UIControlEventTouchUpInside];
[button setTitle:@"Show Flutter!" forState:UIControlStateNormal];
button.backgroundColor = UIColor.blueColor;
button.frame = CGRectMake(80.0, 210.0, 160.0, 40.0);
[self.view addSubview:button];
}
- (void)showFlutter {
FlutterViewController *flutterViewController = [[FlutterViewController alloc] initWithProject:nil nibName:nil bundle:nil];
[self presentViewController:flutterViewController animated:YES completion:nil];
}
@end
2.2、方式二:使用FlutterEngine
2.1.1寥闪、依賴FlutterAppDelegate創(chuàng)建一個實體FlutterEngine
AppDelegate.h文件內(nèi)容:
#import <UIKit/UIKit.h>
#import <Flutter/Flutter.h>
@interface AppDelegate : FlutterAppDelegate
@property (nonatomic,strong) FlutterEngine *flutterEngine;
@end
AppDelegate.m文件內(nèi)容:
#import "AppDelegate.h"
#import <FlutterPluginRegistrant/GeneratedPluginRegistrant.h>
@interface AppDelegate ()
@end
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
self.flutterEngine = [[FlutterEngine alloc] initWithName:@"my 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];
}
@end
2.2.2太惠、使用FlutterEngine調(diào)用Flutter頁面和傳參
ViewController.m文件內(nèi)容:
#import "ViewController.h"
#import "AppDelegate.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
// Make a button to call the showFlutter function when pressed.
UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
[button addTarget:self action:@selector(showFlutter)
forControlEvents:UIControlEventTouchUpInside];
[button setTitle:@"Show Flutter!" forState:UIControlStateNormal];
button.backgroundColor = UIColor.blueColor;
button.frame = CGRectMake(80.0, 210.0, 160.0, 40.0);
[self.view addSubview:button];
}
- (void)showFlutter {
FlutterEngine *flutterEngine =
((AppDelegate *)UIApplication.sharedApplication.delegate).flutterEngine;
FlutterViewController *flutterViewController =
[[FlutterViewController alloc] initWithEngine:flutterEngine nibName:nil bundle:nil];
[self presentViewController:flutterViewController animated:YES completion:nil];
}
@end
2.2.3、運行Xcode中的CarrySniperiOS項目
三疲憋、原生iOS項目也能使用熱更新凿渊、熱重載
官方文檔:
要先安裝homebrew,運行Xcode項目缚柳,保證原生App有安裝到手機(jī)/模擬器上嗽元,然后在Android Studio的Flutter項目跟路徑執(zhí)行指令:
flutter attach
如果有多個設(shè)備(或者輸入下標(biāo)選擇相應(yīng)設(shè)備):
flutter attach -d xxxxx設(shè)備id
如果有多個包名:
那么需要統(tǒng)一Android和iOS的包名后再試一遍
如果出現(xiàn)Waiting for a connection from Flutter on iPhone ...
一直等待,
說明原生App沒有啟動喂击,控制臺連接不到設(shè)備應(yīng)用運行。
需要手動到手機(jī)/模擬器點擊運行App即可(只需運行Android Studio淤翔,不需要打開Xcode軟件翰绊,Xcode會在Flutter下默默運行。當(dāng)然不打開Xcode旁壮,就看不到控制臺打印輸出监嗜,但影響不大)。
執(zhí)行結(jié)果:
Waiting for iPhone 12 Pro to report its views... 7ms
Syncing files to device iPhone 12 Pro...
8,140ms (!)
Flutter run key commands.
r Hot reload. ??????
R Hot restart.
h Repeat this help message.
d Detach (terminate "flutter run" but leave application running).
c Clear the screen
q Quit (terminate the application on the device).
An Observatory debugger and profiler on iPhone 12 Pro is available at: http://127.0.0.1:51810/ucfjPzF2_sY=/
四抡谐、原生iOS項目后續(xù)可能遇到的問題
如果發(fā)現(xiàn)運行App進(jìn)度和Flutter開發(fā)進(jìn)度不一致裁奇,需要去Xcode運行原生項目App。需要保證打包的代碼是最新的麦撵,否則安裝的App再次啟動后刽肠,永遠(yuǎn)是之前的版本溃肪,沒有包含最新Flutter部分的代碼。
個人理解:已安裝的App音五,我們使用飯flutter attach惫撰,熱更新和熱重載只保證運行時代碼是最新的,運行結(jié)束之后就恢復(fù)原生App安裝時的模樣躺涝,并沒有把最新的Flutter代碼打包到安裝包里面厨钻。
Showing Recent Messages Undefined symbol: protocol conformance descriptor fo xxx 等幾十上百個報錯
莫名其妙的出現(xiàn),之前運行還好好的坚嗜,可能Flutter添加了某些package夯膀,在原生項目就突然出問題了。
可能解決方法:
嘗試一:升級CocoaPods苍蔬;
嘗試二:更新依賴庫 pod install --verbose --no-repo-update
嘗試三:為原生項目添加swift橋接文件诱建,任意直接New一個.swift文件,Xcode 提示 Create Bridging Header 银室,選擇創(chuàng)建即可涂佃。記得.swift文件保留不刪除。
Command PhaseScriptExecution failed with a nonzero exit code
/packages/flutter_tools/bin/xcode_backend.sh: No such file or directory
先檢查Flutter SDK里面存不存在xcode_backend.sh文件蜈敢,不存在就去找一個或者重新下載sdk辜荠;
存在的話,可能就是Xcode項目配置可能缺少FLUTTER_ROOT抓狭,Target -> Build Setting -> User-Defined 添加FLUTTER_ROOT對應(yīng)sdk路徑伯病;
如果不知道路徑可以直接復(fù)制flutter項目的.ios的Generated.xcconfig里面的FLUTTER_ROOT內(nèi)容,會自動幫導(dǎo)入到User-Defined否过。
Support for empty structs is deprecated and will be removed in the next stable version of Dart. Use Opaque instead.
遇到SDK版本更新午笛,API過期更替。不影響使用苗桂,需要等待第三方插件更新支持最新包药磺。可以用'flutter downgrade'指令降級SDK煤伟。
五癌佩、一些想法
三端分離,flutter里面iOS的info.plist文件很容易在安卓合并代碼或pub get指令執(zhí)行時會被重置便锨,影響開發(fā)和調(diào)試围辙。當(dāng)然原生項目的info.plist文件是不影響的,只是不想開發(fā)期間直接用原生項目放案,效率不一樣姚建。