iOS与Flutter混合开发

1,051 阅读4分钟

本文通过傻瓜式的图文结合方式,来讲解iOS项目接入Flutter项目的配置过程,以及iOS代码如何与Flutter代码交互,适合初学者。全文是基于自己的学习和理解,通过实际操作实践,整理的纯技术学习笔记,如有不对的地方或者不详细之处,请大家批评指正。

开发环境

  • macOS10.15.5
  • Xcode 11.5
  • Cocoapods 1.9.3
  • Android Studio 3.5.3
  • Flutter 1.12.13

备注:本文假设大家都已安装好iOS和Flutter开发环境。

Flutter项目工程

  • 使用Android Studio新建一个Flutter Module工程,暂且命名为flutter_module。(或者使用命令行新建一个Flutter Module工程)

  • 打开flutter_module工程下的配置文件pubspec.yaml,在文件的头部菜单点击“Packages get”按钮,运行配置。

  • Flutter项目工程完成配置,紧接着配置iOS项目工程。

iOS项目工程

  • 使用Xcode新建一个Single View App,暂且命名为“FlutterApp”,项目保存于flutter_module同一目录下。

  • 在FlutterApp工程目录下,新建一个Podfile文件,用于管理iOS第三方库和Flutter关联配置。

    • 文件目录结构以及Podfile内容如下图所示

    • 以下提供Podfile内容配置代码,可供大家复制粘贴使用。

      	platform :ios, '9.0'
      	use_frameworks!
      
      	##添加如下两行代码,支持flutter混合开发
      	## 该路径是Module工程flutter_module工程的路径,我把它放在与iOS App(FlutterApp)工程同一个目录下
      	##(也可以放在其他目录,只要把路径配置正确即可)
      	flutter_application_path = '../flutter_module/' 
      	## 加载flutter_module的Xcode和pod配置
      	load File.join(flutter_application_path, '.ios', 'Flutter', 'podhelper.rb')
      
      	target 'FlutterApp' do
      
      		pod 'AFNetworking', '~> 2.6'
      		## 加载flutter_module使用到的第三方库
      		install_all_flutter_pods(flutter_application_path)
      
      	end
      
  • 使用macOS自带的终端,切换到Podfile所在的文件目录下,执行“pod install”命令,即可在FlutterApp工程目录下生成FlutterApp.xcworkspace工程。

  • 使用Xcode打开FlutterApp.xcworkspace工程,运行项目,此时混合环境已经配置完成。

iOS与Flutter交互

  • 使用Android Studio打开flutter_module工程,在main.dart文件,编辑代码如下:
	import 'package:flutter/material.dart';
	import 'package:flutter/services.dart';

	void main() => runApp(MyApp());

	class MyApp extends StatelessWidget {
  		// This widget is the root of your application.
  		@override
  		Widget build(BuildContext context) {
    		return MaterialApp(
      			title: 'Flutter Module',
      			theme: ThemeData(
        			primarySwatch: Colors.blue,
      			),
      			home: MyHomePage(),
      			debugShowCheckedModeBanner: false, //隐藏调试模式标签
    		);
  		}
	}

	class MyHomePage extends StatelessWidget {

  		//消息通道(原生代码也需要用到这个通道名称,两者必须命名一致)
  		final MethodChannel _channel = MethodChannel("com.abc.flutter/channel");

  		MyHomePage() {
    		//接收App原生代码端发起的交互请求
    		Future<dynamic> callback(MethodCall call) async {
      			//App原生代码发起的交互:pageInfo请求
      			if (call.method == "pageInfo") {
        			//处理请求
      			}
    		}
    		this._channel.setMethodCallHandler(callback); //配置响应函数
  		}

		//点击了“退出”按钮
  		void _onClickBackButtonHandler() {
    		//flutter发起的交互:点击了导航栏“退出”按钮
    		this._channel.invokeMethod("navigateExit");
  		}

		//点击了“信息”按钮
  		void _onClickInfoButtonHandler() {
    		//flutter发起的交互:点击了导航栏“信息”按钮
    		this._channel.invokeMethod("navigateInfo");
  		}

  		@override
  		Widget build(BuildContext context) {
    		return Scaffold(
      			appBar: AppBar(
        			title: Text("Flutter Page"),
        			leading: IconButton(
          				icon: Icon(Icons.arrow_back),
          				onPressed: _onClickBackButtonHandler,
        			), //导航栏“退出”按钮
        			actions: <Widget>[
          				IconButton(
            				icon: Icon(Icons.info),
            				onPressed: _onClickInfoButtonHandler,
          				)
        			], //导航栏“信息”按钮
      			),
      			body: Center(
                	child: Text("我是flutter_module默认路由页面");
                ), //页面内容
    		);
  		}
	}
    
  • 保存main.dart,无需运行。

  • 使用Xcode打开FlutterApp.xcworkspace工程,在ViewController.m文件,编辑代码如下:

    #import "ViewController.h"
    #import <Flutter/Flutter.h>
    
    @interface ViewController ()
    
    @property (nonatomic, strong) FlutterViewController *flutterVC; 	//flutter视图控制器,展示flutter页面内容
    @property (nonatomic, strong) FlutterMethodChannel *flutterChannel;//消息通道(flutter与原生交互)
    
    @end
    
    @implementation ViewController
    
    - (void)viewDidLoad {
    	[super viewDidLoad];
    	//初始化flutter页面控制器
    	self.flutterVC = [[FlutterViewController alloc] init];
    	//初始化flutter消息通道(消息通道名称必须与flutter_module命名一致)
    	NSObject<FlutterBinaryMessenger> *bm = (NSObject<FlutterBinaryMessenger> *)self.flutterVC;
    	self.flutterChannel = [FlutterMethodChannel methodChannelWithName:@"com.abc.flutter/channel" binaryMessenger:bm];
    	//配置flutter消息通道与原生通讯
    	[self setupFlutterMethodChannel];
    	//添加按钮,打开flutter页面控制器
    	UIButton *btn = [[UIButton alloc] init];
    	[btn setTitle:@"进入flutter页面" forState:UIControlStateNormal];
    	[btn setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
    	[btn addTarget:self action:@selector(onClickFlutterButtonHandler) forControlEvents:UIControlEventTouchUpInside];
    	btn.frame = CGRectMake(0, 0, 200, 45);
    	btn.center = self.view.center;
    	[self.view addSubview:btn];
    }
    
    //点击“进入flutter页面”按钮
    - (void)onClickFlutterButtonHandler {
    	//进入flutter页面,默认进入flutter_module的根页面
    	//[self.flutterVC setInitialRoute:@""]; //可通过setInitialRoute设置flutter模块的某个页面路由,控制进入某个页面
    	[self presentViewController:self.flutterVC animated:true completion:nil];
    }
    
    //设置flutter与iOS原生项目的通讯
    - (void)setupFlutterMethodChannel {
    	__weak typeof(self) weakSelf = self;
    	[self.flutterChannel setMethodCallHandler:^(FlutterMethodCall * _Nonnull call, FlutterResult  _Nonnull result) {
        	__strong typeof(weakSelf) strongSelf = weakSelf;
        	//接收来自flutter页面的交互
        	if ([@"navigateExit" isEqualToString:call.method]) {
            	//接收到flutter_module的“退出”请求
            	[strongSelf.flutterVC dismissViewControllerAnimated:YES completion:nil];
        	} else if ([@"navigateInfo" isEqualToString:call.method]) {
           	//接收到flutter_module的“信息”请求,然后反发起pageInfo请求到flutter_module 
            	[strongSelf.flutterChannel invokeMethod:@"pageInfo" arguments:@{@"name": @"Flutter 中国"}];
        	}
    	}];
    }
    
    @end
    
    
  • 保存代码,编译运行,即可完成简单的iOS与Flutter交互功能。 运行效果如下:

  • flutter_module工程的代码,每次编辑后,只需保存即可,然后需要在Xcode中打开FlutterApp工程代码重新编译并运行,才能生效。