Removing Swift Package Manager (SPM) from a Flutter iOS Project: Lessons Learned
A deep dive into migrating a Flutter iOS project from Swift Package Manager back to CocoaPods, including debugging, Podspec analysis, and dependency cleanup.
Table of contents
- Context: What I’m Building
- What I Learned / Architecture Decisions
- Key Concepts Explained
- 🧩 CocoaPods vs Swift Package Manager
- ⚙️ What Happens Behind the Scenes
- Code Snippets & Scenarios
- 🔍 Removing SPM from Xcode
- 🧱 CocoaPods Podfile Fix
- 🧠 Debugging the Missing Frameworks
- What Worked, What Didn’t
- What I’d Do Differently
- Real-World Use Case
- References / Further Reading
Context: What I’m Building
As part of my ongoing work on a Flutter SDK that wraps a native iOS SDK (ComplyCube), I’ve had to navigate multiple dependency management systems on iOS — namely CocoaPods and Swift Package Manager (SPM). While SPM integration initially simplified setup, it later caused build inconsistencies when the plugin and host app fell out of sync.
This post documents how I reverted my Flutter iOS project from SPM back to CocoaPods, the technical challenges I faced, and what I learned along the way.
What I Learned / Architecture Decisions
The original setup worked like this:
- The Flutter plugin used CocoaPods to include the ComplyCube iOS SDK.
- The host Flutter app also relied on CocoaPods.
Later, I migrated both to use SPM, which Xcode handles natively. However, when reverting to CocoaPods:
- The Flutter plugin successfully returned to Pods.
- The host app, however, still attempted to fetch dependencies from SPM, leading to confusing build errors.
This highlighted an important architectural insight:
When mixing Flutter’s plugin architecture with iOS dependency managers, consistency is everything. The plugin and app must use the same dependency manager, or Xcode will attempt to resolve both.
Key Concepts Explained
🧩 CocoaPods vs Swift Package Manager
- CocoaPods: A Ruby-based dependency manager that generates a workspace (
.xcworkspace) and integrates Pods through aPodfile. It’s the default for Flutter plugins. - SPM: Apple’s native dependency manager integrated into Xcode. It uses
Package.swift,Package.resolved, and.swiftpmmetadata for automatic fetching.
The catch: SPM and CocoaPods don’t share dependency resolution metadata. If one remains in your workspace, Xcode will still attempt to fetch it.
⚙️ What Happens Behind the Scenes
When building a Flutter app for iOS:
- Flutter runs
pod installinsideios/to configure Pods. - The generated
Runner.xcworkspacelinks plugin frameworks through CocoaPods. - If
.swiftpmorPackage.resolvedexists, Xcode still checks for SPM packages and may attempt to fetch or build them.
This explains why my build logs still showed lines like:
Fetching Swift packages…
Updating Package.resolved…
even though I thought I’d reverted to CocoaPods.
Code Snippets & Scenarios
🔍 Removing SPM from Xcode
To fully remove SPM dependencies:
# Step 1: Delete SPM metadata
rm -rf ios/.swiftpm
rm -f ios/Package.resolved
# Step 2: Clean Xcode build cache
rm -rf ~/Library/Developer/Xcode/DerivedData
# Step 3: Reinstall CocoaPods
cd ios
pod deintegrate
pod repo update
pod install
cd ..
flutter clean
flutter pub get
Then reopen the workspace:
open ios/Runner.xcworkspace
Finally, remove any remaining entries under Package Dependencies in Xcode’s Project Navigator.
🧱 CocoaPods Podfile Fix
When reverting, I discovered that my Podfile needed proper framework linkage to handle Swift modules. Here’s the working configuration:
platform :ios, '13.0'
use_frameworks! :linkage => :dynamic
use_modular_headers!
target 'Runner' do
flutter_application_path = '../'
eval(File.read(File.join(flutter_application_path, '.ios', 'Flutter', 'podhelper.rb')), binding)
end
post_install do |installer|
installer.pods_project.targets.each do |target|
flutter_additional_ios_build_settings(target)
target.build_configurations.each do |config|
config.build_settings['BUILD_LIBRARY_FOR_DISTRIBUTION'] = 'YES'
end
end
end
This ensures that all Swift frameworks are linked dynamically and are exportable across binary boundaries — critical when your plugin uses multiple nested frameworks.
🧠 Debugging the Missing Frameworks
After reverting to Pods, I ran into this error:
Unable to find module dependency: 'ComplyCubeAppDesign'
import ComplyCubeAppDesign
The issue: the ComplyCubeMobileSDK.xcframework referenced four internal Swift modules that weren’t shipped inside the Pod.
This led to a deeper discovery: SPM automatically resolves transitive Swift dependencies, but CocoaPods does not — every framework must be declared explicitly.
What Worked, What Didn’t
✅ Worked:
- Removing
.swiftpmandPackage.resolvedstopped SPM fetches. - Reintegrating CocoaPods rebuilt a clean dependency graph.
- Using
use_frameworks!andBUILD_LIBRARY_FOR_DISTRIBUTIONfixed most Swift import issues.
❌ Didn’t Work:
- The ComplyCube Podspec only included the umbrella binary (
ComplyCubeMobileSDK.xcframework), not the transitive frameworks (AppDesign,AppTheme, etc.). - Xcode still showed gray “Package Dependencies” because of cached workspace metadata.
What I’d Do Differently
If I were designing an SDK or plugin again:
-
Keep dependency management consistent — use either SPM or CocoaPods across both the SDK and the app. Mixed setups are fragile.
-
Ship all frameworks explicitly — list all
.xcframeworksin the Podspec, for example:spec.vendored_frameworks = [ 'Frameworks/ComplyCubeMobileSDK.xcframework', 'Frameworks/ComplyCubeAppDesign.xcframework', 'Frameworks/ComplyCubeAppTheme.xcframework', 'Frameworks/ComplyCubeCommon.xcframework', 'Frameworks/ComplyCubeNetworking.xcframework' ] -
Validate Podspecs using
pod lib lintbefore publishing. -
Version alignment — ensure Podspec versions match Git tags and actual binary releases.
-
Document both paths (SPM and CocoaPods) clearly in SDK README to avoid confusion for integrators.
Real-World Use Case
In a large app ecosystem, teams often manage SDKs that need to support multiple package managers (for example, open-source SDKs distributed both via SPM and CocoaPods). This story reflects a real-world friction point:
When SDK authors rely on SPM transitivity, downstream users relying on CocoaPods will experience missing module issues unless the Podspec explicitly lists all binaries.
Understanding this distinction helps teams avoid “phantom” Swift import errors and provides smoother CI/CD pipelines.
References / Further Reading
- Flutter: Developing Packages & Plugins for iOS
- Apple Developer Docs – Swift Package Manager
- CocoaPods: Using Frameworks
- Xcode: Manage Swift Packages
- ComplyCube iOS SDK GitHub Repository
Summary: Migrating between CocoaPods and SPM in Flutter projects is not just a tooling exercise — it’s a dependency architecture problem. This experience taught me how dependency managers interact with Flutter’s plugin model, why transitive modules must be explicitly defined for CocoaPods, and how to maintain a clean build system across iOS and Flutter.
This debugging journey didn’t just fix a build — it deepened my understanding of how Flutter, Xcode, and Swift frameworks interoperate under the hood.
Share
More to explore
Keep exploring
1/11/2026
Kotlin to C/C++ Transition Guide: A Systems Programmer's Cheat Sheet
Preparing for a systems programming interview but haven't touched C/C++ since university? This guide bridges your Kotlin knowledge to C/C++ with side-by-side syntax comparisons, memory management deep dives, and critical undefined behaviors you need to know.
1/10/2026
The SDK Mindset: Why Your Code Isn't Your Own Anymore
A deep dive into the paradigm shift from application development to SDK design, and why building libraries requires a fundamentally different mental model
12/16/2025
Building a Frictionless Android Sample App: README Funnel, Doctor Script, and a CI Compatibility Matrix
My AI-assisted learning log on turning an Android SDK demo into a low-friction client experience: a decision-tree README, environment doctor scripts, and a GitHub Actions build matrix that generates a compatibility matrix.
Previous
German eID (Personalausweis) on Android with OIDC: A Practical Learning Journey
Next