Swift➤Flutter#

Swift➤Flutter#

一、前言#

1、实践目的:iOS项目主工程(Swift)集成和在个别页面调用Flutter页面#

2、经验总结#

  • Flutter官方文档 ➤ 将 Flutter module 集成到 iOS 项目
  • 集成Flutter页面,绝对不是只引入单个的*.dart文件,而是需要将整个Flutter工程文件全部集成(Flutter 里面还包含各种依赖)
  • iOS主工程项目里面的Podfile里面,仅仅是做钩子,勾住Flutter项目里面的配置。而此文件并不直接写pod Flutter
  • 需要安装Flutter环境
  • 必须进入Flutter目录中执行flutter pub get生成中间产物podhelper.rb才能跑通pod install
  • 关于iOS程序的入口配置,会因为Flutter的出现,有一点小变化,这里总结出一个通例

二、集成步骤#

1、目录结构#

需要进入Flutter工程目录 ➤ 执行 flutter build ios --config-only ➤ 生成文件 flutter_lldbinit

image-20251216091201562

2、Product ➤ Scheme ➤ Edit Scheme#

image-20251216091324201

3、配置 Podfilepod install#

# ================================== Podfile ==================================
ENV['COCOAPODS_DISABLE_STATS'] = 'true'

# ⚠️ 与 post_install 保持一致
platform :ios, '15.6'

#source 'https://cdn.cocoapods.org/'
source 'https://github.com/CocoaPods/Specs.git'
# pod install --repo-update

# 关键:恢复这段,避免 Assets.car 重复产物冲突
install! 'cocoapods',
  :deterministic_uuids => false,
  :disable_input_output_paths => true
# 如需的话,你也可以加上(可选):
# , :generate_multiple_pod_projects => false

use_frameworks! :linkage => :static
inhibit_all_warnings!

# Flutter module
FLUTTER_APPLICATION_PATH = File.expand_path('./my_flutter', __dir__)
flutter_podhelper = File.join(FLUTTER_APPLICATION_PATH, '.ios', 'Flutter', 'podhelper.rb')
unless File.exist?(flutter_podhelper)
  raise "[Podfile] ❌ 找不到 #{flutter_podhelper},请确认 Flutter module 路径正确:#{FLUTTER_APPLICATION_PATH}"
end
load flutter_podhelper

# 预留钩子,给 Podfile.deps 调用
def cocoPodsConfig
  # 按需扩展
end

# 加载拆分出来的依赖定义
deps_path = File.join(__dir__, 'Podfile.deps')
unless File.exist?(deps_path)
  raise "[Podfile] ❌ 找不到 #{deps_path},请确认 Podfile.deps 存在于工程根目录"
end
instance_eval(File.read(deps_path), deps_path, 1)

# 统一工程设置 & 把 Podfile.deps 显示到 Pods 分组里
post_install do |installer|
  flutter_post_install(installer) if defined?(flutter_post_install)
  # -------- 1、宿主 App 工程设置 --------
  installer.aggregate_targets.each do |agg|
    user_project = agg.user_project
    user_project.native_targets.each do |t|
      t.build_configurations.each do |config|
        config.build_settings['ENABLE_USER_SCRIPT_SANDBOXING'] = 'NO'
        config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '15.0'
      end
    end
    user_project.save
  end
  # -------- 2、Pods 工程最低系统版本统一 --------
  pods_project = installer.pods_project
  pods_project.targets.each do |t|
    t.build_configurations.each do |config|
      # MacOS 15 以后,苹果采取了更加严格的权限写入机制。新Swift项目如果要利用Cocoapods来集成第三方,就需要将ENABLE_USER_SCRIPT_SANDBOXING设置为NO
      config.build_settings['ENABLE_USER_SCRIPT_SANDBOXING'] = 'NO'
#      config.build_settings['ENABLE_USER_SCRIPT_SANDBOXING'] = '$(inherited)'
      config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '15.0'
    end
  end
  # -------- 3、在 Pods 分组里展示 Podfile.deps(Ruby 语法高亮) --------
  main_group   = pods_project.main_group
  deps_relpath = '../Podfile.deps'
  file_ref = main_group.find_file_by_path(deps_relpath) || main_group.new_file(deps_relpath)
  if file_ref.respond_to?(:explicit_file_type=)
    file_ref.explicit_file_type = 'text.script.ruby'
  end
  pods_project.save
end
# ============================== 目标工程(新工程记得改名) ==============================
# ❤️ 新工程需要修改这里:把目标名替换为你的实际 target 名字
target 'JobsSwiftBaseConfigDemo' do
  use_frameworks! :linkage => :static

  flutter_application_path = File.join(__dir__, 'my_flutter')
  load File.join(flutter_application_path, '.ios', 'Flutter', 'podhelper.rb')
  install_all_flutter_pods(flutter_application_path)

	/// TODO
end

4、iOS侧的入口配置#

//
//  AppDelegate.swift
//  JobsSwiftBaseConfigDemo
//
//  Created by Jobs on 2025/6/4.
//

#if os(OSX)
import AppKit
#elseif os(iOS) || os(tvOS)
import UIKit
#endif

if canImport(Flutter)
import Flutter
import FlutterPluginRegistrant
@main
class AppDelegate: FlutterAppDelegate{
    lazy var flutterEngine: FlutterEngine = {
        let e = FlutterEngine(name: "jobs_flutter_engine")
        e.run()
        GeneratedPluginRegistrant.register(with: e)
        FlutterBridge.shared.setup(engine: e)
        return e
    }()
    override func application(
        _ application: UIApplication,
        didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
    ) -> Bool {
        SA()
        return super.application(application, didFinishLaunchingWithOptions: launchOptions)
    }
    // MARK: UISceneSession Lifecycle
    override func application(
        _ application: UIApplication,
        configurationForConnecting connectingSceneSession: UISceneSession,
        options: UIScene.ConnectionOptions
    ) -> UISceneConfiguration {
        return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
    }
}

#else
@main
class AppDelegate: UIResponder, UIApplicationDelegate {
    lazy var flutterEngine = FlutterEngine(name: "my flutter engine")
    override func application(
        _ application: UIApplication,
        didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
    ) -> Bool {
        SA()
        return true
    }
    // MARK: UISceneSession Lifecycle
    override func application(
        _ application: UIApplication,
        configurationForConnecting connectingSceneSession: UISceneSession,
        options: UIScene.ConnectionOptions
    ) -> UISceneConfiguration {
        return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
    }
}

#endif

extension AppDelegate {
    func SA(){
      _ = flutterEngine
	    /// TODO
    }
}