Flutter: The Ultimate Guide to Fixing iOS Build Errors
1. "It Worked Yesterday, Failed Today"
This is the classic Flutter developer nightmare. You changed zero lines of code. You slept, woke up, and now the build fails.
ld: symbol(s) not found for architecture arm64
Command PhaseScriptExecution failed with a nonzero exit code
The logs look cryptic. The StackOverflow answers are from 2018. Don't panic. It's not your logic. It's the fragile marriage between Xcode and CocoaPods.
2. Understanding the Pipeline
A Flutter iOS build isn't a single step. It's a pipeline:
- Flutter Build: Compiles Dart to Native (
App.framework). - CocoaPods Install: Downloads and links native dependencies defined in
pubspec.yaml(Firebase, Maps, etc.). - Xcode Build: Combines your Runner + Pods + Flutter Engine, Signs them, and packages the
.app.
Most errors happen at Step 2 (Dependency Resolution) or Step 3 (Linking/Signing). Stale caches, mismatched architectures, or valid certificates expiring are the usual suspects.
3. Solution 1: The "Deep Clean" Ritual
Before debugging, perform the ritual. This fixes 80% of "Phantom Errors" caused by bad caching.
# 1. Start at Flutter root
flutter clean
# 2. Go to iOS
cd ios
# 3. Nuke Pods (Crucial)
rm -rf Pods
rm Podfile.lock
# 4. Reinstall with Repo Update
# Note: On Apple Silicon, you might need 'arch -x86_64 pod install' if using Rosetta
pod install --repo-update
# 5. Build again
cd ..
flutter pub get
flutter run
If this works, grab a coffee. If not, read on.
4. Solution 2: Apple Silicon (M1/M2/M3) Arch Issues
If you see Excluded Architectures or symbol not found for architecture arm64, you have an Architecture Mismatch.
Your Mac is arm64, your Simulator tries to run arm64, but one of your old libraries only provides x86_64 (Intel) binaries.
The Fix: Force Rosetta on Simulator
Modify your ios/Podfile inside the post_install block:
post_install do |installer|
installer.pods_project.targets.each do |target|
flutter_additional_ios_build_settings(target)
# Add this loop
target.build_configurations.each do |config|
# Force Simulator builds to exclude arm64, triggering Rosetta translation
config.build_settings['EXCLUDED_ARCHS[sdk=iphonesimulator*]'] = "arm64"
end
end
end
5. Solution 3: Code Signing & Certificates
Code Signing Error, Provisioning Profile doesn't match.
These mean Apple can't verify who you are.
Do NOT try to fix this in Terminal. Open Xcode.
- Double click
ios/Runner.xcworkspace. - Select
Runner(Project root) on the left. - Go to
Signing & Capabilities. - Team: If it's red, re-login to your Apple Developer Account.
- Bundle Identifier: Must be globally unique. If you used
com.example, change it to something real likeio.github.myname.testapp.
6. Solution 4: Ruby Version Hell
pod install crashes with ffi, ethon, or ThreadError?
MacOS comes with a System Ruby. CocoaPods might be installed on a different Ruby version. The mix-up causes crashes.
The Fix: Use rbenv
Never use system ruby (sudo gem install). Use a version manager.
# 1. Install rbenv
brew install rbenv ruby-build
# 2. Install a stable Ruby (e.g., 3.2.2)
rbenv install 3.2.2
rbenv global 3.2.2
# 3. Reinstall CocoaPods on THIS ruby
gem install cocoapods
# 4. Verify path
which pod
# Should be: /Users/you/.rbenv/shims/pod
7. Deep Dive: CI/CD Integration (GitHub Actions & Fastlane)
It works on my machine, but fails on GitHub Actions? This is usually due to Signing or Path issues.
1. Fastlane Match (The Silver Bullet)
Managing certificates manually is a nightmare. Especially when working in a team or with a CI server. Fastlane Match is a tool that syncs your certificates and profiles via a private Git repository.
In your CI, you just run:
fastlane match appstore --readonly
This downloads and installs the certs into the CI runner's keychain automatically. It prevents the classic "It works on Bob's machine but not on Alice's" problem because everyone shares the exact same encrypted certificates.
2. Caching Pods
CocoaPods installation takes forever (5-10 mins). Cache it!
In GitHub Actions main.yml:
- name: Cache CocoaPods
uses: actions/cache@v3
with:
path: ios/Pods
key: ${{ runner.os }}-pods-${{ hashFiles('ios/Podfile.lock') }}
restore-keys: |
${{ runner.os }}-pods-
3. FLUTTER_ROOT Issue
Sometimes script phases fail because they can't find Flutter. Ensure your workflow exports the path:
export FLUTTER_ROOT=$HOME/flutter
8. Deep Dive: Automatic Signing vs Manual Signing
Automatic Signing (Personal Team)
For solo developers, "Automatically manage signing" is great. Xcode creates a provisioning profile for you. Downside: It expires every 7 days if you don't pay $99/year. You have to rebuild and reinstall the app constantly.
Manual Signing (Enterprise / Team)
For serious production apps, turn off Automatic Signing.
Why? Because Automatic Signing generates profiles with wildcards (*) or development-only entitlements.
To publish to the App Store, you need a Distribution Profile.
- Go to Apple Developer Portal -> Profiles -> New.
- Select "App Store".
- Select your App ID and Distribution Certificate.
- Download and double-click to install.
- In Xcode, select this specific profile.
This ensures that the capability you tested (e.g., Push Notifications) is definitely enabled in the production build.
9. Advanced: DerivedData & Flaky Tests
Even after cleaning pods, sometimes Xcode clings to broken intermediates. This is called "DerivedData Hell".
The Manual Nuke
If Product -> Clean Build Folder (Cmd+Shift+K) doesn't fix it, go nuclear:
rm -rf ~/Library/Developer/Xcode/DerivedData
This folder contains the pre-compiled headers and index files. Use this command when Xcode starts showing errors for code that doesn't exist anymore.
CI Flakiness
On CI, xcodebuild might hang or fail randomly. Always pass -quiet to reduce log noise, but use xcpretty to format the output if you need debugging. Use timeout-minutes in GitHub Actions to prevent a stuck build from eating your minutes.
10. Deep Dive Glossary
1. Podfile vs Podfile.lock
- Podfile: Your wish list. "I want Firebase ~> 9.0".
- Podfile.lock: The receipt. "I installed Firebase 9.1.2".
- Rule: Always commit
Podfile.lockto Git. It ensures your CI server and your teammates use the EXACT same versions.
2. Workspace (.xcworkspace)
When using CocoaPods, Xcode creates a Workspace to manage two projects: Runner (Yours) and Pods (Dependencies).
If you open .xcodeproj, Xcode ignores the Pods, and your build fails with "Module not found". Always open the Workspace.
3. Bitcode
An intermediate compilation step allowing Apple to re-optimize binaries.
Status: Deprecated. Since Xcode 14, Bitcode is no longer required. If validation fails on upload, ensure Enable Bitcode is set to No in Build Settings.
4. Derived Data
A massive cache folder where Xcode stores intermediate build files. Sometimes this gets corrupted.
Fix: rm -rf ~/Library/Developer/Xcode/DerivedData. It's the Xcode equivalent of "Did you try turning it off and on again?"
11. Summary
- Ritual:
flutter clean+pod installsaves lives. - Silicon: Check
Excluded Architecturesif you are on M1/M2. - Gui: Fix Signing issues in Xcode, not VS Code.
- Ruby: Use
rbenvto avoid system permission issues.
CocoaPods is messy, but predictable once you understand the pipeline.