Every morning, opening Xcode meant losing thirty seconds to pod install resolving dependencies. That small friction added up until, one weekend, I finally decided to deal with it. I have been running several wallpaper and relaxation apps on my own since 2014, reusing the same Firebase setup across many projects. Migrating from CocoaPods to Swift Package Manager (SPM) had sat at the top of my "someday" list for far too long.
This is a record of doing that migration over three weeks, with Gemini 3 Pro as a companion. The short version: the migration was not a half-day job, but using Gemini as a sounding board for design decisions removed a lot of the points where I tend to get stuck working alone. At the same time, the thing that finally cracked the hardest problem was not the AI — it was reading a linker error one line at a time. I want to be honest about both sides.
Why I wanted to leave CocoaPods now
CocoaPods is not a bad tool. It has supported iOS development admirably for years. Still, my reasons for wanting to move on as a solo developer were clear.
First, the Podfile.lock and the Pods/ directory produced subtle differences between build environments, and I had been burned more than once by errors that would not reproduce in CI. Second, Firebase had started treating SPM as a first-class distribution channel, and CocoaPods updates were occasionally lagging behind. With SPM integrated into Xcode, the dependency resolution state lives inside the project file itself. As someone who maintains apps for the long haul, that feeling of "no external directory to manage" was more reassuring than I expected.
Both of my grandfathers were temple carpenters, and perhaps because of that, whenever I build something I instinctively ask whether it will be easy to revisit later. Dependency management is the same. If I plan to maintain code for years, I want the structure to stay as plain as possible. Moving to SPM was, in part, about recovering that plainness.
What I asked Gemini 3 Pro to do first
Instead of diving into the work, I handed my entire Podfile to Gemini 3 Pro and asked it to build a mapping table to the corresponding SPM products. This helped more than I anticipated.
(gist of the prompt)
I want to migrate this Podfile to Swift Package Manager.
- Map each pod to the matching product in firebase-ios-sdk
- Assume use_frameworks! and note static vs dynamic caveats
- List settings that become unnecessary after migration (Pods-*.xcconfig, etc.)Gemini returned a table like Firebase/Core to FirebaseCore, Firebase/Analytics to FirebaseAnalytics, and Firebase/Crashlytics to FirebaseCrashlytics. It sounds mundane, but pod names and product names are not always one-to-one, and looking them up by hand quietly eats time. Getting that overview up front made the goal of the migration suddenly concrete.
The official docs cover these mappings too, but the difference was that Gemini answered against my specific input — my actual Podfile — and said "for your setup, choose this product." The value of using AI lies precisely in that translation into your own context.
The actual migration steps
Once the table existed, the work itself was uneventful.
First, in Xcode, add the package via File > Add Package Dependencies using https://github.com/firebase/firebase-ios-sdk, then attach only the products you need to the target. In my app that meant three: FirebaseAnalytics, FirebaseCrashlytics, and FirebaseRemoteConfig. Advertising lives in a separate SDK, so here I kept the scope to Firebase.
Next, clean up CocoaPods.
# Remove all traces of CocoaPods
pod deintegrate
rm -f Podfile Podfile.lock
rm -rf Pods/
# Go back to opening .xcodeproj directly instead of .xcworkspacepod deintegrate removes the Pods-*.xcconfig references written into Build Settings, but not completely. It is safer to check by eye whether any CocoaPods-derived values remain in OTHER_LDFLAGS or FRAMEWORK_SEARCH_PATHS. In my case I settled on leaving only $(inherited) and deleting the rest.
If you use Crashlytics, the Run Script that uploads dSYMs also changes. The old "${PODS_ROOT}/FirebaseCrashlytics/run" path no longer exists, so swap it for a script referencing where SPM expands the package. That part matched the official guidance with no surprises.
Where I lost the most time
The migration looked smooth until I hit a linker error at the very end. The build succeeded, but the app crashed on a real device with a dyld: Library not loaded style error. When I described the situation to Gemini, it offered several candidates, but none of them was the deciding factor.
The cause turned out to be a leftover from an existing setup built around use_frameworks! :linkage => :static, which did not mesh with SPM's linking approach. I only noticed this by following the Xcode build log line by line and patiently identifying which framework was being pulled in twice — not from the AI's advice.
That experience was a good reminder. AI quickly unfolds "a map of possible causes," but confirming where you actually are on that map is, in the end, your own manual work. Gemini's candidate list was genuinely useful as a starting point for investigation. But the last step only gets filled in by reading, trying, and reading again. It is obvious, but real migration work is exactly where you feel it.
How it felt after three weeks
After the migration, I ran one of my apps in production with the SPM setup for about three weeks.
The biggest perceptible change was startup from a clean build. With no Pods/ regeneration, the steps for rebuilding the environment on a new Mac dropped noticeably. Even with fifty million cumulative downloads, I operate everything alone, so lowering the cost of rebuilding the environment translates directly into a bit more daily breathing room.
That said, I would not claim SPM is superior in every respect. There were moments when I missed the plainness of the CocoaPods Podfile, especially when I wanted to pin multiple versions and control them precisely. SPM's resolution sometimes makes it hard to see "why did it pick that version," and you have to read through Package.resolved. It is not a universal correct answer; you choose it based on whether it fits the nature of your project.
The same goes for where to use Gemini 3 Pro. For "broad and shallow" tasks like building a mapping table or taking inventory of settings, it saved significant time. Conversely, for bugs entangled with my project's specific history, the AI was only a guide rail. Grasping that boundary may have been the biggest takeaway in three weeks.
If I were to do the same migration again, I would first hand the Podfile to Gemini to build the mapping table, then always inspect Build Settings by eye after pod deintegrate. If you are hesitating over the same migration right now, I would suggest starting with a single app of small blast radius rather than all of them at once. Thank you for reading to the end.