1、安装Flutter环境
1、下载SDK并安装
2、 配置环境
如果 ~/.zshenv 文件存在,请在文本编辑器中打开 Zsh 环境变量文件 ~/.zshenv。如果不存在,请创建 ~/.zshenv
将export PATH=$HOME/development/flutter/bin:$PATH加入到文件的最后面
创建Flutter项目
以Flutter为主
以Flutter为主:意思是直接创建完整的flutter项目,里面就已经包含了iOS、Android等工程。直接用即可
在需要的目录中 执行 flutter create aiflutter
根据提示执行命令
In order to run your application, type:
$ cd flutterdemo
$ flutter run // 执行这2行命令
Your application code is in test/lib/main.dart.
项目配置
进入iOS文件夹
这里需要注意: 需要用到CocosPods将Flutter作为组件导入到项目,但是Flutter并没有直接生成Podfile文件。需要自己init一个
1、进入flutterdemo/ios目录
2、执行 pod init命令
3、执行pod install命令
4、修改Podfile文件,因为项目有flutter的路径信息,文件的配置如下:
source 'https://github.com/CocoaPods/Specs.git'
# Uncomment this line to define a global platform for your project
platform :ios, '13.0'
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
project 'Runner', {
'Debug' => :debug,
'Profile' => :release,
'Release' => :release,
}
def flutter_root
generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__)
unless File.exist?(generated_xcode_build_settings_path)
raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first"
end
File.foreach(generated_xcode_build_settings_path) do |line|
matches = line.match(/FLUTTER_ROOT\=(.*)/)
return matches[1].strip if matches
end
raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get"
end
require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)
flutter_ios_podfile_setup
target 'Runner' do
use_frameworks!
flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
target 'RunnerTests' do
inherit! :search_paths
end
end
post_install do |installer|
installer.pods_project.targets.each do |target|
flutter_additional_ios_build_settings(target)
end
end
在进行
Podfile install时,可能会有警告。 如果想要去掉警告,需要按照以下方式修改。但是修改之后会运行不起来
正确的应该是选中Debug.xcconfig、Release.xcconfig
在使用过程中, 因为iOS工程是其他人创建后给我的,在进行pod install的时候,出现了路径找不到的报错: 修改这个路径
错误处理
-
错误1:
Command PhaseScriptExecution failed with a nonzero exit code这是由于
Run Script的脚本找不到正确路径 在此确认podfile文件已修改,具体参照上面 项目配置中的文件 -
错误2:
Unable to load contents of file list: '/Target Support Files/Pods-Runner/Pods-Runner-frameworks-Debug-input-files.xcfilelist'需要回到Flutter项目目录下,执行
flutter run.
其实在执行flutter create flutterdemo完成的时候,就已经提示了In order to run your application, type: $ cd test $ flutter run Your application code is in test/lib/main.dart. -
错误3
The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.在
Build Settings中搜索ENABLE_USER_SCRIPT_SANDBOXING将其设置为NO,如果原本是NO,设置为YES后运行一次后再改为NO
完全退出Xcode。 -
错误4
[!] Invalid
Podfilefile: cannot load such file -- /Users/huaheshang/packages/flutter_tools/bin/podhelper.
执行pod install时,提示这个错误
解决方案:
查看ios/Flutter路径下的Generated.xcconfig文件中的路径是否正常
FLUTTER_ROOT路径是否是安装FlutterSDK的路径。eg:/Users/xx/development/flutterFLUTTER_APPLICATION_PATH项目路径.eg:/Users/xx/flutterdemo
-
错误5
Removing xxxx
执行pod install时,将Flutter依赖的一些内容移除掉了
常见于: 从其他已经配置好的iOS工程中,直接将iOS文件拷贝到另一个Flutter工程中。第一次pod install时。
解决方案:确保podfile文件的配置正确(参照上面的podfile文件配置)。进入Flutter目录下,执行:flutter run 重新pod install
错误总结
错误总结
1、确保执行过flutter run
2、在Build Settings中搜索ENABLE_USER_SCRIPT_SANDBOXING将其设置为NO
3、使用pod init新建一个podfile文件并修改里面的内容
4、确认ios/Flutter路径下的Generated.xcconfig中的配置FLUTTER_ROOT、FLUTTER_APPLICATION_PATH是否正常
步骤总结
1、安装FlutterSDK并配置其环境
2、使用命令创建Flutter项目
flutter create flutterdemo3、执行
cd flutterdemo和flutter run命令4、导入podfile文件并执行
pod install命令
以iOS为主
以iOS为主意思是:手动创建一个iOS工程,将Flutter作为一个组件导入到iOS项目中
1、创建一个iOS工程AIIOSDemo,并进行pod
2、在同级目录下新建Flutter项目:flutter create -t module my_flutter\
3、在podfile中引入flutter
source 'https://github.com/CocoaPods/Specs.git'
platform :ios, '15.0'
# 1、在文件顶部添加 flutter_application_path
flutter_application_path = '../my_flutter' #这里是刚才创建的flutter module名称
load File.join(flutter_application_path, '.ios', 'Flutter', 'podhelper.rb')
target 'iOSDemo' do
# Comment the next line if you don't want to use dynamic frameworks
use_frameworks!
pod 'SnapKit'
pod 'LookinServer', :subspecs => ['Swift'], :configurations => ['Debug']
// 2、引入路径
install_all_flutter_pods(flutter_application_path)
end
# 3、添加这个 post_install 块
post_install do |installer|
flutter_post_install(installer)
end
页面跳转
FlutterEngine
从原生跳转到Flutter页面官方连接
也可参考链接
在使用Flutter之前,需要先注册GeneratedPluginRegistrant
//在AppDelegate中定义全局的flutterEngine
lazy var flutterEngine: FlutterEngine = FlutterEngine(name: "com.brainco.gameEngine")
private func initEngine() {
// 在用到Flutter之前,要先注册这个方法
//这个要在跳转方法之前运行环境,也可以在appdelegate里面启动就初始化,环境运行需要时间,单写在跳转方法里面靠前位置是不可以的。
flutterEngine.run();
GeneratedPluginRegistrant.register(with: flutterEngine);
}
- 直接以FlutterViewController为页面 在原生页面初始化按钮,并添加点击事件,在事件中实现以下代码:
func jumpToFlutterPage() -> Void {
let flutterEngine = (UIApplication.shared.delegate as? AppDelegate)?.flutterEngine
let flutterViewController = FlutterViewController(engine: flutterEngine!, nibName: nil, bundle: nil)
/*
// 可以通过MethodChannel传递参数
let channel = FlutterMethodChannel(
name: "com.example.app/flutter",
binaryMessenger: flutterViewController.binaryMessenger
)
// 可选 -- 设置初始路由或传递参数
channel.invokeMethod("initialRoute", arguments: "/targetPage")
// 可选 -- 设置监听,执行Flutter调用原生的方法
channel.setMethodCallHandler{[weak self] (call, result) in
guard let strongSelf = self else { return }
print("flutter 给到我 method:\(call.method) arguments:\(String(describing: call.arguments))")
}
*/
self.navigationController?.pushViewController(flutterViewController, animated: true)
}
- 将Flutter作为ChildViewController加入原生的viewController
class FlutterCustomViewController: BaseViewController {
override func viewDidLoad() {
super.viewDidLoad()
let flutterEngine = (UIApplication.shared.delegate as? AppDelegate)?.flutterEngine
let flutterViewController = FlutterViewController(engine: flutterEngine!, nibName: nil, bundle: nil)
/*
// 可以通过MethodChannel传递参数
let channel = FlutterMethodChannel(
name: "com.example.app/flutter",
binaryMessenger: flutterViewController.binaryMessenger
)
// 可选 -- 设置初始路由或传递参数
channel.invokeMethod("initialRoute", arguments: "/targetPage")
// 可选 -- 设置监听,执行Flutter调用原生的方法
channel.setMethodCallHandler{[weak self] (call, result) in
guard let strongSelf = self else { return }
print("flutter 给到我 method:\(call.method) arguments:\(String(describing: call.arguments))")
}
*/
self.addChild(flutterViewController)
self.view.addSubview(flutterViewController.view)
flutterViewController.view.snp.makeConstraints{make in
make.left.right.top.bottom.equalToSuperview()
}
}
FlutterEngineGroup
官方demo--github iOSDemo
在项目中,如果存在多个场景需要跳转Flutter。可参考官方提供的demo,使用FlutterEngineGroup的方式。
// 1、 定义一个全局的engines
class FlutterBridgeHelper: NSObject {
static let engines = FlutterEngineGroup(name: "customEngines", project: nil)
// 2、 创建一个新的engine 并regist插件
// name: Flutter用来判断展示那个页面的参数
// initialRoute: 展示页面的初始参数
public class func createEngine(name: String, initialRoute: String? = nil) -> FlutterEngine {
let newEngine = FlutterBridgeHelper.engines.makeEngine(withEntrypoint: name, libraryURI: nil, initialRoute: initialRoute)
GeneratedPluginRegistrant.register(with: newEngine)
return newEngine
}
}
// 3、实际调用
func pushToFlutter() {
// 创建engine 并指定展示的view: “main”
let newEngine = FlutterBridgeHelper.createEngine(name: "main")
// 根据创建的engine创建一个FlutterViewController
let flutterController = FlutterViewController(engine: newEngine, nibName: nil, bundle: nil)
flutterController.view.backgroundColor = .white
// 根据需要定义Flutter与原生之间的交互 MethodChannel、EventChannel等
// flutterHelper.flutterRegist(controller: flutterController)
self.navigationController?.pushViewController(flutterController, animated: true)
}
在GeneratedPluginRegistrant.register(with: newEngine)这个步骤中,确保register中注册了
// 导入相关头文件
#if __has_include(<connectivity_plus/ConnectivityPlusPlugin.h>)
#import <connectivity_plus/ConnectivityPlusPlugin.h>
#else
@import connectivity_plus;
#endif
+ (void)registerWithRegistry:(NSObject<FlutterPluginRegistry>*)registry {
// ....其他插件导入...
[ConnectivityPlusPlugin registerWithRegistrar:[registry registrarForPlugin:@"ConnectivityPlusPlugin"]];
}
iOS与Flutter交互
Flutter 与原生存在三种交互方式 可参考链接
三种 Channel 之间互相独立,各有用途,但它们在设计上却非常相近。每种 Channel 均有三个重要成员变量:
- name: 【重要参数】String类型,代表 Channel 的名字,也是其唯一标识符
需要和Fluter中的定义保持一致- messager:【重要参数】BinaryMessenger 类型,代表消息信使,是消息的发送与接收的工具
- codec: MessageCodec 类型或 MethodCodec 类型,代表消息的编解码器
MethodChannel
一般用于传递方法调用(method invocation)通常用于Flutter调用原生中某个方法
举例:使用场景-Flutter需要获取原生生成的用户UUID,并传递UUID做存储操作
// 引入Flutter
import Flutter
@objc class AppDelegate: FlutterAppDelegate {
// 枚举的方式定义方法名
enum FlutterMethodType: String {
case saveUUID = "saveUUID" ///< 保存 UUID
case getUUID = "getUUID" ///< 获取 UUID
}
let controller = window?.rootViewController as! FlutterViewController
// 初始化参数,并设置回调handle
func MethodChannelRegist(controller: FlutterViewController) {
let methodChannel_channer = FlutterMethodChannel(
name: "com.example/ai/snowflake",
binaryMessenger: controller.binaryMessenger
)
methodChannel_channer.setMethodCallHandler { [weak self] (call, result) in
guard let self = self else { return }
self.flutterMethodChanner_channer(call: call, result: result)
}
}
// flutter 调用 swift
private func flutterMethodChanner_channer(call: FlutterMethodCall, result: FlutterResult) -> Void {
if call.method == FlutterMethodType.getUUID.rawValue {
let uuid = "uuid"
result(uuid)
}else if call.method == FlutterMethodType.saveUUID.rawValue {
let success = true
result(success)
} else {
result(FlutterMethodNotImplemented)
}
}
}
BasicMessageChannel
它是可以双端通信的,Flutter 端可以给 iOS 发送消息,iOS 也可以给 Flutter 发送消息。
// 全局,方便随时可以发送消息
var basicMessageChannel: FlutterBasicMessageChannel? = nil
// 其他和MethodChannel基本一致
func BasicMessageChannelRegist(controller: FlutterViewController) {
basicMessageChannel = FlutterBasicMessageChannel(name: "com.example/ai/snowflake",
binaryMessenger: controller.binaryMessenger)
basicMessageChannel?.setMessageHandler { [weak self] (call, result) in
guard let self = self else { return }
self.flutterMethodChanner_channer(call: call as! FlutterMethodCall, result: result)
}
// 相比MethodChannel 最重要的区别就是这个 可以主动向Flutter发送消息
basicMessageChannel?.sendMessage(["name":"隔壁老王","age":25])
}
// flutter 调用 swift
private func flutterMethodChanner_channer(call: FlutterMethodCall, result: FlutterResult) -> Void {
if call.method == "methodOne" {
}else if call.method == "methodTwo" {
} else {
result(FlutterMethodNotImplemented)
}
}
EventChannel
只能是原生发送消息给 Flutter 端,例如监听手机电量变化,网络变化,传感器等。
func eventChannelRegist(controller: FlutterViewController) {
let eventChannel = FlutterEventChannel(
name: "com.example.demo/event",
binaryMessenger: controller.binaryMessenger
)
eventChannel.setStreamHandler(self)
}
// MARK: FlutterStreamHandler
var eventSink: FlutterEventSink? = nil
func onListen(withArguments arguments: Any?, eventSink events: @escaping FlutterEventSink) -> FlutterError? {
self.eventSink = events
return nil
}
func onCancel(withArguments arguments: Any?) -> FlutterError? {
self.eventSink = nil
return nil
}
func sendEvent(data: Any) {
eventSink?(data) // 主动发送数据到 Flutter
}
交互方式对比
| 交互方式 | MethodChannel | BasicMessageChannel | EventChannel |
|---|---|---|---|
| 交互场景 | Flutter调用原生中某个方法 | 双端通信,可以相互发消息 | 原生发送消息给 Flutter 端 |
| 示例 | flutter获取原生的用户ID | / | 原生监测网络、电量等的变化 |
交互方法封装与调用
根据上面的单个示例和使用过程中防止单个方法的重复定义,可以将相应的方法进行封装
//
// FlutterBridgeHelper.swift
// Runner
//
import UIKit
import Flutter
// MethodChannel的方法
enum FlutterChannelMethodType: String {
case chan_UUID = "UUID"
}
// Flutter跳转原生的交互定义
enum FlutterChannelPageType: String {
case chan_gotoNativePage = "gotoNativePage"
}
class FlutterBridgeHelper: NSObject {
// 原生跳转Flutter的engine Group全局定义
static let engines = FlutterEngineGroup(name: "customEngines", project: nil)
// 与Flutter之间定义的协议 2端必须保持一致
let channerPrefix = "com.example/ai/"
let channer_page = "xd_app/"
// 交互的FlutterViewController,原生与Flutter之间交互时,需要拿到FlutterViewController
var flutterController: FlutterViewController?
// EventChannel的对象 用于原生主动向Flutter传递信息
var eventSink: FlutterEventSink?
override init() {
super.init()
}
// 注册
func flutterRegist(controller: FlutterViewController) {
self.flutterController = controller
// 注册MethodChanner
MethodChannelRegist(controller: controller)
// 注册 EventChannel
EventChannelRegist(controller: controller)
}
// 原生调用Flutter
public func swiftToFlutter(data: Any) {
eventSink?(data) // 主动发送数据到 Flutter
}
private func MethodChannelRegist(controller: FlutterViewController) {
//注册 MethodChannel 如果有多个MethonChannel的定义,就需要setMethodCallHandler多个
// 这里将方法之间的交互、页面之间的跳转拆分开了。也可以定义在同一套name协议里
// 方法之间的交互
let methodChannel = FlutterMethodChannel(
name: channerPrefix+"snowflake", // "com.example.app/channel",
binaryMessenger: controller.binaryMessenger
)
methodChannel.setMethodCallHandler {[weak self] call, result in
self?.flutterToSwift(call: call, result: result)
}
// 页面之间的跳转
let pageChannel = FlutterMethodChannel(
name: channer_page+"main_module_channel", // "xd_app/main_module_channel",
binaryMessenger: controller.binaryMessenger
)
pageChannel.setMethodCallHandler {[weak self] call, result in
self?.flutterToNavPage(call: call, result: result)
}
}
private func EventChannelRegist(controller: FlutterViewController) {
// 从 Swift 主动发送事件到 Flutter:
let eventChannel = FlutterEventChannel(
name: channerPrefix+"events", //"com.example.app/events",
binaryMessenger: controller.binaryMessenger
)
eventChannel.setStreamHandler(self)
}
// flutter 调用 swift
private func flutterToSwift(call: FlutterMethodCall, result: FlutterResult? = nil) -> Void {
if call.method == FlutterChannelMethodType.UUID.rawValue {
// 处理 Flutter 调用
// let argument = call.arguments as? String ?? ""
let id = SnowflakeSwift.shared.nextIDString()
result?(id)
} else if call.method == xxx {
// ......
result?(success)
} else {
result?(FlutterMethodNotImplemented)
}
}
// MARK: page
private func flutterToNavPage(call: FlutterMethodCall, result: FlutterResult? = nil) -> Void {
if call.method == FlutterChannelPageType.chan_gotoNativePage.rawValue {
// 处理 Flutter 调用
// let argument = call.arguments as? String ?? ""
// UIViewController.curNavViewController?.pushViewController(IFIMTestViewController(), animated: true)
UIViewController.curNavViewController?.pushViewController(IFSecondViewController(), animated: true)
result?(nil)
}
}
// MARK: API service
public func api_readUserInfo(completion: @escaping (Error?) -> Void) {
guard let messenger = self.flutterController?.binaryMessenger else {
completion(NSError(domain: "bridge", code: -1, userInfo: [NSLocalizedDescriptionKey: "信息异常"]))
return
}
let userApi = UserApi(binaryMessenger: messenger)
userApi.getUserInfo { result in
switch result {
case .success(let userInfo):
completion(nil)
print("获取用户信息成功:(userInfo)")
// 这里处理 userInfo
case .failure(let error):
print("获取用户信息失败:(error)")
completion(NSError(domain: "bridge", code: -1, userInfo: [NSLocalizedDescriptionKey: error.localizedDescription]))
// 这里处理 error
}
}
}
// MARK: helper
public class func createEngine(name: String, initialRoute: String? = nil) -> FlutterEngine {
let newEngine = FlutterBridgeHelper.engines.makeEngine(withEntrypoint: name, libraryURI: nil, initialRoute: initialRoute)
GeneratedPluginRegistrant.register(with: newEngine)
return newEngine
}
}
extension FlutterBridgeHelper : FlutterStreamHandler {
// MARK: FlutterStreamHandler
func onListen(withArguments arguments: Any?, eventSink events: @escaping FlutterEventSink) -> FlutterError? {
self.eventSink = events
return nil
}
func onCancel(withArguments arguments: Any?) -> FlutterError? {
self.eventSink = nil
return nil
}
}
调用
-
原生跳转Flutter的调用
-
func SwiftToFlutter() { let newEngine = FlutterBridgeHelper.createEngine(name: "main") let flutterController = FlutterViewController(engine: newEngine, nibName: nil, bundle: nil) // 设置初始路由(可选) // flutterViewController.setInitialRoute("/router") // 按需注册MethodChannel等 flutterHelper.flutterRegist(controller: flutterController) flutterController.view.backgroundColor = .white self.navigationController?.pushViewController(flutterController, animated: true) }
-
-
原生主动从Flutter获取数据
-
在上面的FlutterBridgeHelper类中,有一个
api_readUserInfo的方法,这个方法的用途是:原生主动从Flutter获取用户的信息, -
在这个方法中,
UserApi这个类是Flutter通过命令产生的,而不是原生自己定义的。Flutter写好Flutter代码后 通过命令生成Swift/Object-c文件,使用 pigeon 实现 Flutter 与原生通信
-
-
每次跳转FlutterViewController的时候,一般来说都需要注册一次交互方法
-
在APPdelegate启动时,最初就是FlutterViewController,需要注册一次
-
@objc class AppDelegate: FlutterAppDelegate { let flutterHelper = FlutterBridgeHelper() // 应用启动时 override func application( _ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? ) -> Bool { GeneratedPluginRegistrant.register(with: self) let url = launchOptions?[.url] // 获取 FlutterViewController let controller = window?.rootViewController as! FlutterViewController window.rootViewController = nil let nav = IFBaseNavViewController(rootViewController: controller) window?.rootViewController = nav window.makeKeyAndVisible() // ‼️ 这里需要regist flutterHelper.flutterRegist(controller: controller) return super.application(application, didFinishLaunchingWithOptions: launchOptions) } }
-
-
原生跳转FlutterViewController时,需要注册
-
class CustomViewController: UIViewController { let flutterHelper = FlutterBridgeHelper() func SwiftToFlutter() { let newEngine = FlutterBridgeHelper.createEngine(name: "main") let flutterController = FlutterViewController(engine: newEngine, nibName: nil, bundle: nil) // ‼️ 这里需要按需regist flutterHelper.flutterRegist(controller: flutterController) flutterController.view.backgroundColor = .white self.navigationController?.pushViewController(flutterController, animated: true) } }
-
-