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
swiftToolsVersionfehlt, bricht der Sync mit „Package traits require an explicit Swift tools version of 6.1 or higher.“ ab. - Wenn
swiftToolsVersionniedriger als 6.1 ist, bricht der Sync ab und meldet, dass die Version zu niedrig ist. swiftToolsVersionmuss so aussehen:6.1oder6.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
experimentalund könnten nachios.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
swiftToolsVersionauf 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.gradleoder 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.