flutter 與iOS混合開發


在Flutter項目開發中有時候有些常用的框架沒有Flutter版本,這樣的場景就需要接入原生sdk並完成與原生sdk通訊 這里主要講解如何實現與iOS的混合開發
大致思路就是創建Flutter_module項目,並將Flutter項目以及引用的第三方庫編譯成靜態Framework並在iOS中通過pod的方式引入

第一步:創建一個原生的iOS工程
1.創建一個空文件夾 名字叫 flutter_iOS_Mixture
2.在flutter_iOS_Mixture文件夾中創建XCode工程,並在工程中執行

pod init
pod install

第二步:創建Flutter_Module
1.定位到flutter_iOS_Mixture文件夾目錄,並在終端執行命令,創建flutter module

flutter create -t module flutter_project


執行完畢后,工程中的目錄結構
2.查看目錄結構查看隱藏文件請使用快捷鍵打開隱藏文件

command + shift + .

目錄結構

.
├── flutter_project
│   ├── README.md
│   ├── flutter_project.iml
│   ├── flutter_project_android.iml
│   ├── lib
│   ├── pubspec.lock
│   ├── pubspec.yaml
│   └── test
└── iOS_App
    ├── iOS_App
    ├── iOS_App.xcodeproj
    ├── iOS_AppTests
    └── iOS_AppUITests

3.打開Flutter工程,並在pubspec.yaml文件中添加兩個第三方框架 執行 pub get

  cupertino_icons: ^0.1.2
  webview_flutter: ^0.3.19+9
  url_launcher: ^5.1.2

第三步:將Flutter編譯成靜態Framework並引用到iOS工程中
這里就有個分支了兩種解決方案 1種是直接在iOS中添加依賴,就可以實現Flutter與iOS的混合,操作簡單,但是有個缺點就是如果是多人開發項目的話,直接引入,需要每個開發者都需要有Flutter環境才可以正常編譯通過,否則會報錯,這樣侵入性太強,但是如果開發人數少,使用這種方式確實可以提升開發效率(不能每次修改Flutter內容后都需要重新將Flutter打包成Framework,節約了不少時間),這也是蘋果官方推薦使用的解決方案
直接在Podfile文件中加入如果內容,Flutter與iOS的橋接就算完成了

flutter_application_path = '../flutter_project/'
load File.join(flutter_application_path, '.ios', 'Flutter', 'podhelper.rb')
install_all_flutter_pods(flutter_application_path)

全部文件如下:

# Uncomment the next line to define a global platform for your project
# platform :ios, '9.0'
flutter_application_path = '../flutter_project/'

load File.join(flutter_application_path, '.ios', 'Flutter', 'podhelper.rb')

target 'iOS_App' do
  # Comment the next line if you don't want to use dynamic frameworks
  use_frameworks!
  
  install_all_flutter_pods(flutter_application_path)

  # Pods for iOS_App

  target 'iOS_AppTests' do
    inherit! :search_paths
    # Pods for testing
  end

  target 'iOS_AppUITests' do
    # Pods for testing
  end

end

執行pod install將Flutter引入到iOS項目中

chenhaodeMac:iOS_App chenhao$ pod install
Analyzing dependencies
Downloading dependencies
Installing FlutterPluginRegistrant 0.0.1
Installing url_launcher (0.0.1)
Installing url_launcher_macos (0.0.1)
Installing url_launcher_web (0.0.1)
Installing webview_flutter (0.0.1)
Generating Pods project
Integrating client project
Pod installation complete! There are 7 dependencies from the Podfile and 7 total pods installed.

查看XCode工程發現導入的Flutter庫也被加入到了iOS中

以上是Flutter與iOS橋接的第一種方式

2.將Flutter作為一個組件加入到iOS工程中,這樣需要使用的時候,直接通過pod導入就行了,這樣的好處是任何人都可以導入該插件,不需要電腦中裝有Flutter環境,但是這種方式橋接操作相對繁雜,Flutter項目中內容有修改,需要重新打包並提交iOS工程中才可以生效,下面主要介紹這種方式如何實現與iOS的橋接
1> 創建一個Pod庫在flutter_iOS_Mixture根目錄執行命令創建pod lib

pod lib create flutter_lib
chenhaodeMac:flutter_iOS_ Mixture chenhao$ pod lib create flutter_lib
Cloning `https://github.com/CocoaPods/pod-template.git` into `flutter_lib`.
Configuring flutter_lib template.

To get you started we need to ask a few questions, this should only take a minute.

If this is your first time we recommend running through with the guide: 
 - https://guides.cocoapods.org/making/using-pod-lib-create.html
 ( hold cmd and double click links to open in a browser. )


What platform do you want to use?? [ iOS / macOS ]
 > iOS

What language do you want to use?? [ Swift / ObjC ]
 > ObjC

Would you like to include a demo application with your library? [ Yes / No ]
 > NO

Which testing frameworks will you use? [ Specta / Kiwi / None ]
 > None

Would you like to do view based testing? [ Yes / No ]
 > No 

What is your class prefix?
 > ASS 

Running pod install on your new library.

Analyzing dependencies
Downloading dependencies
Installing flutter_lib (0.1.0)
Generating Pods project
Integrating client project

[!] Please close any current Xcode sessions and use `flutter_lib.xcworkspace` for this project from now on.
Pod installation complete! There is 1 dependency from the Podfile and 1 total pod installed.
Ace! you're ready to go!

此時的目錄結構如下

.
├── flutter_lib
│   ├── Example
│   ├── LICENSE
│   ├── README.md
│   ├── _Pods.xcodeproj -> Example/Pods/Pods.xcodeproj
│   ├── flutter_lib
│   └── flutter_lib.podspec
├── flutter_project
│   ├── README.md
│   ├── flutter_project.iml
│   ├── flutter_project_android.iml
│   ├── lib
│   ├── pubspec.lock
│   ├── pubspec.yaml
│   └── test
└── iOS_App
    ├── Podfile
    ├── Podfile.lock
    ├── Pods
    ├── iOS_App
    ├── iOS_App.xcodeproj
    ├── iOS_App.xcworkspace
    ├── iOS_AppTests
    └── iOS_AppUITests

在flutter_lib中創建ios_frameworks文件夾用來存放Flutter編譯后的靜態文件
找到flutter_lib中flutter_lib.podspec找打並修改引用 在文件最后添加如下一段代碼

  s.ios.deployment_target = '8.0'

  s.static_framework = true
  p = Dir::open("ios_frameworks")
  arr = Array.new
  arr.push('ios_frameworks/*.framework')
  s.ios.vendored_frameworks = arr

  #s.source_files = 'flutter_lib/Classes/**/*'
  
  # s.resource_bundles = {
  #   'flutter_lib' => ['flutter_lib/Assets/*.png']
  # }

  # s.public_header_files = 'Pod/Classes/**/*.h'
  # s.frameworks = 'UIKit', 'MapKit'
  # s.dependency 'AFNetworking', '~> 2.3'

下面開始執行一段腳本 將Flutter編譯並打包,將生成的frameworks自動移入到flutter_lib中的ios_frameworks中,這個ios_frameworks也正好是剛剛修改的flutter_lib.podspec引入的路徑,將腳本放在flutter項目根目錄中,腳本內容

if [ -z $out ]; then
    out='ios_frameworks'
fi

echo "准備輸出所有文件到目錄: $out"

echo "清除所有已編譯文件"
find . -d -name build | xargs rm -rf
flutter clean
rm -rf $out
rm -rf build

flutter packages get

addFlag(){
    cat .ios/Podfile > tmp1.txt
    echo "use_frameworks!" >> tmp2.txt
    cat tmp1.txt >> tmp2.txt
    cat tmp2.txt > .ios/Podfile
    rm tmp1.txt tmp2.txt
}

echo "檢查 .ios/Podfile文件狀態"
a=$(cat .ios/Podfile)
if [[ $a == use* ]]; then
    echo '已經添加use_frameworks, 不再添加'
else
    echo '未添加use_frameworks,准備添加'
    addFlag
    echo "添加use_frameworks 完成"
fi

echo "編譯flutter"
flutter build ios --debug --no-codesign
#flutter build ios --release --no-codesign

echo "編譯flutter完成"
mkdir $out

cp -r build/ios/Debug-iphoneos/*/*.framework $out
#cp -r build/ios/Release-iphoneos/*/*.framework $out
cp -r .ios/Flutter/App.framework $out
cp -r .ios/Flutter/engine/Flutter.framework $out

echo "復制framework庫到臨時文件夾: $out"

libpath='../flutter_lib/'

rm -rf "$libpath/ios_frameworks"
mkdir $libpath
cp -r $out $libpath

echo "復制庫文件到: $libpath"

執行腳本后發現flutter_lib中的ios_frameworks中多了一些flutter的使用的庫文件

sh build_ios.sh

在podfile文件中引入組件化的flutter庫

  pod 'flutter_lib', :path => '../flutter_lib'

執行pod install

chenhaodeMac:iOS_App chenhao$ pod install
Analyzing dependencies
Downloading dependencies
Installing flutter_lib (0.1.0)
Generating Pods project
Integrating client project
Pod installation complete! There is 1 dependency from the Podfile and 1 total pod installed.

此時Flutter與iOS的第二種橋接方式算是操作完了,此時flutter_lib已經通過pod引入到了項目中

第四步:iOS與Flutter互相通訊

1.iOS中調用Flutter工程

//初始化FlutterViewController
self.flutterViewController = [[FlutterViewController alloc] init];
//這里可以傳遞參數用來控制flutter做一些操作
[self.flutterViewController setInitialRoute:@"{\"msg\":\"我是iOS傳入的指令\"}"];
self.flutterViewController.modalPresentationStyle = UIModalPresentationFullScreen;
[self presentViewController:self.flutterViewController animated:YES completion:nil];

2.iOS與flutter通訊
通訊方式共有三種
- BasicMessageChannel通用數據傳輸,全雙工,實時傳遞
- MethodChannel方法傳遞通道,傳遞只執行一次 全雙工
- EventChannel事件監聽通道持續監聽如果電池電量的監聽

這里只寫MethodChannel寫幾個方法實現flutter與iOS的方法互調
在iOS中首先要創建消息通道並初始化通道名,這樣后面所有消息都通過這個通道名對應的通道傳遞

//初始化通道
FlutterMethodChannel *methodChannel = [FlutterMethodChannel methodChannelWithName:@"MSGChannel" binaryMessenger:self.flutterViewController.binaryMessenger];
self.methodChannel = methodChannel;

//通過block回調監聽通道中來自flutter的消息體 這里做一個dismiss方法,由於iOS中將flutter頁面push出來,次數實現dismiss方法,給flutter發送dismss消息,就知道是讓iOS將當前頁面關閉的動作,iOS收到后,執行關閉操作
__weak typeof(self) weakself = self;
[methodChannel setMethodCallHandler:^(FlutterMethodCall * _Nonnull call, FlutterResult  _Nonnull result) {
    __strong typeof(weakself) strongself = weakself;
    //dissmiss當前頁面
    if([call.method isEqualToString:@"dismiss"]){
        [strongself dismissViewControllerAnimated:YES completion:nil];
    }
    if (result) {
        result(@"成功關閉頁面");
    }
}];

//iOS中也可以主動給Flutter發消息通過invokeMethod 只需要注意消息通道名要跟初始化保持一致
[self.methodChannel invokeMethod:@"MSGChannel" arguments:@"我是iOS發送過來的消息"];

在flutter中,首先要在main方法中通過window.defaultRouteName的方式獲取iOS中傳入的Route參數
flutter中同樣需要創建消息通道

//創建消息通道並初始化消息名 這個名字要與iOS對應
 static const MethodChannel methodChannel = MethodChannel('MSGChannel');

//設置消息監聽
methodChannel.setMethodCallHandler((MethodCall call){
  //接收到消息
  print(call.method);
  print(call.arguments);
  return Future.value(1);
});

//發送消息通過invokeMethod方法
methodChannel.invokeMethod('dismiss');

flutter中完整代碼如下

import 'dart:ui';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';

void main() => runApp(MyApp(route: window.defaultRouteName));

class MyApp extends StatefulWidget {
  String route;
  MyApp({@required this.route});
  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  @override
  void initState() {
    super.initState();
    //收到iOS中傳入指令
    print(widget.route);
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: HomePage(),
    );
  }
}

class HomePage extends StatefulWidget {
  @override
  _HomePageState createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {

//創建消息通道並初始化消息名 這個名字要與iOS對應
 static const MethodChannel methodChannel = MethodChannel('MSGChannel');
 
  @override
  void initState() {
    super.initState();

    //設置消息監聽
    methodChannel.setMethodCallHandler((MethodCall call){
      //接收到消息
      print(call.method);
      print(call.arguments);
      return Future.value(true);
    });

  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('iOS與Flutter通訊'),
      ),
      body: Center(
        child: GestureDetector(
          onTap: () {
            //發送消息通過invokeMethod方法
             methodChannel.invokeMethod('dismiss');
          },
          child: Container(
            alignment: Alignment.center,
            color: Colors.red,
            width: 100,
            height: 40,
            child: Text(
              '點擊返回iOS',
              style: TextStyle(
                color: Colors.white,
              ),
            ),
          ),
        ),
      ),
    );
  }
}

最終效果圖如下:

需要特別注意一點:當flutter中內容修改后,需要重新執行sh腳本,將flutter重新打包成framework,在iOS中操作才會有效,要不然改動后,iOS中還是使用的之前的老版本

demo放在github中,如果需要請自取:https://github.com/qqcc1388/flutter_iOS_Mixture

參考來源:

https://juejin.im/post/5e228d21518825265c248e7b
https://blog.csdn.net/qq_28478281/article/details/92416686
https://www.jianshu.com/p/c1034513be13

轉載請標注出處https://www.cnblogs.com/qqcc1388/p/12693991.html和參考源 謝謝!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM