Skip to content

feat(darwin): add Swift Package Manager support, keeping CocoaPods - #94

Open
kristjan wants to merge 5 commits into
uxduck:mainfrom
HahaMoment:spm-darwin
Open

feat(darwin): add Swift Package Manager support, keeping CocoaPods#94
kristjan wants to merge 5 commits into
uxduck:mainfrom
HahaMoment:spm-darwin

Conversation

@kristjan

Copy link
Copy Markdown

Closes #90.

Adds Swift Package Manager support for the darwin plugin, keeping CocoaPods working. Both build systems are supported side by side, per Flutter's guidance.

Why now

Flutter 3.44 defaults to Swift Package Manager. Today that is a warning; the Flutter team has said it becomes an error. CocoaPods trunk goes read-only on 2 December 2026. pub.dev already applies a scoring penalty for this on the package page.

Prior art

This builds directly on #92 by @PhantomRay, which reached the same structural design independently and which they withdrew 15 minutes after opening, without any review. I branched from wip/spm-support-july-24 so their commits stay in the history, and their code is co-authored on the commit that touches it. I commented on #92 before starting.

Two things are different here:

  1. CocoaPods is retained. feat: migrate iOS example to SwiftPM #92 deleted darwin/push.podspec and both example Podfiles, and declared "Apple consumers must use Swift Package Manager" as a breaking change. That seemed like a hard sell for a package with ~22k weekly downloads, so the podspec is kept and repointed instead. See below for how that is made to work.
  2. The diff is only the migration. feat: migrate iOS example to SwiftPM #92 also upgraded the example app's dependencies and edited example/lib/main.dart, metadata_sliver.dart and the macOS AppDelegate, across 39 files. All of that is reverted here. This PR touches darwin/, the pigeon config, .gitignore, the changelog and the version. Nothing else — no lib/ changes, no example changes, no formatting sweeps.

What changed

darwin/
  push.podspec                       repointed, deployment targets raised
  push/
    Package.swift                    new
    Sources/
      push_pigeon/
        include/push_pigeon/PushApi.h    moved
        PushApi.m                        moved
      push/
        PushPlugin.swift             was SwiftPushPlugin.swift
        PushHostHandlers.swift       moved
        UserNotificationCenterDelegateHandlers.swift  moved
        Serialization.swift          moved

The Objective-C trampoline is gone. SwiftPM cannot mix Objective-C and Swift in one target, so the pigeon output is now its own push_pigeon target that the Swift target depends on. PushPlugin.m forwarded to SwiftPushPlugin via the generated push-Swift.h, which would have made the Objective-C target depend on the Swift target while the Swift target already depends on Objective-C — a cycle SwiftPM cannot express. So PushPlugin.h/.m are deleted and the Swift class is promoted to PushPlugin, pinned with @objc(PushPlugin). pluginClass: PushPlugin in pubspec.yaml is unchanged, so no app-side change is needed. SwiftPushPlugin remains as a typealias for source compatibility.

How CocoaPods keeps working. Splitting into two SwiftPM targets means the Swift files need an explicit import push_pigeon, and that module does not exist under CocoaPods, which compiles everything into one module. The import is guarded:

#if SWIFT_PACKAGE
    import push_pigeon
#endif

and the podspec declares the pigeon headers as public_header_files, so the generated umbrella header still exposes the PU-prefixed types to Swift.

Deployment targets are raised to iOS 13.0 and macOS 10.15, from iOS 11.0 and macOS 10.14, to match the SwiftPM minimums. This is the only user-visible change in the PR. Every Flutter version still receiving support already requires at least these.

Version bumped to 3.4.0, since a deployment target rise is not a patch.

Deliberately not included

  • Request From Flutter Team: Migrate UNUserNotificationCenterDelegate API usage #93 UNUserNotificationCenterDelegate and Add UIScene life cycle support #89 UIScene lifecycle. Both change iOS notification behaviour and belong in their own PRs with their own testing.
  • Regenerating pigeon as Swift. pigeons/push_api.dart has commented-out swiftOut lines, and doing that would collapse this to a single trivial target. It would also mean rewriting all four handler files, roughly 500 lines, which is a behaviour risk inside a migration PR. Worth doing separately if you want it.
  • The example app's own SwiftPM migration. The Flutter tool migrates the Xcode project automatically on first build, so committing it would just be churn in this diff.

How this was verified

Flutter 3.44.2 stable, Xcode 26.5, on the plugin's own example/ app.

With Swift Package Manager enabled:

Build Result
flutter build ios --no-codesign ✅ Built build/ios/iphoneos/Runner.app
flutter build macos ✅ compiles and links

push is absent from both regenerated Podfile.locks in this configuration, which confirms SwiftPM really is providing it rather than CocoaPods silently covering for it. The generated iOS GeneratedPluginRegistrant.m resolves [PushPlugin registerWithRegistrar:] through the @import push; fallback and links, confirming the Swift class is reachable from the Objective-C registrant. macOS emits a Swift registrant and resolves it directly.

With Swift Package Manager disabled (flutter config --no-enable-swift-package-manager), after reverting the tool's Xcode project migration:

Build Result
flutter build ios --no-codesign ✅ Built build/ios/iphoneos/Runner.app
flutter build macos ✅ compiles and links

Both Podfile.locks list push (0.0.1) in this configuration, confirming the CocoaPods path is genuinely exercised.

One caveat, stated plainly: the two flutter build macos runs above are xcodebuild ... CODE_SIGNING_ALLOWED=NO on the workspace that flutter build macos had already configured. flutter build macos itself fails on my machine for want of a Mac App Development provisioning profile for uk.orth.push-example, which is a signing problem unrelated to this change. Compilation and linking of both targets is verified; the codesign step is not.

Also verified: dart run pigeon --input pigeons/push_api.dart regenerates the Objective-C output byte-identically at the new paths, so the next regeneration will not silently write to the old location.

Not verified, and I want to be honest about it: I have not yet run this on a physical iOS device to confirm a real APNs token arrives, a notification is delivered and a tap is routed. The simulator cannot receive real pushes. This PR does not intend any behaviour change — the Swift is moved, not rewritten, apart from the class rename — but that is an argument, not evidence. I will follow up here once I have device results, and I would not blame you for waiting for that before merging.

Offer

I maintain an app that depends on push and I would be glad to help maintain the package — triage, reviews, or the remaining deprecations (#89, #93). It looks more like you are short on time than gone. Happy to take as much or as little as is useful, and equally happy if the answer is no.

PhantomRay and others added 5 commits July 25, 2026 00:13
…tion script

chore: Remove Pods.xcodeproj reference from workspace contents

fix: Update AppDelegate to use PushPlugin instead of SwiftPushPlugin

chore: Update pubspec.lock with dependency version upgrades

chore: Update pubspec.yaml to require Dart SDK 3.0.0 and Flutter 3.44.0

fix: Modify Pigeon output paths for PushApi to match new directory structure

chore: Update pubspec.lock with additional dependency version upgrades

chore: Update pubspec.yaml to require Dart SDK 3.0.0 and Flutter 3.44.0
Reverts everything on this branch that is not the Swift Package Manager
migration itself, so the change stays reviewable:

- example app dependency upgrades, main.dart, metadata_sliver.dart and the
  macOS AppDelegate
- the regenerated Android pigeon bindings
- README, UPDATING and CHANGELOG edits
- the root and example lockfiles, and the pubspec environment constraints

Also restores darwin/push.podspec and the example Podfiles. CocoaPods support
is retained alongside Swift Package Manager, per Flutter's guidance that
plugins support both until further notice; the podspec is repointed at the new
source paths in a following commit.
Swift Package Manager cannot mix Objective-C and Swift in one target, so the
pigeon output now lives in its own `push_pigeon` target that the Swift target
depends on. CocoaPods has no such split: it compiles every source into one
module, where `push_pigeon` does not exist. Guard the new imports with
`#if SWIFT_PACKAGE` so both build systems compile the same sources.

- repoint `push.podspec` at `push/Sources/**`, and declare the pigeon headers
  as `public_header_files` so the generated umbrella header still exposes the
  `PU`-prefixed types to Swift under CocoaPods
- raise the podspec deployment targets to iOS 13.0 and macOS 10.15 to match
  the Package.swift minimums. This is the only user-visible change here
- pin the Objective-C runtime name with `@objc(PushPlugin)`, so the
  `GeneratedPluginRegistrant.m` that Flutter emits for iOS resolves
  `[PushPlugin registerWithRegistrar:]` now that the Objective-C trampoline
  is gone
- add `SwiftPushPlugin` as a typealias, for source compatibility with anyone
  referencing the pre-3.4.0 Swift class name
- ignore `.build/` and `.swiftpm/`, and drop the empty `darwin/Assets/`

Co-authored-by: Ray Wang <PhantomRay@users.noreply.github.com>
@kristjan

Copy link
Copy Markdown
Author

Looks like Claude got excited about contributing to upstream, so apologies for the AI PRs, but the maintenance offer stands and I'll have on-device verification in a few weeks.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add Swift Package Manager support

2 participants