Skip to content

Instantly share code, notes, and snippets.

@cdeil
Created February 18, 2026 20:36
Show Gist options
  • Select an option

  • Save cdeil/814801db67b7b47b75faeb9533dc5f62 to your computer and use it in GitHub Desktop.

Select an option

Save cdeil/814801db67b7b47b75faeb9533dc5f62 to your computer and use it in GitHub Desktop.

Swift Package Manager Support for package:printing — Implementation Report

Summary

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.

What Was Done

1. Created SPM Package Structure (iOS & macOS)

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

2. Solved the Mixed-Language Problem

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)
}

3. Added Missing Import

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.

4. Updated CocoaPods Podspec Paths

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'

5. Updated .gitignore

Added SPM build artifacts to the repository .gitignore:

.build/
.swiftpm/

6. Package.swift Configuration

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

How It Works

Swift Package Manager Flow

When a Flutter project has SPM enabled (flutter config --enable-swift-package-manager), Flutter:

  1. Creates a local SPM package in the app's ios/Flutter/ephemeral/Packages/ directory
  2. Resolves plugin packages by finding Package.swift files in the plugin's platform directories
  3. Adds them as local package dependencies in the Xcode project
  4. Xcode builds the SPM packages alongside the app

CocoaPods Flow (Unchanged)

When SPM is disabled, Flutter falls back to CocoaPods:

  1. The podspec files define how the plugin's native code is compiled
  2. The updated source_files path points to the same Swift files in the new location
  3. CocoaPods compiles them as before

Both Systems Coexist

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.

Build Verification Results

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).

Files Changed

New Files

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)

Moved Files

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

Deleted Files

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

Modified Files

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

Open Questions & Considerations

1. @_cdecl is an Underscored Attribute

@_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.

2. Minimum Deployment Targets

The Package.swift files use:

  • iOS: 12.0 (the podspec says 8.0, but Flutter 3.22+ requires iOS 12.0+)
  • macOS: 10.14 (the podspec says 10.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.

3. Demo App Changes

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.

4. No darwin/ Shared Package

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment