Skip to main content

面向插件作者的 Swift 包管理器

:::警告 Flutter 正在迁移到Swift 包管理器来管理 iOS 和 macOS 原生依赖项。Flutter 对 Swift 包管理器的支持正在开发中。如果您在 Flutter 的 Swift 包管理器支持中发现错误,请提交问题。Swift 包管理器支持默认情况下关闭。Flutter 继续支持 CocoaPods。 :::

Flutter 的 Swift 包管理器集成具有以下几个优势:

  1. 提供对 Swift 包生态系统的访问。 Flutter 插件可以使用不断增长的Swift 包生态系统!
  2. 简化 Flutter 安装。 Swift 包管理器与 Xcode 捆绑在一起。将来,您无需安装 Ruby 和 CocoaPods 即可定位 iOS 或 macOS。

如何启用 Swift Package Manager

#

Flutter 默认情况下禁用 Swift Package Manager 支持。 要启用它:

  1. 升级到最新 Flutter SDK:

    sh
    flutter upgrade
  2. 启用 Swift Package Manager 功能:

    sh
    flutter 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

pubspec.yaml
yaml
# 下面部分特定于 Flutter 包。
flutter:
  disable-swift-package-manager: true

这将为该项目的所有贡献者禁用 Swift Package Manager。

全局禁用所有项目

#

运行以下命令:

sh
flutter config --no-enable-swift-package-manager

这将为当前用户禁用 Swift Package Manager。

如果项目与 Swift Package Manager 不兼容,所有贡献者都需要运行此命令。

如何向现有的 Flutter 插件添加 Swift 包管理器支持

#

本指南介绍如何向已支持 CocoaPods 的插件添加 Swift 包管理器支持。这确保了插件可被所有 Flutter 项目使用。

除非另行通知,Flutter 插件应同时支持 Swift 包管理器和 CocoaPods。

Swift 包管理器的采用将是渐进式的。不支持 CocoaPods 的插件将无法被尚未迁移到 Swift 包管理器的项目使用。不支持 Swift 包管理器的插件可能会导致已迁移项目的出现问题。

将本指南中的 plugin_name 全部替换为您插件的名称。 以下示例使用 ios,请根据需要将 ios 替换为 macos/darwin

  1. 启用 Swift Package Manager 功能

  2. 首先在 iosmacos 和/或 darwin 目录下创建一个目录。 将此新目录命名为平台包的名称。

    plugin_name/ios/
    ├── ...
    └── plugin_name/
    
  3. 在此新目录中,创建以下文件/目录:

    • Package.swift(文件)
    • Sources(目录)
    • Sources/plugin_name(目录)

    您的插件应如下所示:

    plugin_name/ios/
    ├── ...
    └── plugin_name/
       ├── Package.swift
       └── Sources/plugin_name/
    
  4. Package.swift 文件中使用以下模板:

    Package.swift
    swift
    // swift-tools-version: 5.9
    // swift-tools-version 声明构建此包所需的 Swift 的最低版本。
    
    import PackageDescription
    
    let package = Package(
        // TODO:更新您的插件名称。
        name: "plugin_name",
        platforms: [
            // TODO:更新您的插件支持的平台。
            // 如果您的插件仅支持 iOS,请删除 `.macOS(...)`。
            // 如果您的插件仅支持 macOS,请删除 `.iOS(...)`。
            .iOS("12.0"),
            .macOS("10.14")
        ],
        products: [
            // TODO:更新您的库和目标名称。
            // 如果插件名称包含 "_",则将其替换为库名称的 "-"。
            .library(name: "plugin-name", targets: ["plugin_name"])
        ],
        dependencies: [],
        targets: [
            .target(
                // TODO:更新您的目标名称。
                name: "plugin_name",
                dependencies: [],
                resources: [
                    // TODO:如果您的插件需要隐私清单
                    // (例如,如果它使用任何必需的原因 API),请更新 PrivacyInfo.xcprivacy 文件
                    // 以描述您的插件的隐私影响,然后取消注释此行。
                    // 有关更多信息,请参阅:
                    // https://developer.apple.com/documentation/bundleresources/privacy_manifest_files
                    // .process("PrivacyInfo.xcprivacy"),
    
                    // TODO:如果您的插件需要捆绑其他资源,请参考
                    // 以下说明添加它们:
                    // https://developer.apple.com/documentation/xcode/bundling-resources-with-a-swift-package
                ]
            )
        ]
    )
  5. 更新 Package.swift 文件中的支持的平台

    Package.swift
    swift
        platforms: [
            // TODO:更新您的插件支持的平台。
            // 如果您的插件仅支持 iOS,请删除 `.macOS(...)`。
            // 如果您的插件仅支持 macOS,请删除 `.iOS(...)`。
            .iOS("12.0"),
            .macOS("10.14")
        ],
  6. 更新 Package.swift 文件中的包、库和目标名称。

    Package.swift
    swift
    let package = Package(
        // TODO:更新您的插件名称。
        name: "plugin_name",
        platforms: [
            .iOS("12.0"),
            .macOS("10.14")
        ],
        products: [
            // TODO:更新您的库和目标名称。
            // 如果插件名称包含 "_",则将其替换为库名称的 "-"
            .library(name: "plugin-name", targets: ["plugin_name"])
        ],
        dependencies: [],
        targets: [
            .target(
                // TODO:更新您的目标名称。
                name: "plugin_name",
                dependencies: [],
                resources: [
                    // TODO:如果您的插件需要隐私清单
                    // (例如,如果它使用任何必需的原因 API),请更新 PrivacyInfo.xcprivacy 文件
                    // 以描述您的插件的隐私影响,然后取消注释此行。
                    // 有关更多信息,请参阅:
                    // https://developer.apple.com/documentation/bundleresources/privacy_manifest_files
                    // .process("PrivacyInfo.xcprivacy"),
    
                    // TODO:如果您的插件需要捆绑其他资源,请参考
                    // 以下说明添加它们:
                    // https://developer.apple.com/documentation/xcode/bundling-resources-with-a-swift-package
                ]
            )
        ]
    )
  7. 如果您的插件具有 PrivacyInfo.xcprivacy 文件,请将其移动到 ios/plugin_name/Sources/plugin_name/PrivacyInfo.xcprivacy 并取消注释 Package.swift 文件中的资源。

    Package.swift
    swift
                resources: [
                    // TODO:如果您的插件需要隐私清单
                    // (例如,如果它使用任何必需的原因 API),请更新 PrivacyInfo.xcprivacy 文件
                    // 以描述您的插件的隐私影响,然后取消注释此行。
                    // 有关更多信息,请参阅:
                    // https://developer.apple.com/documentation/bundleresources/privacy_manifest_files
                    .process("PrivacyInfo.xcprivacy"),
    
                    // TODO:如果您的插件需要捆绑其他资源,请参考
                    // 以下说明添加它们:
                    // https://developer.apple.com/documentation/xcode/bundling-resources-with-a-swift-package
                ],
  8. 将任何资源文件从 ios/Assets 移动到 ios/plugin_name/Sources/plugin_name(或子目录)。如果适用,请将资源文件添加到您的 Package.swift 文件中。有关更多说明,请参阅 https://developer.apple.com/documentation/xcode/bundling-resources-with-a-swift-package

  9. 将所有文件从 ios/Classes 移动到 ios/plugin_name/Sources/plugin_name

  10. ios/Assetsios/Resourcesios/Classes 目录现在应该为空,可以删除。

  11. 如果您的插件使用Pigeon,请更新您的 Pigeon 输入文件。

    pigeons/messages.dart
    dart
    kotlinOptions: 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(),
  12. 使用您可能需要的任何自定义项更新您的 Package.swift 文件。

    1. 在 Xcode 中打开 ios/plugin_name/ 目录。

    2. 在 Xcode 中,打开您的 Package.swift 文件。 验证 Xcode 是否没有为此文件生成任何警告或错误。

    3. 如果您的 ios/plugin_name.podspec 文件具有CocoaPods dependencys, 请将相应的Swift Package Manager 依赖项 添加到您的 Package.swift 文件中。

    4. 如果您的包必须显式链接 staticdynamicApple 不推荐),请更新Product 以定义类型:

      Package.swift
      swift
      products: [
          .library(name: "plugin-name", type: .static, targets: ["plugin_name"])
      ],
    5. 进行任何其他自定义。有关如何编写 Package.swift 文件的更多信息,请参阅 https://developer.apple.com/documentation/packagedescription

  13. 更新您的 ios/plugin_name.podspec 以指向新的路径。

    ios/plugin_name.podspec
    ruby
    s.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']}
  14. 更新从 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
  15. 如果您的 .gitignore 不包含 .build/.swiftpm/ 目录,您需要更新您的 .gitignore 以包含:

    .gitignore
    text
    .build/
    .swiftpm/

    将插件的更改提交到您的版本控制系统。

  16. 验证插件仍然可以使用 CocoaPods。

    1. 关闭 Swift Package Manager。

      sh
      flutter config --no-enable-swift-package-manager
    2. 导航到插件的示例应用程序。

      sh
      cd path/to/plugin/example/
    3. 确保插件的示例应用程序可以构建和运行。

      sh
      flutter run
    4. 导航到插件的顶级目录。

      sh
      cd path/to/plugin/
    5. 运行 CocoaPods 验证 lint。

      sh
      pod lib lint ios/plugin_name.podspec  --configuration=Debug --skip-tests --use-modular-headers --use-libraries
      sh
      pod lib lint ios/plugin_name.podspec  --configuration=Debug --skip-tests --use-modular-headers
  17. 验证插件可以使用 Swift Package Manager。

    1. 启用 Swift Package Manager。

      sh
      flutter config --enable-swift-package-manager
    2. 导航到插件的示例应用程序。

      sh
      cd path/to/plugin/example/
    3. 确保插件的示例应用程序可以构建和运行。

      sh
      flutter run

将本指南中的 plugin_name 全部替换为您插件的名称。 以下示例使用 ios,请根据需要将 ios 替换为 macos/darwin

  1. 启用 Swift Package Manager 功能

  2. 首先在 iosmacos 和/或 darwin 目录下创建一个目录。 将此新目录命名为平台包的名称。

    plugin_name/ios/
    ├── ...
    └── plugin_name/
    
  3. 在此新目录中,创建以下文件/目录:

    • Package.swift(文件)
    • Sources(目录)
    • Sources/plugin_name(目录)

    您的插件应如下所示:

    plugin_name/ios/
    ├── ...
    └── plugin_name/
       ├── Package.swift
       └── Sources/plugin_name/
    
  4. Package.swift 文件中使用以下模板:

    Package.swift
    swift
    // swift-tools-version: 5.9
    // swift-tools-version 声明构建此包所需的 Swift 的最低版本。
    
    import PackageDescription
    
    let package = Package(
        // TODO:更新您的插件名称。
        name: "plugin_name",
        platforms: [
            // TODO:更新您的插件支持的平台。
            // 如果您的插件仅支持 iOS,请删除 `.macOS(...)`。
            // 如果您的插件仅支持 macOS,请删除 `.iOS(...)`。
            .iOS("12.0"),
            .macOS("10.14")
        ],
        products: [
            // TODO:更新您的库和目标名称。
            // 如果插件名称包含 "_",则将其替换为库名称的 "-"。
            .library(name: "plugin-name", targets: ["plugin_name"])
        ],
        dependencies: [],
        targets: [
            .target(
                // TODO:更新您的目标名称。
                name: "plugin_name",
                dependencies: [],
                resources: [
                    // TODO:如果您的插件需要隐私清单
                    // (例如,如果它使用任何必需的原因 API),请更新 PrivacyInfo.xcprivacy 文件
                    // 以描述您的插件的隐私影响,然后取消注释此行。
                    // 有关更多信息,请参阅:
                    // https://developer.apple.com/documentation/bundleresources/privacy_manifest_files
                    // .process("PrivacyInfo.xcprivacy"),
    
                    // TODO:如果您的插件需要捆绑其他资源,请参考
                    // 以下说明添加它们:
                    // https://developer.apple.com/documentation/xcode/bundling-resources-with-a-swift-package
                ]
            )
        ]
    )
  5. 更新 Package.swift 文件中的支持的平台

    Package.swift
    swift
        platforms: [
            // TODO:更新您的插件支持的平台。
            // 如果您的插件仅支持 iOS,请删除 `.macOS(...)`。
            // 如果您的插件仅支持 macOS,请删除 `.iOS(...)`。
            .iOS("12.0"),
            .macOS("10.14")
        ],
  6. 更新 Package.swift 文件中的包、库和目标名称。

    Package.swift
    swift
    let package = Package(
        // TODO:更新您的插件名称。
        name: "plugin_name",
        platforms: [
            .iOS("12.0"),
            .macOS("10.14")
        ],
        products: [
            // TODO:更新您的库和目标名称。
            // 如果插件名称包含 "_",则将其替换为库名称的 "-"
            .library(name: "plugin-name", targets: ["plugin_name"])
        ],
        dependencies: [],
        targets: [
            .target(
                // TODO:更新您的目标名称。
                name: "plugin_name",
                dependencies: [],
                resources: [
                    // TODO:如果您的插件需要隐私清单
                    // (例如,如果它使用任何必需的原因 API),请更新 PrivacyInfo.xcprivacy 文件
                    // 以描述您的插件的隐私影响,然后取消注释此行。
                    // 有关更多信息,请参阅:
                    // https://developer.apple.com/documentation/bundleresources/privacy_manifest_files
                    // .process("PrivacyInfo.xcprivacy"),
    
                    // TODO:如果您的插件需要捆绑其他资源,请参考
                    // 以下说明添加它们:
                    // https://developer.apple.com/documentation/xcode/bundling-resources-with-a-swift-package
                ]
            )
        ]
    )
  7. 如果您的插件具有 PrivacyInfo.xcprivacy 文件,请将其移动到 ios/plugin_name/Sources/plugin_name/PrivacyInfo.xcprivacy 并取消注释 Package.swift 文件中的资源。

    Package.swift
    swift
                resources: [
                    // TODO:如果您的插件需要隐私清单
                    // (例如,如果它使用任何必需的原因 API),请更新 PrivacyInfo.xcprivacy 文件
                    // 以描述您的插件的隐私影响,然后取消注释此行。
                    // 有关更多信息,请参阅:
                    // https://developer.apple.com/documentation/bundleresources/privacy_manifest_files
                    .process("PrivacyInfo.xcprivacy"),
    
                    // TODO:如果您的插件需要捆绑其他资源,请参考
                    // 以下说明添加它们:
                    // https://developer.apple.com/documentation/xcode/bundling-resources-with-a-swift-package
                ],
  8. 将任何资源文件从 ios/Assets 移动到 ios/plugin_name/Sources/plugin_name(或子目录)。如果适用,请将资源文件添加到您的 Package.swift 文件中。有关更多说明,请参阅 https://developer.apple.com/documentation/xcode/bundling-resources-with-a-swift-package

  9. 将所有文件从 ios/Classes 移动到 ios/plugin_name/Sources/plugin_name

  10. ios/Assetsios/Resourcesios/Classes 目录现在应该为空,可以删除。

  11. 如果您的插件使用Pigeon,请更新您的 Pigeon 输入文件。

    pigeons/messages.dart
    dart
    kotlinOptions: 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(),
  12. 使用您可能需要的任何自定义项更新您的 Package.swift 文件。

    1. 在 Xcode 中打开 ios/plugin_name/ 目录。

    2. 在 Xcode 中,打开您的 Package.swift 文件。 验证 Xcode 是否没有为此文件生成任何警告或错误。

    3. 如果您的 ios/plugin_name.podspec 文件具有CocoaPods dependencys, 请将相应的Swift Package Manager 依赖项 添加到您的 Package.swift 文件中。

    4. 如果您的包必须显式链接 staticdynamicApple 不推荐),请更新Product 以定义类型:

      Package.swift
      swift
      products: [
          .library(name: "plugin-name", type: .static, targets: ["plugin_name"])
      ],
    5. 进行任何其他自定义。有关如何编写 Package.swift 文件的更多信息,请参阅 https://developer.apple.com/documentation/packagedescription

  13. 更新您的 ios/plugin_name.podspec 以指向新的路径。

    ios/plugin_name.podspec
    ruby
    s.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']}
  14. 更新从 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
  15. 如果您的 .gitignore 不包含 .build/.swiftpm/ 目录,您需要更新您的 .gitignore 以包含:

    .gitignore
    text
    .build/
    .swiftpm/

    将插件的更改提交到您的版本控制系统。

  16. 验证插件仍然可以使用 CocoaPods。

    1. 关闭 Swift Package Manager。

      sh
      flutter config --no-enable-swift-package-manager
    2. 导航到插件的示例应用程序。

      sh
      cd path/to/plugin/example/
    3. 确保插件的示例应用程序可以构建和运行。

      sh
      flutter run
    4. 导航到插件的顶级目录。

      sh
      cd path/to/plugin/
    5. 运行 CocoaPods 验证 lint。

      sh
      pod lib lint ios/plugin_name.podspec  --configuration=Debug --skip-tests --use-modular-headers --use-libraries
      sh
      pod lib lint ios/plugin_name.podspec  --configuration=Debug --skip-tests --use-modular-headers
  17. 验证插件可以使用 Swift Package Manager。

    1. 启用 Swift Package Manager。

      sh
      flutter config --enable-swift-package-manager
    2. 导航到插件的示例应用程序。

      sh
      cd path/to/plugin/example/
    3. 确保插件的示例应用程序可以构建和运行。

      sh
      flutter run

      如果您想使用较旧的 Flutter SDK 版本运行示例应用程序,请不要将迁移的更改提交到您的版本控制系统。如有需要,您可以随时 撤销 Swift Package Manager 迁移。 :::

    4. 在 Xcode 中打开插件的示例应用程序。 确保左侧的 项目导航器 中显示 包依赖项

  18. 验证测试是否通过。

将本指南中所有出现的 plugin_name 替换为您插件的名称。 以下示例使用 ios,请根据需要将 ios 替换为 macos/darwin

  1. 启用 Swift Package Manager 功能

  2. 首先在 iosmacos 和/或 darwin 目录下创建一个目录。 将此新目录命名为平台包的名称。

    plugin_name/ios/
    ├── ...
    └── plugin_name/
    
  3. 在此新目录中,创建以下文件/目录:

    • 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
    
  4. Package.swift 文件中使用以下模板:

    Package.swift
    swift
    // swift-tools-version: 5.9
    // swift-tools-version 声明构建此包所需的 Swift 的最低版本。
    
    import PackageDescription
    
    let package = Package(
        // TODO:更新您的插件名称。
        name: "plugin_name",
        platforms: [
            // TODO:更新您的插件支持的平台。
            // 如果您的插件仅支持 iOS,请删除 `.macOS(...)`。
            // 如果您的插件仅支持 macOS,请删除 `.iOS(...)`。
            .iOS("12.0"),
            .macOS("10.14")
        ],
        products: [
            // TODO:更新您的库和目标名称。
            // 如果插件名称包含 "_",则将其替换为库名称的 "-"
            .library(name: "plugin-name", targets: ["plugin_name"])
        ],
        dependencies: [],
        targets: [
            .target(
                // TODO:更新您的目标名称。
                name: "plugin_name",
                dependencies: [],
                resources: [
                    // TODO:如果您的插件需要隐私清单
                    // (例如,如果它使用任何必需原因 API),请更新 PrivacyInfo.xcprivacy 文件
                    // 以描述您的插件的隐私影响,然后取消注释此行。
                    // 有关更多信息,请参阅:
                    // https://developer.apple.com/documentation/bundleresources/privacy_manifest_files
                    // .process("PrivacyInfo.xcprivacy"),
    
                    // TODO:如果您还有其他需要与插件捆绑在一起的资源,请参阅
                    // 以下说明以添加它们:
                    // https://developer.apple.com/documentation/xcode/bundling-resources-with-a-swift-package
                ],
                cSettings: [
                    // TODO:更新您的插件名称。
                    .headerSearchPath("include/plugin_name")
                ]
            )
        ]
    )
  5. 更新 Package.swift 文件中的支持的平台

    Package.swift
    swift
        platforms: [
            // TODO:更新您的插件支持的平台。
            // 如果您的插件仅支持 iOS,请删除 `.macOS(...)`。
            // 如果您的插件仅支持 macOS,请删除 `.iOS(...)`。
            .iOS("12.0"),
            .macOS("10.14")
        ],
  6. 更新 Package.swift 文件中的包、库和目标名称。

    Package.swift
    swift
    let package = Package(
        // TODO:更新您的插件名称。
        name: "plugin_name",
        platforms: [
            .iOS("12.0"),
            .macOS("10.14")
        ],
        products: [
            // TODO:更新您的库和目标名称。
            // 如果插件名称包含 "_",则将其替换为库名称的 "-"
            .library(name: "plugin-name", targets: ["plugin_name"])
        ],
        dependencies: [],
        targets: [
            .target(
                // TODO:更新您的目标名称。
                name: "plugin_name",
                dependencies: [],
                resources: [
                    // TODO:如果您的插件需要隐私清单
                    // (例如,如果它使用任何必需原因 API),请更新 PrivacyInfo.xcprivacy 文件
                    // 以描述您的插件的隐私影响,然后取消注释此行。
                    // 有关更多信息,请参阅:
                    // https://developer.apple.com/documentation/bundleresources/privacy_manifest_files
                    // .process("PrivacyInfo.xcprivacy"),
    
                    // TODO:如果您还有其他需要与插件捆绑在一起的资源,请参阅
                    // 以下说明以添加它们:
                    // https://developer.apple.com/documentation/xcode/bundling-resources-with-a-swift-package
                ],
                cSettings: [
                    // TODO:更新您的插件名称。
                    .headerSearchPath("include/plugin_name")
                ]
            )
        ]
    )
  7. 如果您的插件有 PrivacyInfo.xcprivacy 文件,请将其移动到 ios/plugin_name/Sources/plugin_name/PrivacyInfo.xcprivacy 并取消注释 Package.swift 文件中的资源。

    Package.swift
    swift
                resources: [
                    // TODO:如果您的插件需要隐私清单
                    // (例如,如果它使用任何必需原因 API),请更新 PrivacyInfo.xcprivacy 文件
                    // 以描述您的插件的隐私影响,然后取消注释此行。
                    // 有关更多信息,请参阅:
                    // https://developer.apple.com/documentation/bundleresources/privacy_manifest_files
                    .process("PrivacyInfo.xcprivacy"),
    
                    // TODO:如果您还有其他需要与插件捆绑在一起的资源,请参阅
                    // 以下说明以添加它们:
                    // https://developer.apple.com/documentation/xcode/bundling-resources-with-a-swift-package
                ],
  8. 将任何资源文件从 ios/Assets 移动到 ios/plugin_name/Sources/plugin_name(或子目录)。 如果适用,请将资源文件添加到您的 Package.swift 文件中。 有关更多说明,请参阅 https://developer.apple.com/documentation/xcode/bundling-resources-with-a-swift-package

  9. 将任何公共头文件从 ios/Classes 移动到 ios/plugin_name/Sources/plugin_name/include/plugin_name

    • 如果你不确定哪些头文件是公共的,请检查你的 podspec 文件的 public_header_files 属性。 如果未指定此属性,则所有头文件都是公共的。 您应该考虑是否希望所有头文件都公开。

    • pubspec.yaml 文件中定义的 pluginClass 必须是公共的,并且在此目录中。

  10. 处理 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.swift
    swift
    .target(
        name: "plugin_name",
        dependencies: [],
        exclude: ["include/cocoapods_plugin_name.modulemap", "include/plugin_name-umbrella.h"],

    如果您想保持您的单元测试与 CocoaPods 和 Swift Package Manager 都兼容,您可以尝试以下方法:

    Tests/TestFile.m
    objc
    @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 的文档

  11. ios/Classes 中所有剩余的文件移动到 ios/plugin_name/Sources/plugin_name

  12. ios/Assetsios/Resourcesios/Classes 目录现在应该为空,可以删除。

  13. 如果您的头文件不再与您的实现文件位于同一目录中,则应更新您的导入语句。

    例如,假设以下迁移:

    • 之前:

      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.m
    objc
    #import "PublicHeaderFile.h"
    #import "./include/plugin_name/PublicHeaderFile.h"
  14. 如果您的插件使用Pigeon,请更新您的 Pigeon 输入文件。

    pigeons/messages.dart
    dart
    javaOptions: 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.dart
    dart
    javaOptions: 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 以使用最新配置重新生成其代码。

  15. 使用您可能需要的任何自定义项更新您的 Package.swift 文件。

    1. 在 Xcode 中打开 ios/plugin_name/ 目录。

    2. 在 Xcode 中,打开您的 Package.swift 文件。 验证 Xcode 是否没有为此文件产生任何警告或错误。

    3. 如果您的 ios/plugin_name.podspec 文件有CocoaPods dependency

将本指南中所有出现的 plugin_name 替换为您插件的名称。 以下示例使用 ios,请根据需要将 ios 替换为 macos/darwin

  1. 启用 Swift Package Manager 功能

  2. 首先在 iosmacos 和/或 darwin 目录下创建一个目录。 将此新目录命名为平台包的名称。

    plugin_name/ios/
    ├── ...
    └── plugin_name/
    
  3. 在此新目录中,创建以下文件/目录:

    • 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
    
  4. Package.swift 文件中使用以下模板:

    Package.swift
    swift
    // swift-tools-version: 5.9
    // swift-tools-version 声明构建此包所需的 Swift 的最低版本。
    
    import PackageDescription
    
    let package = Package(
        // TODO:更新您的插件名称。
        name: "plugin_name",
        platforms: [
            // TODO:更新您的插件支持的平台。
            // 如果您的插件仅支持 iOS,请删除 `.macOS(...)`。
            // 如果您的插件仅支持 macOS,请删除 `.iOS(...)`。
            .iOS("12.0"),
            .macOS("10.14")
        ],
        products: [
            // TODO:更新您的库和目标名称。
            // 如果插件名称包含 "_",则将其替换为库名称的 "-"
            .library(name: "plugin-name", targets: ["plugin_name"])
        ],
        dependencies: [],
        targets: [
            .target(
                // TODO:更新您的目标名称。
                name: "plugin_name",
                dependencies: [],
                resources: [
                    // TODO:如果您的插件需要隐私清单
                    // (例如,如果它使用任何必需原因 API),请更新 PrivacyInfo.xcprivacy 文件
                    // 以描述您的插件的隐私影响,然后取消注释此行。
                    // 有关更多信息,请参阅:
                    // https://developer.apple.com/documentation/bundleresources/privacy_manifest_files
                    // .process("PrivacyInfo.xcprivacy"),
    
                    // TODO:如果您还有其他需要与插件捆绑在一起的资源,请参阅
                    // 以下说明以添加它们:
                    // https://developer.apple.com/documentation/xcode/bundling-resources-with-a-swift-package
                ],
                cSettings: [
                    // TODO:更新您的插件名称。
                    .headerSearchPath("include/plugin_name")
                ]
            )
        ]
    )
  5. 更新 Package.swift 文件中的支持的平台

    Package.swift
    swift
        platforms: [
            // TODO:更新您的插件支持的平台。
            // 如果您的插件仅支持 iOS,请删除 `.macOS(...)`。
            // 如果您的插件仅支持 macOS,请删除 `.iOS(...)`。
            .iOS("12.0"),
            .macOS("10.14")
        ],
  6. 更新 Package.swift 文件中的包、库和目标名称。

    Package.swift
    swift
    let package = Package(
        // TODO:更新您的插件名称。
        name: "plugin_name",
        platforms: [
            .iOS("12.0"),
            .macOS("10.14")
        ],
        products: [
            // TODO:更新您的库和目标名称。
            // 如果插件名称包含 "_",则将其替换为库名称的 "-"
            .library(name: "plugin-name", targets: ["plugin_name"])
        ],
        dependencies: [],
        targets: [
            .target(
                // TODO:更新您的目标名称。
                name: "plugin_name",
                dependencies: [],
                resources: [
                    // TODO:如果您的插件需要隐私清单
                    // (例如,如果它使用任何必需原因 API),请更新 PrivacyInfo.xcprivacy 文件
                    // 以描述您的插件的隐私影响,然后取消注释此行。
                    // 有关更多信息,请参阅:
                    // https://developer.apple.com/documentation/bundleresources/privacy_manifest_files
                    // .process("PrivacyInfo.xcprivacy"),
    
                    // TODO:如果您还有其他需要与插件捆绑在一起的资源,请参阅
                    // 以下说明以添加它们:
                    // https://developer.apple.com/documentation/xcode/bundling-resources-with-a-swift-package
                ],
                cSettings: [
                    // TODO:更新您的插件名称。
                    .headerSearchPath("include/plugin_name")
                ]
            )
        ]
    )
  7. 如果您的插件有 PrivacyInfo.xcprivacy 文件,请将其移动到 ios/plugin_name/Sources/plugin_name/PrivacyInfo.xcprivacy 并取消注释 Package.swift 文件中的资源。

    Package.swift
    swift
                resources: [
                    // TODO:如果您的插件需要隐私清单
                    // (例如,如果它使用任何必需原因 API),请更新 PrivacyInfo.xcprivacy 文件
                    // 以描述您的插件的隐私影响,然后取消注释此行。
                    // 有关更多信息,请参阅:
                    // https://developer.apple.com/documentation/bundleresources/privacy_manifest_files
                    .process("PrivacyInfo.xcprivacy"),
    
                    // TODO:如果您还有其他需要与插件捆绑在一起的资源,请参阅
                    // 以下说明以添加它们:
                    // https://developer.apple.com/documentation/xcode/bundling-resources-with-a-swift-package
                ],
  8. 将任何资源文件从 ios/Assets 移动到 ios/plugin_name/Sources/plugin_name(或子目录)。 如果适用,请将资源文件添加到您的 Package.swift 文件中。 有关更多说明,请参阅 https://developer.apple.com/documentation/xcode/bundling-resources-with-a-swift-package

  9. 将任何公共头文件从 ios/Classes 移动到 ios/plugin_name/Sources/plugin_name/include/plugin_name

    • 如果你不确定哪些头文件是公共的,请检查你的 podspec 文件的 public_header_files 属性。 如果未指定此属性,则所有头文件都是公共的。 您应该考虑是否希望所有头文件都公开。

    • pubspec.yaml 文件中定义的 pluginClass 必须是公共的,并且在此目录中。

  10. 处理 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.swift
    swift
    .target(
        name: "plugin_name",
        dependencies: [],
        exclude: ["include/cocoapods_plugin_name.modulemap", "include/plugin_name-umbrella.h"],

    如果您想保持您的单元测试与 CocoaPods 和 Swift Package Manager 都兼容,您可以尝试以下方法:

    Tests/TestFile.m
    objc
    @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 的文档

  11. ios/Classes 中所有剩余的文件移动到 ios/plugin_name/Sources/plugin_name

  12. ios/Assetsios/Resourcesios/Classes 目录现在应该为空,可以删除。

  13. 如果您的头文件不再与您的实现文件位于同一目录中,则应更新您的导入语句。

    例如,假设以下迁移:

    • 之前:

      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.m
    objc
    #import "PublicHeaderFile.h"
    #import "./include/plugin_name/PublicHeaderFile.h"
  14. 如果您的插件使用Pigeon,请更新您的 Pigeon 输入文件。

    pigeons/messages.dart
    dart
    javaOptions: 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.dart
    dart
    javaOptions: 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 以使用最新配置重新生成其代码。

  15. 使用您可能需要的任何自定义项更新您的 Package.swift 文件。

    1. 在 Xcode 中打开 ios/plugin_name/ 目录。

    2. 在 Xcode 中,打开您的 Package.swift 文件。 验证 Xcode 是否没有为此文件产生任何警告或错误。

    3. 如果您的 ios/plugin_name.podspec 文件有CocoaPods dependency

  16. 如果您的包必须显式链接 staticdynamic不推荐使用 Apple),请更新产品以定义类型:

Package.swift
swift
products: [
    .library(name: "plugin-name", type: .static, targets: ["plugin_name"])
],
  1. 进行任何其他自定义。有关如何编写 Package.swift 文件的更多信息,请参阅 https://developer.apple.com/documentation/packagedescription

  2. 更新 ios/plugin_name.podspec 以指向新的路径。

ios/plugin_name.podspec
ruby
s.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']}
  1. 更新从包加载资源以使用 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"];
  1. 如果您的 ios/plugin_name/Sources/plugin_name/include 目录只包含 .gitkeep,您需要更新您的 .gitignore 以包含以下内容:

    .gitignore
    text
    !.gitkeep

    运行 flutter pub publish --dry-run 以确保发布 include 目录。

  2. 将插件的更改提交到您的版本控制系统。

  3. 验证插件是否仍可与 CocoaPods 一起使用。

    1. 关闭 Swift Package Manager:

      sh
      flutter config --no-enable-swift-package-manager
    2. 导航到插件的示例应用程序。

      sh
      cd path/to/plugin/example/
    3. 确保插件的示例应用程序可以构建和运行。

      sh
      flutter run
    4. 导航到插件的顶级目录。

      sh
      cd path/to/plugin/
    5. 运行 CocoaPods 验证 lint:

      sh
      pod lib lint ios/plugin_name.podspec  --configuration=Debug --skip-tests --use-modular-headers --use-libraries
      sh
      pod lib lint ios/plugin_name.podspec  --configuration=Debug --skip-tests --use-modular-headers
  4. 验证插件是否可与 Swift Package Manager 一起使用。

    1. 启用 Swift Package Manager:

      sh
      flutter config --enable-swift-package-manager
    2. 导航到插件的示例应用程序。

      sh
      cd path/to/plugin/example/
    3. 确保插件的示例应用程序可以构建和运行。

      sh
      flutter run
    4. 在 Xcode 中打开插件的示例应用程序。 确保左侧的 项目导航器 中显示 包依赖项

  5. 验证测试是否通过。

如何更新插件示例应用中的单元测试

#

如果您的插件具有原生 XCTests,如果满足以下任一条件,您可能需要更新它们以与 Swift 包管理器配合使用:

  • 您正在使用 CocoaPod 依赖项进行测试。
  • 您的插件在其 Package.swift 文件中明确设置为 type: .dynamic

要更新您的单元测试:

  1. 在 Xcode 中打开您的 example/ios/Runner.xcworkspace

  2. 如果您正在使用 CocoaPod 依赖项进行测试(例如 OCMock),您需要将其从您的 Podfile 文件中移除。

    ios/Podfile
    ruby
    target 'RunnerTests' do
      inherit! :search_paths
    
      pod 'OCMock', '3.5'
    end

    然后在终端中,在 plugin_name_ios/example/ios 目录中运行 pod install

  3. 导航到项目的 包依赖项

    项目的包依赖项
    项目的包依赖项

  4. 点击**+**按钮,通过在右上角的搜索栏中搜索来添加任何仅供测试的依赖项。

    搜索仅供测试的依赖项
    搜索仅供测试的依赖项

    :::注意 OCMock 使用不安全的构建标志,只有在目标提交 fe1661a3efed11831a6452f4b1a0c5e6ddc08c3d(3.9.3 版本)时才能使用。 :::

  5. 确保依赖项已添加到 RunnerTests 目标。

    确保依赖项已添加到目标
    确保依赖项已添加到RunnerTests目标

  6. 点击 添加包 按钮。

  7. 如果您已在其 Package.swift 文件中将插件的库类型明确设置为 .dynamicApple 不推荐),您还需要将其作为依赖项添加到 RunnerTests 目标。

    1. 确保 RunnerTests构建阶段 具有 使用库链接二进制文件 构建阶段:

      目标中的构建阶段
      RunnerTests目标中的使用库链接二进制文件构建阶段

      如果构建阶段尚不存在,请创建一个。点击add,然后点击 新建使用库链接二进制文件阶段

      添加构建阶段
      添加使用库链接二进制文件构建阶段

    2. 导航到项目的 包依赖项

    3. 点击add

    4. 在打开的对话框中,点击**添加本地...**按钮。

    5. 导航到 plugin_name/plugin_name_ios/ios/plugin_name_ios 并点击 添加包 按钮。

    6. 确保它已添加到 RunnerTests 目标,然后点击 添加包 按钮。

  8. 确保测试通过 产品 > 测试