跳到主內容

面向外掛作者的 Swift Package Manager 指南

如何為 iOS 和 macOS 外掛新增 Swift Package Manager 相容性

Flutter 的 Swift Package Manager 整合具有多項優勢

  1. 提供對 Swift 包生態系統的訪問許可權。Flutter 外掛可以使用不斷增長的 Swift 包 生態系統!
  2. 簡化 Flutter 安裝。Swift Package Manager 與 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 部分下,將 config 子部分中的 enable-swift-package-manager 設定為 false

pubspec.yaml
yaml
# The following section is specific to Flutter packages.
flutter:
  config:
    enable-swift-package-manager: false

這會為該專案的所有貢獻者關閉 Swift Package Manager。

全域性關閉所有專案

#

執行以下命令

sh
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

  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
    // 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("13.0"),
            .macOS("10.15")
        ],
        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: [
            .package(name: "FlutterFramework", path: "../FlutterFramework")
        ],
        targets: [
            .target(
                // TODO: Update your target name.
                name: "plugin_name",
                dependencies: [
                    .product(name: "FlutterFramework", package: "FlutterFramework")
                ],
                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
                ]
            )
        ]
    )
    
  5. 更新 Package.swift 檔案中的受支援平臺

    Package.swift
    swift
        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("13.0"),
            .macOS("10.15")
        ],
    
  6. 更新 Package.swift 檔案中的包、庫和目標名稱。

    Package.swift
    swift
    let package = Package(
        // TODO: Update your plugin name.
        name: "plugin_name",
        platforms: [
            .iOS("13.0"),
            .macOS("10.15")
        ],
        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
                ]
            )
        ]
    )
    
  7. 如果你的外掛有 PrivacyInfo.xcprivacy 檔案,請將其移動到 ios/plugin_name/Sources/plugin_name/PrivacyInfo.xcprivacy,並取消註釋 Package.swift 檔案中的資源配置。

    Package.swift
    swift
                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
                ],
    
  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. Flutter 3.41 新功能! 新增 FlutterFramework 作為依賴項並更新 Dart/Flutter 版本。

    更新 Package.swift 以包含 FlutterFramework

    Package.swift
    swift
    dependencies: [
        .package(name: "FlutterFramework", path: "../FlutterFramework")
    ],
    targets: [
        .target(
            // TODO: Update your target name.
            name: "plugin_name",
            dependencies: [
                .product(name: "FlutterFramework", package: "FlutterFramework")
            ],
    

    pubspec.yaml 中,更新版本號為

    pubspec.yaml
    yaml
    environment:
      sdk: ^3.11.0
      flutter: ">=3.41.0"
    
  11. ios/Assetsios/Resourcesios/Classes 目錄現在應該是空的,可以刪除。

  12. 如果你的外掛使用了 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(),
    
  13. 使用你可能需要的任何自定義設定更新你的 Package.swift 檔案。

    1. 在 Xcode 中開啟 ios/plugin_name/ 目錄。

    2. 在 Xcode 中開啟你的 Package.swift 檔案。確認 Xcode 沒有為此檔案產生任何警告或錯誤。

    3. 如果你的 ios/plugin_name.podspec 檔案包含 CocoaPods dependency,請將相應的 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

  14. 更新你的 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']}
    
  15. 更新從 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
    
  16. 如果你的 .gitignore 沒有包含 .build/.swiftpm/ 目錄,你需要更新你的 .gitignore 以包含它們:

    .gitignore
    .build/
    .swiftpm/
    

    將外掛的更改提交到你的版本控制系統。

  17. 驗證外掛在使用 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 驗證檢查。

      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
      
  18. 驗證外掛在使用 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 中開啟外掛的示例應用。確保 Package Dependencies 顯示在左側的 Project Navigator 中。

  19. 驗證測試透過。

在本指南中,將 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
    // 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("13.0"),
            .macOS("10.15")
        ],
        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")
                ]
            )
        ]
    )
    
  5. 更新 Package.swift 檔案中的受支援平臺

    Package.swift
    swift
        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("13.0"),
            .macOS("10.15")
        ],
    
  6. 更新 Package.swift 檔案中的包、庫和目標名稱。

    Package.swift
    swift
    let package = Package(
        // TODO: Update your plugin name.
        name: "plugin_name",
        platforms: [
            .iOS("13.0"),
            .macOS("10.15")
        ],
        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")
                ]
            )
        ]
    )
    
  7. 如果你的外掛有 PrivacyInfo.xcprivacy 檔案,請將其移動到 ios/plugin_name/Sources/plugin_name/PrivacyInfo.xcprivacy,並取消註釋 Package.swift 檔案中的資源配置。

    Package.swift
    swift
                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
                ],
    
  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,請跳過此步驟。

    如果你為了建立一個 Test 子模組而使用了 CocoaPods 的 modulemap,請考慮在 Swift Package Manager 中將其移除。注意,這會使所有公共標頭檔案都可透過該模組訪問。

    若要從 Swift Package Manager 移除 modulemap 但保留在 CocoaPods 中,請在外掛的 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,請將相應的 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

  16. 更新你的 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']}
    
  17. 更新從 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"];
    
  18. 如果你的 ios/plugin_name/Sources/plugin_name/include 目錄僅包含一個 .gitkeep,你需要更新你的 .gitignore 以包含以下內容:

    .gitignore
    !.gitkeep
    

    執行 flutter pub publish --dry-run 以確保 include 目錄被正確釋出。

  19. 將外掛的更改提交到你的版本控制系統。

  20. 驗證外掛在使用 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 驗證檢查

      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
      
  21. 驗證外掛在使用 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 中開啟外掛的示例應用。確保 Package Dependencies 顯示在左側的 Project Navigator 中。

  22. 驗證測試透過。

#

如果你的外掛包含示例,建議將外掛作為本地包新增到示例應用中。這不是必需的,但在示例應用中編輯外掛原始碼時,它能提供更好的 Xcode 支援。參見 issue #179032

將外掛新增為本地包

#
  1. 在終端中導航到 my_plugin

  2. 執行以下命令在 Xcode 中開啟示例應用的工作區(如果你的外掛針對 macOS,請將 ios 替換為 macos):

bash
open example/ios/Runner.xcworkspace
  1. 右鍵點選 Flutter > Add Files to “Runner”

    Add Files to Runner

  2. 選擇 my_plugin/ios/my_plugin(或根據你的外掛支援的平臺選擇 macosdarwin)。

  3. 確保選中“Reference files in place”(通常是預設選項),然後點選 Finish

    Select Reference files in place

這會將外掛新增為本地包,但它會以絕對路徑引用,這對於釋出是不利的。要將其更改為相對路徑,請執行以下步驟。

修改為相對路徑

#
  1. 在 File Inspector 中複製外掛的“Full Path”。

    Copy Full Path

  2. 在終端中:open -a Xcode example/ios/Runner.xcodeproj/project.pbxproj

  3. 查詢以下內容:

    path = [COPIED FULL PATH]; sourceTree = "<absolute>"
    

    例如

    path = /Users/username/path/to/my_plugin/ios/my_plugin; sourceTree = "<absolute>"
    
  4. 並替換為相對路徑:

    path = ../../ios/my_plugin; sourceTree = "<group>"
    

    (根據需要將 ios 調整為 macosdarwin)。

如何更新外掛示例應用中的單元測試

#

如果你的外掛有原生 XCTests,並且滿足以下任一條件,你可能需要更新它們以適配 Swift Package Manager:

  • 你為測試使用了 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. 導航到專案的 Package DependenciesThe project's package dependencies

    專案的包依賴項

  4. 點選 + 按鈕,透過在右上角的搜尋欄中搜索,新增任何僅限測試的依賴項。Search for test-only dependencies

    搜尋僅限測試的依賴項

  5. 確保將該依賴項新增到 RunnerTests 目標中。Ensure the dependency is added to the `RunnerTests` target

    確保將該依賴項新增到 RunnerTests 目標

  6. 點選 Add Package 按鈕。

  7. 如果你在 Package.swift 檔案中顯式將外掛庫型別設定為 .dynamicApple 不推薦),你也需要將其新增為 RunnerTests 目標的依賴項。

    1. 確保 RunnerTestsBuild Phases 中有 Link Binary With Libraries 構建階段The `Link Binary With Libraries` Build Phase in the `RunnerTests` target

      RunnerTests 目標中的 Link Binary With Libraries 構建階段

      如果構建階段不存在,請建立一個。點選 add 按鈕,然後點選 New Link Binary With Libraries PhaseAdd `Link Binary With Libraries` Build Phase

      新增 Link Binary With Libraries 構建階段

    2. 導航到專案的 Package Dependencies

    3. 點選 add 按鈕。

    4. 在彈出的對話方塊中,點選 Add Local... 按鈕。

    5. 導航到 plugin_name/plugin_name_ios/ios/plugin_name_ios 並點選 Add Package 按鈕。

    6. 確保將其新增到 RunnerTests 目標,並點選 Add Package 按鈕。

  8. 確保透過 Product > Test 測試透過。