Zum Hauptinhalt springen

So nutzt du SPM Package Traits in Capacitor 8

Nutze SPM Package Traits in Capacitor 8: Aktiviere optionale Plugin-Features mit packageTraits, setze swiftToolsVersion 6.1 und füge Traits zu deinem eigenen Plugin hinzu.

Illustration der Swift Package Manager Konfiguration in Capacitor iOS-Projekten

Um SPM-Package-Traits in Capacitor 8 zu nutzen, setze experimental.ios.spm.swiftToolsVersion zu "6.1" und listet die Traits pro Plugin unter experimental.ios.spm.packageTraits in deiner Capacitor-Config, dann führe aus bunx cap sync ios. CLI schreibt diese Traits in den generierten CapApp-SPM/Package.swift, und SwiftPM baut das Plugin mit den passenden optionalen Abhängigkeiten und code-Pfaden. Das erfordert Capacitor CLI 8.3.0 oder neuer und gilt nur für iOS-Projekte, die den Swift Package Manager nutzen.

Traits schließen die letzte große Funktionslücke zwischen SPM und CocoaPods für Plugins: optionale native Abhängigkeiten, die früher Podspec-Subspecs waren. Dieser Guide deckt die App-Seite, die Seite der Plugin-Autoren und die Grenzen der aktuellen experimentellen Unterstützung ab.

Was Package Traits sind

Package Traits stammen aus dem Swift-Evolution-Vorschlag SE-0450, implementiert in Swift 6.1 (Xcode 16.3 und neuer). Ein Package deklariert benannte Traits in seiner Package.swift. Wer das Package einbindet, wählt aus, welche aktiviert werden. Ein Trait kann:

  • eine Abhängigkeit optional machen (condition: .when(traits: [...]) bei einer Target-Abhängigkeit),
  • aktiviere code Pfade, da jeder aktivierte Trait als Kompilierungsbedingung verfügbar ist (#if TraitName),
  • schalte Build-Einstellungen wie Defines oder Linker-Flags um,
  • aktiviere andere Traits.

Ein Package kann Traits auch als Standard markieren. Diese sind aktiv, sofern der Consumer keine explizite Liste übergibt.

Wenn du CocoaPods-Subspecs oder Cargo Features in Rust kennst, ist es dasselbe Prinzip.

Warum das in Capacitor wichtig ist

Capacitor generiert das Package.swift für dich, sodass du Dependency-Zeilen nicht manuell bearbeiten kannst, um Traits zu übergeben. Vor CLI 8.3 hatten Plugin-Autoren mit einem optionalen nativen SDK bei SPM zwei schlechte Optionen: immer das SDK linken, was die Größe erhöht und teils Privacy- oder Export-Compliance-Aufwand bedeutet, oder ein zweites Plugin veröffentlichen. Typische Fälle:

  • verschlüsseltes vs. unverschlüsseltes SQLite (SQLCipher),
  • Analytics SDKs mit und ohne Erfassung von Werbe-IDs,
  • optional ein Vendor-SDK für einen Zahlungs- oder Login-Provider,
  • reines Debug-Tooling.

Traits in deiner App aktivieren

1. Versionen prüfen

bunx cap --version      # 8.3.0 or later
xcodebuild -version     # Xcode 26 (required by Capacitor 8 anyway)

Das Projekt muss SPM nutzen: ios/App/CapApp-SPM vorhanden. Falls du CocoaPods nutzt, siehe wie du deine Capacitor App auf SPM migrierst.

2. Trait-Namen des Plugins finden

Öffne das Plugin Package.swift in einem node_modules und suche nach dem traits: Array oder schau in die README. Trait-Namen sind case-sensitive.

3. Capacitor konfigurieren

import type { CapacitorConfig } from '@capacitor/cli';

const config: CapacitorConfig = {
  appId: 'com.example.app',
  appName: 'Example',
  webDir: 'dist',
  experimental: {
    ios: {
      spm: {
        swiftToolsVersion: '6.1',
        packageTraits: {
          '@acme/capacitor-db': ['.defaults', 'SQLCipher'],
        },
      },
    },
  },
};

export default config;

Der Key ist der npm Package-Name des Plugins. Der Value ist die Liste der zu aktivierenden Traits.

4. Sync durchführen und Output prüfen

bunx cap sync ios

Das generierte ios/App/CapApp-SPM/Package.swift beginnt jetzt mit // swift-tools-version: 6.1 und die Dependency-Zeile des Plugins enthält die Traits:

.package(name: "AcmeCapacitorDb", path: "../../../node_modules/@acme/capacitor-db", traits: [.defaults, "SQLCipher"])

Der CLI schreibt .defaults (oder defaults, .default, default in jedem Fall) als SwiftPM .defaults Value, und alle anderen Namen als String in Anführungszeichen.

In Xcode builden. Falls Xcode noch den alten Graph anzeigt, nutze File > Packages > Reset Package Caches.

Regeln, die CLI erzwingt

Der Capacitor CLI validiert die Config während des Syncs:

  • Falls ein Plugin eine nicht-leere Trait-Liste hat und swiftToolsVersion fehlt, bricht der Sync mit „Package traits require an explicit Swift tools version of 6.1 or higher.“ ab.
  • Wenn swiftToolsVersion niedriger als 6.1 ist, bricht der Sync ab und meldet, dass die Version zu niedrig ist.
  • swiftToolsVersion muss so aussehen: 6.1 oder 6.1.0.
  • Plugins mit einem leeren Array werden so behandelt, als wären sie nicht gelistet.

Standard-Traits behalten oder entfernen

Das Auflisten von Traits ersetzt die Defaults. Vergleich:

Konfigurationswert Ergebnis
nicht aufgeführt Default-Traits des Plugins
['SQLCipher'] nur SQLCipher, Defaults aus
['.defaults', 'SQLCipher'] Defaults plus SQLCipher

Lies die Plugin-Doku, bevor du Defaults entfernst. Ein Plugin könnte sein Standard-Backend hinter einem Default-Trait verstecken.

Traits zum eigenen Plugin hinzufügen

Wenn du ein Plugin mit einem optionalen SDK pflegst, findest du hier ein vollständiges Package.swift. Der Paketname muss mit dem Namen übereinstimmen, den der Capacitor CLI aus deinem npm Namen ableitet (@acme/capacitor-db wird zu AcmeCapacitorDb). Siehe Migration eines Capacitor Plugins zu SPM für die Regel.

// swift-tools-version: 6.1
import PackageDescription

let package = Package(
    name: "AcmeCapacitorDb",
    platforms: [.iOS(.v15)],
    products: [
        .library(name: "AcmeCapacitorDb", targets: ["DbPlugin"])
    ],
    traits: [
        .trait(name: "SQLCipher", description: "Link SQLCipher and enable encrypted databases."),
        .default(enabledTraits: [])
    ],
    dependencies: [
        .package(url: "https://github.com/ionic-team/capacitor-swift-pm.git", from: "8.0.0"),
        .package(url: "https://github.com/sqlcipher/SQLCipher.swift.git", from: "4.10.0")
    ],
    targets: [
        .target(
            name: "DbPlugin",
            dependencies: [
                .product(name: "Capacitor", package: "capacitor-swift-pm"),
                .product(name: "Cordova", package: "capacitor-swift-pm"),
                .product(
                    name: "SQLCipher",
                    package: "SQLCipher.swift",
                    condition: .when(traits: ["SQLCipher"])
                )
            ],
            path: "ios/Sources/DbPlugin"
        )
    ],
    swiftLanguageModes: [.v5]
)

Prüfe im eigenen Repository von SDK die aktuelle Package-URL, den Produktnamen und die Version, bevor du das hier kopierst.

Punkte, die man leicht übersieht:

  • Tools-Version 6.x ändert den Standard-Sprachmodus auf Swift 6 für deine Targets. Capacitor unterstützt Swift 6 noch nicht offiziell, also bleib vorerst dabei, swiftLanguageModes: [.v5] es sei denn, dein code ist bereit für Strict Concurrency.
  • Wenn du die Tools-Version deines Plugins auf 6.1 erhöhst, brauchen Nutzer Xcode 16.3+. Capacitor 8 Apps erfordern bereits Xcode 26, das ist also keine neue Einschränkung.

Nutze das Trait in code

Aktivierte Traits sind Kompilierungsbedingungen in den Targets deines Packages:

#if SQLCipher
import SQLCipher
#endif

@objc func open(_ call: CAPPluginCall) {
    let key = call.getString("encryptionKey")
    #if SQLCipher
    // open with key
    #else
    if key != nil {
        call.unavailable("Encryption requires the SQLCipher trait. Enable it in experimental.ios.spm.packageTraits.")
        return
    }
    #endif
    // open without encryption
}

Gib eine klare Fehlermeldung aus, die das Trait benennt, wenn ein Feature deaktiviert ist. Sonst bemerken App-Entwickler nur ein stilles Versagen und geben dem Plugin die Schuld.

Du kannst auch dein eigenes Flag definieren mit swiftSettings: [.define("ACME_SQLCIPHER", .when(traits: ["SQLCipher"]))] wenn du einen präfixierten Namen bevorzugst.

Entwirf Traits so, dass sie additiv sind

SwiftPM vereinheitlicht Traits über den Dependency-Graphen. Wenn zwei Pakete von deinem Plugin mit unterschiedlichen Traits abhängen, wird die Vereinigung aktiviert. Also:

  • Das Aktivieren eines Traits sollte Funktionen hinzufügen, nicht API entfernen oder Defaults ändern.
  • Avoid mutually exclusive traits. If you can’t, fail the build loudly:
#if TraitA && TraitB
#error("TraitA and TraitB cannot be enabled together")
#endif

Teste jede Kombination

swift build ein Build nur für Mac, und ein Plugin, das von Capacitor abhängt, baut nur für iOS – teste die Traits also über eine Capacitor Test-App für den iOS Simulator. Für jede Kombination (kein Eintrag für die Default-Traits, ['SQLCipher'], ['.defaults', 'SQLCipher'] und so weiter), ändere packageTraits in der Config der Test-App, dann sync und build:

bunx cap sync ios
xcodebuild build \
  -project ios/App/App.xcodeproj \
  -scheme App \
  -destination 'generic/platform=iOS Simulator' \
  CODE_SIGNING_ALLOWED=NO

Füge jede Kombination als eigenen Job zur CI hinzu.

CocoaPods-Nutzer weiterhin unterstützen

Traits existieren nur in SPM. Wenn dein Plugin zusätzlich ein podspec bereitstellt, biete dieselbe Auswahl wie bei Subspecs an:

s.default_subspec = 'Core'

s.subspec 'Core' do |core|
  core.source_files = 'ios/Sources/DbPlugin/**/*.swift'
  core.dependency 'Capacitor'
end

s.subspec 'SQLCipher' do |sc|
  sc.dependency 'AcmeCapacitorDb/Core'
  sc.dependency 'SQLCipher', '~> 4.10'
  sc.pod_target_xcconfig = { 'SWIFT_ACTIVE_COMPILATION_CONDITIONS' => '$(inherited) SQLCipher' }
end

Durch die Verwendung desselben Compilation-Condition-Namens im Subspec kannst du einen #if SQLCipher code Pfad. CocoaPods-Apps wählen dann den Subspec in ihrem Podfile aus. Capacitor CLI schreibt eine pod Zeile pro Plugin hinein capacitor_pods, also dokumentiere die zusätzliche Podfile-Zeile in deiner README. CocoaPods mit Capacitor 8 nutzen zeigt, wo die benutzerdefinierten Pod-Zeilen hingehören.

Einschränkungen

  • Experimentell. Die Optionen befinden sich unter experimental und könnten nach ios.spm.* in a future major. Expect a config rename at some point.
  • Swift 6 wird nicht offiziell unterstützt. Die eigene Doku von CLI warnt davor, dass das Setzen swiftToolsVersion auf 6.0 oder höher kann Probleme verursachen. Teste die gesamte App, besonders Plugins mit binären xcframeworks.
  • Nur für iOS. Android hat kein Äquivalent in Capacitor. Plugins verwalten optionale Android SDKs über Gradle-Variablen in variables.gradle oder separaten Artifacts.
  • Plugins müssen explizit opt-in machen. Die meisten Plugins definieren noch keine Traits. Prüfe das, bevor du damit planst.

Fehlerbehebung

Sync schlägt fehl mit: “Package traits require an explicit Swift tools version of 6.1 or higher”. Füge swiftToolsVersion: '6.1' neben packageTraits.

SwiftPM meldet ein unbekanntes Trait. Trait-Namen sind case-sensitive und müssen im Plugin vorhanden sein: Package.swift für die installierte Version.

Feature fehlt weiterhin, obwohl das Trait aktiviert wurde. Package-Caches in Xcode zurücksetzen, Build-Ordner bereinigen und prüfen, ob der generierte CapApp-SPM/Package.swift den traits: -Suffix hat. Manuelles Editieren der Datei funktioniert nicht, da CLI sie bei jedem Sync überschreibt.

Fehler in Swift code nach Erhöhung der Tool-Version. Das ist der Swift 6 Language Mode in einem Package, das du kontrollierst. Füge swiftLanguageModes: [.v5].

Namenskonflikte zwischen Plugins. Seit CLI 8.4, experimental.ios.spm.packageOptions ermöglicht das Einstellen symlink oder moduleAliases pro Plugin. Mehr dazu im Capacitor iOS-Troubleshooting-Guide.

Für den größeren SPM-Überblick lies SPM vs. CocoaPods für Capacitor. Wenn du Cloud-Builds willst, die bereits das aktuelle Xcode für SPM-Projekte nutzen, schau dir an: Capgo Build.

Live-Updates für Capacitor-Apps

Wenn ein Bug im Web-Layer live ist, liefere den Fix über Capgo aus, statt Tage auf die App-Store-Freigabe zu warten. Nutzer bekommen das Update im Hintergrund, während native Änderungen im normalen Review-Prozess bleiben.

Neues aus unserem Blog

Capgo gibt dir die besten Insights, um eine wirklich professionelle Mobile App zu erstellen.