This report documents the implementation of Swift Package Manager (SPM) support for the printing Flutter plugin, as requested in DavBfr/dart_pdf#1778.
Flutter is migrating from CocoaPods to Swift Package Manager. CocoaPods is now in maintenance mode. This change future-proofs the printing plugin by supporting both SPM and CocoaPods.
Following the official Flutter SPM migration guide, we created the standard SPM directory layout for both platforms:
printing/ios/
├── printing.podspec # Updated path
└── printing/ # NEW: SPM package directory
├── Package.swift # NEW: SPM manifest
└── Sources/printing/
├── CustomPrintPaper.swift # Moved from ios/Classes/
├── PrintJob.swift # Moved from ios/Classes/
├── PrintingPlugin.swift # Moved from ios/Classes/
└── PrintingPluginFfi.swift # NEW: Pure Swift FFI bridge
printing/macos/
├── printing.podspec # Updated path
└── printing/ # NEW: SPM package directory
├── Package.swift # NEW: SPM manifest
└── Sources/printing/
├── PrintJob.swift # Moved from macos/Classes/
├── PrintingPlugin.swift # Moved from macos/Classes/
└── PrintingPluginFfi.swift # NEW: Pure Swift FFI bridge
Problem: The original plugin had both Swift (.swift) and Objective-C (.m) source files. SPM does not support mixed-language targets — this is a fundamental SPM limitation.
Solution: Converted the Objective-C FFI bridge files (PrintingPlugin.m) to pure Swift using @_cdecl. The original ObjC files defined two C-linkage functions (net_nfet_printing_set_document and net_nfet_printing_set_error) that are called from Dart via dart:ffi. The @_cdecl Swift attribute exports functions with C linkage, providing identical behavior.
Original ObjC (PrintingPlugin.m):
#import <printing/printing-Swift.h>
void net_nfet_printing_set_document(uint32_t job, const uint8_t* doc, uint64_t size) {
[PrintingPlugin setDocumentWithJob:job doc:doc size:size];
}
void net_nfet_printing_set_error(uint32_t job, const char* message) {
[PrintingPlugin setErrorWithJob:job message:message];
}New Swift (PrintingPluginFfi.swift):
@_cdecl("net_nfet_printing_set_document")
public func net_nfet_printing_set_document(job: UInt32, doc: UnsafePointer<UInt8>, size: UInt64) {
PrintingPlugin.setDocument(job: job, doc: doc, size: size)
}
@_cdecl("net_nfet_printing_set_error")
public func net_nfet_printing_set_error(job: UInt32, message: UnsafePointer<CChar>) {
PrintingPlugin.setError(job: job, message: message)
}CustomPrintPaper.swift (iOS only) was missing import UIKit. Under CocoaPods, UIKit types were available via the Flutter umbrella header, but SPM requires explicit imports per file.
Both ios/printing.podspec and macos/printing.podspec were updated to point to the new file locations:
- Before:
s.source_files = 'Classes/**/*' - After:
s.source_files = 'printing/Sources/printing/**/*.swift'
Added SPM build artifacts to the repository .gitignore:
.build/
.swiftpm/
iOS (ios/printing/Package.swift):
- Platform:
.iOS("12.0") - Single target with all Swift sources
macOS (macos/printing/Package.swift):
- Platform:
.macOS("10.14") - Single target with all Swift sources
When a Flutter project has SPM enabled (flutter config --enable-swift-package-manager), Flutter:
- Creates a local SPM package in the app's
ios/Flutter/ephemeral/Packages/directory - Resolves plugin packages by finding
Package.swiftfiles in the plugin's platform directories - Adds them as local package dependencies in the Xcode project
- Xcode builds the SPM packages alongside the app
When SPM is disabled, Flutter falls back to CocoaPods:
- The
podspecfiles define how the plugin's native code is compiled - The updated
source_filespath points to the same Swift files in the new location - CocoaPods compiles them as before
The plugin simultaneously supports both package managers. The same Swift source files are referenced by both Package.swift (for SPM) and printing.podspec (for CocoaPods). This is the recommended approach per Flutter documentation.
All 4 build configurations were tested successfully:
| Platform | Package Manager | Result |
|---|---|---|
| iOS | SPM | ✅ Built successfully |
| iOS | CocoaPods | ✅ Built successfully |
| macOS | SPM | ✅ Built successfully |
| macOS | CocoaPods | ✅ Built successfully |
Dart unit tests: 11 passed, 2 failed (pre-existing failures due to missing test font file open-sans.ttf, unrelated to this change).
| File | Purpose |
|---|---|
printing/ios/printing/Package.swift |
SPM manifest for iOS |
printing/macos/printing/Package.swift |
SPM manifest for macOS |
printing/ios/printing/Sources/printing/PrintingPluginFfi.swift |
Pure Swift FFI bridge (replaces ObjC) |
printing/macos/printing/Sources/printing/PrintingPluginFfi.swift |
Pure Swift FFI bridge (replaces ObjC) |
| From | To |
|---|---|
printing/ios/Classes/PrintingPlugin.swift |
printing/ios/printing/Sources/printing/PrintingPlugin.swift |
printing/ios/Classes/PrintJob.swift |
printing/ios/printing/Sources/printing/PrintJob.swift |
printing/ios/Classes/CustomPrintPaper.swift |
printing/ios/printing/Sources/printing/CustomPrintPaper.swift |
printing/macos/Classes/PrintingPlugin.swift |
printing/macos/printing/Sources/printing/PrintingPlugin.swift |
printing/macos/Classes/PrintJob.swift |
printing/macos/printing/Sources/printing/PrintJob.swift |
| File | Reason |
|---|---|
printing/ios/Classes/PrintingPlugin.m |
Replaced by PrintingPluginFfi.swift |
printing/macos/Classes/PrintingPlugin.m |
Replaced by PrintingPluginFfi.swift |
printing/ios/Classes/ (directory) |
Empty after moves |
printing/macos/Classes/ (directory) |
Empty after moves |
| File | Change |
|---|---|
printing/ios/printing.podspec |
Updated source_files path |
printing/macos/printing.podspec |
Updated source_files path |
printing/ios/printing/Sources/printing/CustomPrintPaper.swift |
Added import UIKit |
.gitignore |
Added .build/, .swiftpm/, !.gitkeep |
@_cdecl is not officially part of the public Swift language specification — it's a Swift compiler attribute prefixed with underscore, meaning it's technically "not guaranteed stable." However:
- It is widely used in the Swift ecosystem, including by Apple's own projects
- It has been stable for many years (since Swift 2+)
- It is the only way to export C-linkage functions from pure Swift
- The alternative (keeping ObjC files) is incompatible with SPM's single-language-per-target requirement
- Flutter's own plugins and many community plugins use this attribute
Risk: Very low. If Swift ever removes @_cdecl, they would need to provide an alternative, and it would be a major ecosystem-breaking change.
The Package.swift files use:
- iOS:
12.0(the podspec says8.0, but Flutter 3.22+ requires iOS 12.0+) - macOS:
10.14(the podspec says10.11, but Flutter 3.22+ requires macOS 10.14+)
The podspec deployment targets were not updated to avoid breaking existing CocoaPods users, though they are effectively dead code since Flutter enforces higher minimums.
The flutter build commands automatically migrated the demo app's Xcode project files to support SPM (updated project.pbxproj, Runner.xcscheme, AppDelegate.swift, etc.). These are auto-generated changes by Flutter and are included in the git diff but are not part of the plugin change itself.
The current plugin has separate ios/ and macos/ implementations. Flutter also supports a darwin/ directory for shared iOS/macOS code. This migration keeps the existing separate structure — merging into darwin/ would be a separate, larger refactoring effort.