iOS 原生項目嵌入 Flutter
雖然一般不建議在原生項目中嵌入Flutter
,但是Flutter
也可以支持這種方式,下面我們來看一下具體的實現。
原生嵌入 Flutter 的工程配置
如圖,我們想使原生嵌入Flutter
的話,使用Android Studio
創建項目的時候就要選擇Module
進行創建,使之作爲一個模塊來開發。
打開我們新建的flutter_module
工程目錄可以看到,與創建的Flutter App
相比,文件裏面仍然有Android
、iOS
工程文件,但是這裏只是爲了讓我們做調試用的,而且這兩個文件都是隱藏文件,不過Android
、iOS
工程中不建議加入原生代碼,而且即使加了,打包的時候也不會被打包進去。flutter_module
是一個純Flutter
的工程。
Podfile
文件配置
flutter_application_path = '../flutter_module'
load File.join(flutter_application_path,'.iOS','Flutter','podhelper.rb')
platform :ios, '9.0'
target 'NativeDemo' do
install_all_flutter_pods(flutter_application_path)
use_frameworks!
# Pods for NativeDemo
end
我們使用Xcode
創建一個原生工程,NativeDemo
,使用終端,cd
到NativeDemo
目錄下,pod init
,然後配置Podfile
文件,然後執行pod install
。
pod install
完成之後,打開原生項目,引用頭文件#import <Flutter/Flutter.h>
,可以成功的話就代表配置成功,現在的話原生工程與Flutter
就有聯繫了,下面我們就可以實現代碼了,來使原生工程中嵌入Flutter
。
原生項目調起 Flutter 頁面
- 原生代碼部分
#import "ViewController.h"
#import <Flutter/Flutter.h>
@interface ViewController ()
@property(nonatomic, strong) FlutterEngine* flutterEngine;
@property(nonatomic, strong) FlutterViewController* flutterVc;
@property(nonatomic, strong) FlutterBasicMessageChannel * msgChannel;
@end
@implementation ViewController
-(FlutterEngine *)flutterEngine
{
if (!_flutterEngine) {
FlutterEngine * engine = [[FlutterEngine alloc] initWithName:@"hank"];
if (engine.run) {
_flutterEngine = engine;
}
}
return _flutterEngine;
}
- (IBAction)pushFlutter:(id)sender {
self.flutterVc.modalPresentationStyle = UIModalPresentationFullScreen;
//創建channel
FlutterMethodChannel * methodChannel = [FlutterMethodChannel methodChannelWithName:@"one_page" binaryMessenger:self.flutterVc.binaryMessenger];
//告訴Flutter對應的頁面
[methodChannel invokeMethod:@"one" arguments:nil];
//彈出頁面
[self presentViewController:self.flutterVc animated:YES completion:nil];
//監聽退出
[methodChannel setMethodCallHandler:^(FlutterMethodCall * _Nonnull call, FlutterResult _Nonnull result) {
//如果是exit我就退出頁面!
if ([call.method isEqualToString:@"exit"]) {
[self.flutterVc dismissViewControllerAnimated:YES completion:nil];
}
}];
}
- (IBAction)pushFlutterTwo:(id)sender {
self.flutterVc.modalPresentationStyle = UIModalPresentationFullScreen;
//創建channel
FlutterMethodChannel * methodChannel = [FlutterMethodChannel methodChannelWithName:@"two_page" binaryMessenger:self.flutterVc.binaryMessenger];
//告訴Flutter對應的頁面
[methodChannel invokeMethod:@"two" arguments:nil];
//彈出頁面
[self presentViewController:self.flutterVc animated:YES completion:nil];
//監聽退出
[methodChannel setMethodCallHandler:^(FlutterMethodCall * _Nonnull call, FlutterResult _Nonnull result) {
//如果是exit我就退出頁面!
if ([call.method isEqualToString:@"exit"]) {
[self.flutterVc dismissViewControllerAnimated:YES completion:nil];
}
}];
}
- (void)viewDidLoad {
[super viewDidLoad];
self.flutterVc = [[FlutterViewController alloc] initWithEngine:self.flutterEngine nibName:nil bundle:nil];
self.msgChannel = [FlutterBasicMessageChannel messageChannelWithName:@"messageChannel" binaryMessenger:self.flutterVc.binaryMessenger];
[self.msgChannel setMessageHandler:^(id _Nullable message, FlutterReply _Nonnull callback) {
NSLog(@"收到Flutter的:%@",message);
}];
}
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
static int a = 0;
[self.msgChannel sendMessage:[NSString stringWithFormat:@"%d",a++]];
}
在原生代碼部分我們定義了三個屬性,flutterEngine
代表引擎對象,flutterVc
是FlutterViewController
類型的控制器對象,msgChannel
是通信方式中的一種channel
,爲FlutterBasicMessageChannel
類型,下面會有介紹。
在這裏我們實現了pushFlutter
與pushFlutterTwo
兩個方法,代表調起兩個不同的Flutter
頁面。在這兩個方法中,我們首先創建methodChannel
對象,並分別傳入one
跟two
兩個字符串標識,並且binaryMessenger
傳參傳入的都是self.flutterVc.binaryMessenger
。在兩個方法中分別調用invokeMethod
方法,向Flutter
頁面發送消息,然後彈出頁面,並且實現setMethodCallHandler
方法,在閉包中判斷call.method isEqualToString:@"exit"
,進行頁面的退出。
- Flutter 代碼部分
// ignore_for_file: avoid_print
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
void main() => runApp(const MyApp());
class MyApp extends StatefulWidget {
const MyApp({Key? key}) : super(key: key);
@override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
final MethodChannel _oneChannel = const MethodChannel('one_page');
final MethodChannel _twoChannel = const MethodChannel('two_page');
final BasicMessageChannel _messageChannel =
const BasicMessageChannel('messageChannel', StandardMessageCodec());
String pageIndex = 'one';
@override
void initState() {
super.initState();
_messageChannel.setMessageHandler((message) {
print('收到來自iOS的$message');
return Future(() {});
});
_oneChannel.setMethodCallHandler((call) {
pageIndex = call.method;
print(call.method);
setState(() {});
return Future(() {});
});
_twoChannel.setMethodCallHandler((call) {
pageIndex = call.method;
print(call.method);
setState(() {});
return Future(() {});
});
}
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: _rootPage(pageIndex),
);
}
//根據pageIndex來返回頁面!
Widget _rootPage(String pageIndex) {
switch (pageIndex) {
case 'one':
return Scaffold(
appBar: AppBar(
title: Text(pageIndex),
),
body: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(
onPressed: () {
_oneChannel.invokeMapMethod('exit');
},
child: Text(pageIndex),
),
TextField(
onChanged: (String str) {
_messageChannel.send(str);
},
)
],
),
);
case 'two':
return Scaffold(
appBar: AppBar(
title: Text(pageIndex),
),
body: Center(
child: ElevatedButton(
onPressed: () {
_twoChannel.invokeMapMethod('exit');
},
child: Text(pageIndex),
),
),
);
default:
return Scaffold(
appBar: AppBar(
title: Text(pageIndex),
),
body: Center(
child: ElevatedButton(
onPressed: () {
const MethodChannel('default_page').invokeMapMethod('exit');
},
child: Text(pageIndex),
),
),
);
}
}
}
在Flutter
代碼中我們定義了_oneChannel
與_twoChannel
這兩個變量用了接收原生頁面發送的消息,並且向原生頁面發送消息。定義了變量pageIndex
用來標識創建那個頁面。
在initState
方法中調用setMethodCallHandler
方法,獲取到原生頁面傳來的數據並賦值給pageIndex
,然後調用setState
方法。
在build
方法中我們調用_rootPage
方法來判斷創建哪個頁面。並且分別在這兩個頁面的點擊事件中調用invokeMapMethod
方法,代表退出頁面,原生頁面在setMethodCallHandler
閉包中接收到exit
數據後就會調用[self.flutterVc dismissViewControllerAnimated:YES completion:nil]
,進行頁面的退出。
Flutter 與原生的通信
MethodChannel
:Flutter
與Native
端相互調用,調用後可以返回結果,可以Native
端主動調用,也可以Flutter
主動調用,屬於雙向通信。此方式爲最常用的方式,Native
端調用需要在主線程中執行。
BasicMessageChannel
: 用於使用指定的編解碼器對消息進行編碼和解碼,屬於雙向通信,可以Native
端主動調用,也可Flutter
主動調用。
EventChannel
:用於數據流(event streams
)的通信,Native
端主動發送數據給Flutter
,通常用於狀態的監聽,比如網絡變化、傳感器數據等。
Flutter
與原生通信有三種方式,Flutter
爲我們提供了三種Channel
,分別是MethodChannel
、BasicMessageChannel
與EventChannel
。但是我們比較常用的就是MethodChannel
與BasicMessageChannel
這兩種。因爲MethodChannel
前面已經講過了,所以這裏我們介紹一下BasicMessageChannel
的用法。
BasicMessageChannel 用法
BasicMessageChannel
的用法與FlutterMethodChannel
類似,在上面的代碼示例中,首先在Flutter
代碼中我們也是定義一個BasicMessageChannel
類型的變量_messageChannel
,在_messageChannel
的setMessageHandler
閉包中接收來自於原生頁面發來的消息,調用_messageChannel
的send
方法向原生頁面進行通信,在輸入框文字變化的時候都會調用send
方法。在原生代碼中也是類似,定義了msgChannel
屬性,setMessageHandler
中的block
負責接收消息,sendMessage
發送消息,在touchesBegan
中向Flutter
傳遞a
的累加值。
作者:晨曦_iOS
https://juejin.cn/post/7036393182053007367
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/kvQC7hrHUgAtK9RjqyFOFg