插件作者的 Swift Package Manager
Flutter 的 Swift Package Manager 集成具有多个优势
- 访问 Swift 包生态系统。Flutter 插件可以使用不断增长的 Swift 包 生态系统!
- 简化 Flutter 安装。Swift Package Manager 与 Xcode 捆绑在一起。将来,您无需安装 Ruby 和 CocoaPods 即可定位 iOS 或 macOS。
如果您在 Flutter 的 Swift Package Manager 支持中发现错误,请 提交问题。
如何开启 Swift Package Manager
#默认情况下,Flutter 的 Swift Package Manager 支持处于关闭状态。要开启它
切换到 Flutter 的
main
渠道shflutter channel main --no-cache-artifacts
升级到最新的 Flutter SDK 并下载工件
shflutter upgrade
开启 Swift Package Manager 功能
shflutter config --enable-swift-package-manager
使用 Flutter CLI 运行应用会 迁移项目 以添加 Swift Package Manager 集成。这使得您的项目下载 Flutter 插件依赖的 Swift 包。具有 Swift Package Manager 集成的应用需要 Flutter 版本 3.24 或更高版本。要使用旧版本的 Flutter,您需要 从应用中移除 Swift Package Manager 集成。
对于尚不支持 Swift Package Manager 的依赖项,Flutter 会回退到 CocoaPods。
如何关闭 Swift Package Manager
#禁用 Swift Package Manager 会导致 Flutter 对所有依赖项使用 CocoaPods。但是,Swift Package Manager 仍然与您的项目集成。要完全从项目中移除 Swift Package Manager 集成,请按照 如何移除 Swift Package Manager 集成 的说明操作。
为单个项目关闭
#在项目的 pubspec.yaml
文件中,在 flutter
部分下,添加 disable-swift-package-manager: true
。
# The following section is specific to Flutter packages.
flutter:
disable-swift-package-manager: true
这将为该项目的所有贡献者关闭 Swift Package Manager。
全局关闭所有项目
#运行以下命令
flutter config --no-enable-swift-package-manager
这将为当前用户关闭 Swift Package Manager。
如果项目与 Swift Package Manager 不兼容,则所有贡献者都需要运行此命令。
如何为现有 Flutter 插件添加 Swift Package Manager 支持
#本指南演示如何为一个已经支持 CocoaPods 的插件添加 Swift Package Manager 支持。这确保了插件可被所有 Flutter 项目使用。
在另行通知之前,Flutter 插件应同时支持 Swift Package Manager 和 CocoaPods。
Swift Package Manager 的采用将是渐进式的。不支持 CocoaPods 的插件将无法被尚未迁移到 Swift Package Manager 的项目使用。不支持 Swift Package Manager 的插件可能会导致已迁移项目的出现问题。
在本指南中,将 plugin_name
替换为您的插件名称。以下示例使用 ios
,请根据需要将 ios
替换为 macos
/darwin
。
首先在
ios
、macos
和/或darwin
目录下创建一个目录。将此新目录命名为平台包的名称。plugin_name/ios/ ├── ... └── plugin_name/
在此新目录中,创建以下文件/目录
Package.swift
(文件)Sources
(目录)Sources/plugin_name
(目录)
您的插件应如下所示
plugin_name/ios/ ├── ... └── plugin_name/ ├── Package.swift └── Sources/plugin_name/
在
Package.swift
文件中使用以下模板Package.swiftswift// swift-tools-version: 5.9 // The swift-tools-version declares the minimum version of Swift required to build this package. import PackageDescription let package = Package( // TODO: Update your plugin name. name: "plugin_name", platforms: [ // TODO: Update the platforms your plugin supports. // If your plugin only supports iOS, remove `.macOS(...)`. // If your plugin only supports macOS, remove `.iOS(...)`. .iOS("12.0"), .macOS("10.14") ], products: [ // TODO: Update your library and target names. // If the plugin name contains "_", replace with "-" for the library name. .library(name: "plugin-name", targets: ["plugin_name"]) ], dependencies: [], targets: [ .target( // TODO: Update your target name. name: "plugin_name", dependencies: [], resources: [ // TODO: If your plugin requires a privacy manifest // (e.g. if it uses any required reason APIs), update the PrivacyInfo.xcprivacy file // to describe your plugin's privacy impact, and then uncomment this line. // For more information, see: // https://developer.apple.com/documentation/bundleresources/privacy_manifest_files // .process("PrivacyInfo.xcprivacy"), // TODO: If you have other resources that need to be bundled with your plugin, refer to // the following instructions to add them: // https://developer.apple.com/documentation/xcode/bundling-resources-with-a-swift-package ] ) ] )
更新
Package.swift
文件中的 支持的平台。Package.swiftswiftplatforms: [ // TODO: Update the platforms your plugin supports. // If your plugin only supports iOS, remove `.macOS(...)`. // If your plugin only supports macOS, remove `.iOS(...)`. .iOS("12.0"), .macOS("10.14") ],
更新
Package.swift
文件中的包、库和目标名称。Package.swiftswiftlet package = Package( // TODO: Update your plugin name. name: "plugin_name", platforms: [ .iOS("12.0"), .macOS("10.14") ], products: [ // TODO: Update your library and target names. // If the plugin name contains "_", replace with "-" for the library name .library(name: "plugin-name", targets: ["plugin_name"]) ], dependencies: [], targets: [ .target( // TODO: Update your target name. name: "plugin_name", dependencies: [], resources: [ // TODO: If your plugin requires a privacy manifest // (e.g. if it uses any required reason APIs), update the PrivacyInfo.xcprivacy file // to describe your plugin's privacy impact, and then uncomment this line. // For more information, see: // https://developer.apple.com/documentation/bundleresources/privacy_manifest_files // .process("PrivacyInfo.xcprivacy"), // TODO: If you have other resources that need to be bundled with your plugin, refer to // the following instructions to add them: // https://developer.apple.com/documentation/xcode/bundling-resources-with-a-swift-package ] ) ] )
如果您的插件具有
PrivacyInfo.xcprivacy
文件,请将其移动到ios/plugin_name/Sources/plugin_name/PrivacyInfo.xcprivacy
并取消Package.swift
文件中资源的注释。Package.swiftswiftresources: [ // TODO: If your plugin requires a privacy manifest // (e.g. if it uses any required reason APIs), update the PrivacyInfo.xcprivacy file // to describe your plugin's privacy impact, and then uncomment this line. // For more information, see: // https://developer.apple.com/documentation/bundleresources/privacy_manifest_files .process("PrivacyInfo.xcprivacy"), // TODO: If you have other resources that need to be bundled with your plugin, refer to // the following instructions to add them: // https://developer.apple.com/documentation/xcode/bundling-resources-with-a-swift-package ],
将任何资源文件从
ios/Assets
移动到ios/plugin_name/Sources/plugin_name
(或子目录)。如果适用,请将资源文件添加到Package.swift
文件中。有关更多说明,请参阅 https://developer.apple.com/documentation/xcode/bundling-resources-with-a-swift-package。将所有文件从
ios/Classes
移动到ios/plugin_name/Sources/plugin_name
。ios/Assets
、ios/Resources
和ios/Classes
目录现在应该为空,可以删除。如果您的插件使用 Pigeon,请更新您的 Pigeon 输入文件。
pigeons/messages.dartdartkotlinOptions: KotlinOptions(), javaOut: 'android/app/src/main/java/io/flutter/plugins/Messages.java', javaOptions: JavaOptions(), swiftOut: 'ios/Classes/messages.g.swift', swiftOut: 'ios/plugin_name/Sources/plugin_name/messages.g.swift', swiftOptions: SwiftOptions(),
使用您可能需要的任何自定义项更新
Package.swift
文件。在 Xcode 中打开
ios/plugin_name/
目录。在 Xcode 中,打开
Package.swift
文件。验证 Xcode 是否没有为此文件生成任何警告或错误。如果您的
ios/plugin_name.podspec
文件具有 CocoaPodsdependency
,请将相应的 Swift Package Manager 依赖项 添加到Package.swift
文件中。如果您的包必须显式链接
static
或dynamic
(Apple 不推荐),请更新 Product 以定义类型Package.swiftswiftproducts: [ .library(name: "plugin-name", type: .static, targets: ["plugin_name"]) ],
进行任何其他自定义。有关如何编写
Package.swift
文件的更多信息,请参阅 https://developer.apple.com/documentation/packagedescription。
更新
ios/plugin_name.podspec
以指向新路径。ios/plugin_name.podspecrubys.source_files = 'Classes/**/*.swift' s.resource_bundles = {'plugin_name_privacy' => ['Resources/PrivacyInfo.xcprivacy']} s.source_files = 'plugin_name/Sources/plugin_name/**/*.swift' s.resource_bundles = {'plugin_name_privacy' => ['plugin_name/Sources/plugin_name/PrivacyInfo.xcprivacy']}
更新从 bundle 加载资源以使用
Bundle.module
。swift#if SWIFT_PACKAGE let settingsURL = Bundle.module.url(forResource: "image", withExtension: "jpg") #else let settingsURL = Bundle(for: Self.self).url(forResource: "image", withExtension: "jpg") #endif
将插件的更改提交到您的版本控制系统。
验证插件是否仍与 CocoaPods 兼容。
关闭 Swift Package Manager。
shflutter config --no-enable-swift-package-manager
导航到插件的示例应用。
shcd path/to/plugin/example/
确保插件的示例应用可以构建并运行。
shflutter run
导航到插件的顶级目录。
shcd path/to/plugin/
运行 CocoaPods 验证 lint。
shpod lib lint ios/plugin_name.podspec --configuration=Debug --skip-tests --use-modular-headers --use-libraries
shpod lib lint ios/plugin_name.podspec --configuration=Debug --skip-tests --use-modular-headers
验证插件是否与 Swift Package Manager 兼容。
开启 Swift Package Manager。
shflutter config --enable-swift-package-manager
导航到插件的示例应用。
shcd path/to/plugin/example/
确保插件的示例应用可以构建并运行。
shflutter run
在 Xcode 中打开插件的示例应用。确保左侧的**项目导航器**中显示了**包依赖项**。
验证测试是否通过。
如果您的插件具有本地单元测试 (XCTest),请确保您还 更新插件示例应用中的单元测试。
按照 测试插件 的说明操作。
在本指南中,将 plugin_name
替换为您的插件名称。以下示例使用 ios
,请根据需要将 ios
替换为 macos
/darwin
。
首先在
ios
、macos
和/或darwin
目录下创建一个目录。将此新目录命名为平台包的名称。plugin_name/ios/ ├── ... └── plugin_name/
在此新目录中,创建以下文件/目录
Package.swift
(文件)Sources
(目录)Sources/plugin_name
(目录)Sources/plugin_name/include
(目录)Sources/plugin_name/include/plugin_name
(目录)Sources/plugin_name/include/plugin_name/.gitkeep
(文件)- 此文件确保提交目录。如果将其他文件添加到目录中,可以删除
.gitkeep
文件。
- 此文件确保提交目录。如果将其他文件添加到目录中,可以删除
您的插件应如下所示
plugin_name/ios/ ├── ... └── plugin_name/ ├── Package.swift └── Sources/plugin_name/include/plugin_name/ └── .gitkeep
在
Package.swift
文件中使用以下模板Package.swiftswift// swift-tools-version: 5.9 // The swift-tools-version declares the minimum version of Swift required to build this package. import PackageDescription let package = Package( // TODO: Update your plugin name. name: "plugin_name", platforms: [ // TODO: Update the platforms your plugin supports. // If your plugin only supports iOS, remove `.macOS(...)`. // If your plugin only supports macOS, remove `.iOS(...)`. .iOS("12.0"), .macOS("10.14") ], products: [ // TODO: Update your library and target names. // If the plugin name contains "_", replace with "-" for the library name .library(name: "plugin-name", targets: ["plugin_name"]) ], dependencies: [], targets: [ .target( // TODO: Update your target name. name: "plugin_name", dependencies: [], resources: [ // TODO: If your plugin requires a privacy manifest // (e.g. if it uses any required reason APIs), update the PrivacyInfo.xcprivacy file // to describe your plugin's privacy impact, and then uncomment this line. // For more information, see: // https://developer.apple.com/documentation/bundleresources/privacy_manifest_files // .process("PrivacyInfo.xcprivacy"), // TODO: If you have other resources that need to be bundled with your plugin, refer to // the following instructions to add them: // https://developer.apple.com/documentation/xcode/bundling-resources-with-a-swift-package ], cSettings: [ // TODO: Update your plugin name. .headerSearchPath("include/plugin_name") ] ) ] )
更新
Package.swift
文件中的 支持的平台。Package.swiftswiftplatforms: [ // TODO: Update the platforms your plugin supports. // If your plugin only supports iOS, remove `.macOS(...)`. // If your plugin only supports macOS, remove `.iOS(...)`. .iOS("12.0"), .macOS("10.14") ],
更新
Package.swift
文件中的包、库和目标名称。Package.swiftswiftlet package = Package( // TODO: Update your plugin name. name: "plugin_name", platforms: [ .iOS("12.0"), .macOS("10.14") ], products: [ // TODO: Update your library and target names. // If the plugin name contains "_", replace with "-" for the library name .library(name: "plugin-name", targets: ["plugin_name"]) ], dependencies: [], targets: [ .target( // TODO: Update your target name. name: "plugin_name", dependencies: [], resources: [ // TODO: If your plugin requires a privacy manifest // (e.g. if it uses any required reason APIs), update the PrivacyInfo.xcprivacy file // to describe your plugin's privacy impact, and then uncomment this line. // For more information, see: // https://developer.apple.com/documentation/bundleresources/privacy_manifest_files // .process("PrivacyInfo.xcprivacy"), // TODO: If you have other resources that need to be bundled with your plugin, refer to // the following instructions to add them: // https://developer.apple.com/documentation/xcode/bundling-resources-with-a-swift-package ], cSettings: [ // TODO: Update your plugin name. .headerSearchPath("include/plugin_name") ] ) ] )
如果您的插件具有
PrivacyInfo.xcprivacy
文件,请将其移动到ios/plugin_name/Sources/plugin_name/PrivacyInfo.xcprivacy
并取消Package.swift
文件中资源的注释。Package.swiftswiftresources: [ // TODO: If your plugin requires a privacy manifest // (e.g. if it uses any required reason APIs), update the PrivacyInfo.xcprivacy file // to describe your plugin's privacy impact, and then uncomment this line. // For more information, see: // https://developer.apple.com/documentation/bundleresources/privacy_manifest_files .process("PrivacyInfo.xcprivacy"), // TODO: If you have other resources that need to be bundled with your plugin, refer to // the following instructions to add them: // https://developer.apple.com/documentation/xcode/bundling-resources-with-a-swift-package ],
将任何资源文件从
ios/Assets
移动到ios/plugin_name/Sources/plugin_name
(或子目录)。如果适用,请将资源文件添加到Package.swift
文件中。有关更多说明,请参阅 https://developer.apple.com/documentation/xcode/bundling-resources-with-a-swift-package。将任何公共头文件从
ios/Classes
移动到ios/plugin_name/Sources/plugin_name/include/plugin_name
。如果您不确定哪些头文件是公共的,请检查您的
podspec
文件的public_header_files
属性。如果未指定此属性,则所有头文件都是公共的。您应该考虑是否希望所有头文件都为公共的。在您的
pubspec.yaml
文件中定义的pluginClass
必须是公共的,并且位于此目录中。
处理
modulemap
。如果您的插件没有
modulemap
,请跳过此步骤。如果您正在使用
modulemap
让CocoaPods创建测试子模块,请考虑将其从Swift Package Manager中移除。请注意,这使得所有公共头文件都可通过模块访问。要移除Swift Package Manager的
modulemap
但保留CocoaPods的modulemap
,请在插件的Package.swift
文件中排除modulemap
和伞形头文件。以下示例假设
modulemap
和伞形头文件位于ios/plugin_name/Sources/plugin_name/include
目录中。Package.swiftswift.target( name: "plugin_name", dependencies: [], exclude: ["include/cocoapods_plugin_name.modulemap", "include/plugin_name-umbrella.h"],
如果您希望保持单元测试与CocoaPods和Swift Package Manager兼容,您可以尝试以下操作
Tests/TestFile.mobjc@import plugin_name; @import plugin_name.Test; #if __has_include(<plugin_name/plugin_name-umbrella.h>) @import plugin_name.Test; #endif
如果您想在Swift包中使用自定义
modulemap
,请参阅Swift Package Manager的文档。将
ios/Classes
中所有剩余的文件移动到ios/plugin_name/Sources/plugin_name
。ios/Assets
、ios/Resources
和ios/Classes
目录现在应该为空,可以删除。如果您的头文件不再与您的实现文件位于同一目录中,则应更新您的导入语句。
例如,假设以下迁移
之前
ios/Classes/ ├── PublicHeaderFile.h └── ImplementationFile.m
之后
ios/plugin_name/Sources/plugin_name/ └── include/plugin_name/ └── PublicHeaderFile.h └── ImplementationFile.m
在此示例中,应更新
ImplementationFile.m
中的导入语句Sources/plugin_name/ImplementationFile.mobjc#import "PublicHeaderFile.h" #import "./include/plugin_name/PublicHeaderFile.h"
如果您的插件使用 Pigeon,请更新您的 Pigeon 输入文件。
pigeons/messages.dartdartjavaOptions: JavaOptions(), objcHeaderOut: 'ios/Classes/messages.g.h', objcSourceOut: 'ios/Classes/messages.g.m', objcHeaderOut: 'ios/plugin_name/Sources/plugin_name/messages.g.h', objcSourceOut: 'ios/plugin_name/Sources/plugin_name/messages.g.m', copyrightHeader: 'pigeons/copyright.txt',
如果您的
objcHeaderOut
文件不再与objcSourceOut
位于同一目录中,您可以使用ObjcOptions.headerIncludePath
更改#import
。pigeons/messages.dartdartjavaOptions: JavaOptions(), objcHeaderOut: 'ios/Classes/messages.g.h', objcSourceOut: 'ios/Classes/messages.g.m', objcHeaderOut: 'ios/plugin_name/Sources/plugin_name/include/plugin_name/messages.g.h', objcSourceOut: 'ios/plugin_name/Sources/plugin_name/messages.g.m', objcOptions: ObjcOptions( headerIncludePath: './include/plugin_name/messages.g.h', ), copyrightHeader: 'pigeons/copyright.txt',
运行Pigeon以使用最新配置重新生成其代码。
使用您可能需要的任何自定义项更新
Package.swift
文件。在 Xcode 中打开
ios/plugin_name/
目录。在 Xcode 中,打开
Package.swift
文件。验证 Xcode 是否没有为此文件生成任何警告或错误。如果您的
ios/plugin_name.podspec
文件具有 CocoaPodsdependency
,请将相应的 Swift Package Manager 依赖项 添加到Package.swift
文件中。如果您的包必须显式链接
static
或dynamic
(Apple 不推荐),请更新 Product 以定义类型Package.swiftswiftproducts: [ .library(name: "plugin-name", type: .static, targets: ["plugin_name"]) ],
进行任何其他自定义。有关如何编写
Package.swift
文件的更多信息,请参阅 https://developer.apple.com/documentation/packagedescription。
更新
ios/plugin_name.podspec
以指向新路径。ios/plugin_name.podspecrubys.source_files = 'Classes/**/*.{h,m}' s.public_header_files = 'Classes/**/*.h' s.module_map = 'Classes/cocoapods_plugin_name.modulemap' s.resource_bundles = {'plugin_name_privacy' => ['Resources/PrivacyInfo.xcprivacy']} s.source_files = 'plugin_name/Sources/plugin_name/**/*.{h,m}' s.public_header_files = 'plugin_name/Sources/plugin_name/include/**/*.h' s.module_map = 'plugin_name/Sources/plugin_name/include/cocoapods_plugin_name.modulemap' s.resource_bundles = {'plugin_name_privacy' => ['plugin_name/Sources/plugin_name/PrivacyInfo.xcprivacy']}
更新从bundle加载资源以使用
SWIFTPM_MODULE_BUNDLE
objc#if SWIFT_PACKAGE NSBundle *bundle = SWIFTPM_MODULE_BUNDLE; #else NSBundle *bundle = [NSBundle bundleForClass:[self class]]; #endif NSURL *imageURL = [bundle URLForResource:@"image" withExtension:@"jpg"];
如果您的
ios/plugin_name/Sources/plugin_name/include
目录仅包含.gitkeep
,则需要更新您的.gitignore
以包含以下内容.gitignore文本!.gitkeep
运行
flutter pub publish --dry-run
以确保发布include
目录。将插件的更改提交到您的版本控制系统。
验证插件是否仍与 CocoaPods 兼容。
关闭Swift Package Manager
shflutter config --no-enable-swift-package-manager
导航到插件的示例应用。
shcd path/to/plugin/example/
确保插件的示例应用可以构建并运行。
shflutter run
导航到插件的顶级目录。
shcd path/to/plugin/
运行CocoaPods验证lint
shpod lib lint ios/plugin_name.podspec --configuration=Debug --skip-tests --use-modular-headers --use-libraries
shpod lib lint ios/plugin_name.podspec --configuration=Debug --skip-tests --use-modular-headers
验证插件是否与 Swift Package Manager 兼容。
打开Swift Package Manager
shflutter config --enable-swift-package-manager
导航到插件的示例应用。
shcd path/to/plugin/example/
确保插件的示例应用可以构建并运行。
shflutter run
在 Xcode 中打开插件的示例应用。确保左侧的**项目导航器**中显示了**包依赖项**。
验证测试是否通过。
如果您的插件具有本地单元测试 (XCTest),请确保您还 更新插件示例应用中的单元测试。
按照 测试插件 的说明操作。
如何更新插件示例应用中的单元测试
#如果您的插件具有本地XCTest,则可能需要更新它们以使其与Swift Package Manager一起使用,如果以下任一情况为真
- 您正在为测试使用CocoaPod依赖项。
- 您的插件在其
Package.swift
文件中显式设置为type: .dynamic
。
要更新您的单元测试
在Xcode中打开您的
example/ios/Runner.xcworkspace
。如果您正在使用CocoaPod依赖项进行测试,例如
OCMock
,则需要将其从您的Podfile
文件中删除。ios/Podfilerubytarget 'RunnerTests' do inherit! :search_paths pod 'OCMock', '3.5' end
然后在终端中,在
plugin_name_ios/example/ios
目录中运行pod install
。导航到项目的Package Dependencies。
项目的包依赖项 单击+按钮,并通过在右上角的搜索栏中搜索来添加任何仅用于测试的依赖项。
搜索仅用于测试的依赖项 确保将依赖项添加到
RunnerTests
目标中。确保将依赖项添加到 RunnerTests
目标中单击Add Package按钮。
如果您已在其
Package.swift
文件中显式将插件的库类型设置为.dynamic
(Apple不推荐),则还需要将其作为依赖项添加到RunnerTests
目标中。确保
RunnerTests
的Build Phases具有Link Binary With Libraries构建阶段RunnerTests
目标中的Link Binary With Libraries
构建阶段如果构建阶段尚不存在,请创建一个。单击add,然后单击New Link Binary With Libraries Phase。
添加 Link Binary With Libraries
构建阶段导航到项目的Package Dependencies。
单击add。
在打开的对话框中,单击Add Local...按钮。
导航到
plugin_name/plugin_name_ios/ios/plugin_name_ios
并单击Add Package按钮。确保将其添加到
RunnerTests
目标中,然后单击Add Package按钮。
确保测试通过Product > Test。
除非另有说明,否则本网站上的文档反映了Flutter的最新稳定版本。页面上次更新于 2024-12-04。 查看源代码 或 报告问题。