🙌

FlutterAppをiOS Frameworkビルドして別プロジェクトにSPMで導入する

2023/10/19に公開

タイトルの通りです

FlutterアプリをネイティブのiOSで表示したかった。
cocoapods ではなく今は SwiftPackageManager が主流なのでそちらを利用する。

つまり 『Add-to-app』 を別パッケージに対して行おう。ということです。

環境: Flutter v3.13.6

ネイティブアプリで参照するまでのやり方

iOSFramework作成 -> Github Release Assets にアップロード -> Package.swift 作成

1. Flutterアプリを作成する

flutter create -i swift spm_practice_app
cd spm_practice_app

独自のパッケージ参照が必要ならこれに導入する

2. iOS Framework を作成する

# iOSのビルド周りを整備するために一応やっておく
flutter precache --ios

# iOS Framework を作成. 
# Build Configuration が全てつくられてしまうので今回はReleaseのみ
flutter build ios-framework --no-debug --no-profile

# つくられたか確認
ls build/ios/framework/Release
> App.xcframework         Flutter.xcframework

# zipファイルを作成
cd build/ios/framework/Release
zip -r App.xcframework.zip App.xcframework/
zip -r Flutter.xcframework.zip Flutter.xcframework/

# zipファイルのchecksum を調べて覚えておく
swift package compute-checksum App.xcframework.zip
> xxxxx
swift package compute-checksum Flutter.xcframework.zip
> yyyyy

3. Githubでリリースする

作成した .xcframework をリリースに載せる.
Private Repository でもOK
Image

4. Package.swift を作成してGithubにPushする

AppPackage, {owner}, {repo}, {tag} は適宜置き換えてください.

Package.swift はリポジトリのルートに置いておくと楽

// swift-tools-version:5.9
import PackageDescription

let package = Package(
    name: "AppPackage",
    platforms: [.iOS(.v14)],
    products: [
        .library(
            name: "AppPackage",
            targets: ["App", "Flutter"]
        )
    ],
    targets: [
        .binaryTarget(
            name: "App",
            url: "https://github.com/{owner}/{repo}/releases/download/{tag}/App.xcframework.zip",
            checksum: "xxxxx"
        ),
        .binaryTarget(
            name: "Flutter",
            url: "https://github.com/{owner}/{repo}/releases/download/{tag}/Flutter.xcframework.zip",
            checksum: "yyyyy"
        )
    ],
    swiftLanguageVersions: [.v5]
)

5. iOSネイティブアプリにSPMでPackage.swiftがあるリポジトリを参照させる

Package.swift の name で指定した名前が表示されていればOK

Image

Frameworkの参照を追加
Image2

Flutterの画面を呼んでみよう

1. Import できるか確認

これでビルドしてみましょう。

// AppDelegate
import UIKit
import Flutter

@main
class AppDelegate: UIResponder, UIApplicationDelegate {
    var window: UIWindow?
    
    var flutterEngine: FlutterEngine?

    func application(
        _ application: UIApplication,
        didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
    ) -> Bool {
        self.flutterEngine = FlutterEngine(name: "Practice Flutter Engine")
        self.flutterEngine?.run()
        return true
    }
}

2. 画面を呼ぶ

UIKit+Storyboardで失礼します

import UIKit
import Flutter

class MainViewController: UIViewController {
    @IBAction func onTap(_ sender: Any) {
        if let app = UIApplication.shared.delegate as? AppDelegate,
           let flutterEngine = app.flutterEngine {
            let vc = FlutterViewController(engine: flutterEngine, nibName: nil, bundle: nil)
            present(vc, animated: true)
        }
    }
}

ボタンを押したら表示されて終わり.

Discussion