iOS App Development
Build, configure, and deploy iOS applications using XcodeGen and Swift Package Manager.
Critical Warnings
| Issue |
Cause |
Solution |
| "Library not loaded: @rpath/Framework" |
XcodeGen doesn't auto-embed SPM dynamic frameworks |
Build in Xcode GUI first (not xcodebuild). See Troubleshooting |
xcodegen generate loses signing |
Overwrites project settings |
Configure in project.yml target settings, not global |
| Command-line signing fails |
Free Apple ID limitation |
Use Xcode GUI or paid developer account ($99/yr) |
| "Cannot be set when automaticallyAdjustsVideoMirroring is YES" |
Setting isVideoMirrored without disabling automatic |
Set automaticallyAdjustsVideoMirroring = false first. See Camera |
| App signed as adhoc despite certificate |
@electron/packager defaults continueOnError: true |
Set continueOnError: false in osxSign. See Code Signing |
| "Cannot use password credentials, API key credentials..." |
Passing teamId to @electron/notarize with API key auth |
Remove teamId. notarytool infers team from API key. See Code Signing |
| EMFILE during signing (large embedded runtime) |
@electron/osx-sign traverses all files in .app bundle |
Add ignore filter + ulimit -n 65536 in CI. See Code Signing |
Quick Reference
| Task |
Command |
| Generate project |
xcodegen generate |
| Build simulator |
xcodebuild -destination 'platform=iOS Simulator,name=iPhone 17' build |
| Build device (paid account) |
xcodebuild -destination 'platform=iOS,name=DEVICE' -allowProvisioningUpdates build |
| Clean DerivedData |
rm -rf ~/Library/Developer/Xcode/DerivedData/PROJECT-* |
| Find device name |
xcrun xctrace list devices |
XcodeGen Configuration
Minimal project.yml
name: AppName
options:
bundleIdPrefix: com.company
deploymentTarget:
iOS: "16.0"
settings:
base:
SWIFT_VERSION: "6.0"
packages:
SomePackage:
url: https://github.com/org/repo
from: "1.0.0"
targets:
AppName:
type: application
platform: iOS
sources:
- path: AppName
settings:
base:
INFOPLIST_FILE: AppName/Info.plist
PRODUCT_BUNDLE_IDENTIFIER: com.company.appname
CODE_SIGN_STYLE: Automatic
DEVELOPMENT_TEAM: TEAM_ID_HERE
dependencies:
- package: SomePackage
Code Signing Configuration
Personal (free) account: Works in Xcode GUI only. Command-line builds require paid account.
settings:
base:
CODE_SIGN_STYLE: Automatic
DEVELOPMENT_TEAM: TEAM_ID
Get Team ID:
security find-identity -v -p codesigning | head -3
iOS Version Compatibility
API Changes by Version
| iOS 17+ Only |
iOS 16 Compatible |
.onChange { old, new in } |
.onChange { new in } |
ContentUnavailableView |
Custom VStack |
AVAudioApplication |
AVAudioSession |
@Observable macro |
@ObservableObject |
| SwiftData |
CoreData/Realm |
Lowering Deployment Target
- Update
project.yml:
deploymentTarget:
iOS: "16.0"
- Fix incompatible APIs:
.onChange(of: value) { oldValue, newValue in }
.onChange(of: value) { newValue in }
ContentUnavailableView("Title", systemImage: "icon")
VStack {
Image(systemName: "icon").font(.system(size: 48))
Text("Title").font(.title2.bold())
}
AVAudioApplication.shared.recordPermission
AVAudioSession.sharedInstance().recordPermission
- Regenerate:
xcodegen generate
Device Deployment
First-time Setup
- Connect device via USB
- Trust computer on device
- In Xcode: Settings β Accounts β Add Apple ID
- Select device in scheme dropdown
- Run (
Cmd + R)
- On device: Settings β General β VPN & Device Management β Trust
Command-line Build (requires paid account)
xcodebuild \
-project App.xcodeproj \
-scheme App \
-destination 'platform=iOS,name=DeviceName' \
-allowProvisioningUpdates \
build
Common Issues
| Error |
Solution |
| "Library not loaded: @rpath/Framework" |
SPM dynamic framework not embedded. Build in Xcode GUI first, then CLI works |
| "No Account for Team" |
Add Apple ID in Xcode Settings β Accounts |
| "Provisioning profile not found" |
Free account limitation. Use Xcode GUI or get paid account |
| Device not listed |
Reconnect USB, trust computer on device, restart Xcode |
| DerivedData won't delete |
Close Xcode first: pkill -9 Xcode && rm -rf ~/Library/Developer/Xcode/DerivedData/PROJECT-* |
Free vs Paid Developer Account
| Feature |
Free Apple ID |
Paid ($99/year) |
| Xcode GUI builds |
β
|
β
|
| Command-line builds |
β |
β
|
| App validity |
7 days |
1 year |
| App Store |
β |
β
|
| CI/CD |
β |
β
|
SPM Dependencies
SPM Dynamic Framework Not Embedded
Root Cause: XcodeGen doesn't generate the "Embed Frameworks" build phase for SPM dynamic frameworks (like RealmSwift, Realm). The app builds successfully but crashes on launch with:
dyld: Library not loaded: @rpath/RealmSwift.framework/RealmSwift
Referenced from: /var/containers/Bundle/Application/.../App.app/App
Reason: image not found
Why This Happens:
- Static frameworks (most SPM packages) are linked into the binary - no embedding needed
- Dynamic frameworks (RealmSwift, etc.) must be copied into the app bundle
- XcodeGen generates link phase but NOT embed phase for SPM packages
embed: true in project.yml causes build errors (XcodeGen limitation)
The Fix (Manual, one-time per project):
- Open project in Xcode GUI
- Select target β General β Frameworks, Libraries
- Find the dynamic framework (RealmSwift)
- Change "Do Not Embed" β "Embed & Sign"
- Build and run from Xcode GUI first
After Manual Fix: Command-line builds (xcodebuild) will work because Xcode persists the embed setting in project.pbxproj.
Identifying Dynamic Frameworks:
file ~/Library/Developer/Xcode/DerivedData/PROJECT-*/Build/Products/Debug-iphoneos/FRAMEWORK.framework/FRAMEWORK
Adding Packages
packages:
AudioKit:
url: https://github.com/AudioKit/AudioKit
from: "5.6.5"
RealmSwift:
url: https://github.com/realm/realm-swift
from: "10.54.6"
targets:
App:
dependencies:
- package: AudioKit
- package: RealmSwift
product: RealmSwift
Resolving Dependencies (China proxy)
git config --global http.proxy http://127.0.0.1:1082
git config --global https.proxy http://127.0.0.1:1082
xcodebuild -scmProvider system -resolvePackageDependencies
Never clear global SPM cache (~/Library/Caches/org.swift.swiftpm). Re-downloading is slow.
Camera / AVFoundation
Camera preview requires real device (simulator has no camera).
Quick Debugging Checklist
- Permission: Added
NSCameraUsageDescription to Info.plist?
- Device: Running on real device, not simulator?
- Session running:
session.startRunning() called on background thread?
- View size: UIViewRepresentable has non-zero bounds?
- Video mirroring: Disabled
automaticallyAdjustsVideoMirroring before setting isVideoMirrored?
Video Mirroring (Front Camera)
CRITICAL: Must disable automatic adjustment before setting manual mirroring:
connection.isVideoMirrored = true
connection.automaticallyAdjustsVideoMirroring = false
connection.isVideoMirrored = true
UIViewRepresentable Sizing Issue
UIViewRepresentable in ZStack may have zero bounds. Fix with explicit frame:
ZStack {
CameraPreviewView(session: session)
OtherContent()
}
ZStack {
GeometryReader { geo in
CameraPreviewView(session: session)
.frame(