developing-ios-apps
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseiOS App Development
iOS应用开发
Build, configure, and deploy iOS applications using XcodeGen and Swift Package Manager.
使用XcodeGen和Swift Package Manager构建、配置和部署iOS应用。
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 |
| Overwrites project settings | Configure in |
| 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 | Set |
| 问题 | 原因 | 解决方案 |
|---|---|---|
| "Library not loaded: @rpath/Framework" | XcodeGen不会自动嵌入SPM动态框架 | 先在Xcode图形界面中构建(不要使用xcodebuild)。查看故障排除 |
| 覆盖了项目设置 | 在 |
| 命令行签名失败 | 免费Apple ID限制 | 使用Xcode图形界面或付费开发者账号(99美元/年) |
| "Cannot be set when automaticallyAdjustsVideoMirroring is YES" | 未禁用自动调整就设置 | 先设置 |
Quick Reference
快速参考
| Task | Command |
|---|---|
| Generate project | |
| Build simulator | |
| Build device (paid account) | |
| Clean DerivedData | |
| Find device name | |
| 任务 | 命令 |
|---|---|
| 生成项目 | |
| 构建模拟器版本 | |
| 构建设备版本(需付费账号) | |
| 清理DerivedData | |
| 查找设备名称 | |
XcodeGen Configuration
XcodeGen配置
Minimal project.yml
最简project.yml
yaml
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: SomePackageyaml
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: SomePackageCode Signing Configuration
代码签名配置
Personal (free) account: Works in Xcode GUI only. Command-line builds require paid account.
yaml
undefined个人(免费)账号:仅在Xcode图形界面中可用。命令行构建需要付费账号。
yaml
undefinedIn target settings
在target设置中
settings:
base:
CODE_SIGN_STYLE: Automatic
DEVELOPMENT_TEAM: TEAM_ID # Get from Xcode → Settings → Accounts
**Get Team ID**:
```bash
security find-identity -v -p codesigning | head -3settings:
base:
CODE_SIGN_STYLE: Automatic
DEVELOPMENT_TEAM: TEAM_ID # 从Xcode → 设置 → 账号中获取
**获取Team ID**:
```bash
security find-identity -v -p codesigning | head -3iOS Version Compatibility
iOS版本兼容性
API Changes by Version
各版本API变化
| iOS 17+ Only | iOS 16 Compatible |
|---|---|
| |
| Custom VStack |
| |
| |
| SwiftData | CoreData/Realm |
| 仅iOS 17+支持 | 兼容iOS 16 |
|---|---|
| |
| 自定义VStack |
| |
| |
| SwiftData | CoreData/Realm |
Lowering Deployment Target
降低部署目标版本
- Update :
project.yml
yaml
deploymentTarget:
iOS: "16.0"- Fix incompatible APIs:
swift
// iOS 17
.onChange(of: value) { oldValue, newValue in }
// iOS 16
.onChange(of: value) { newValue in }
// iOS 17
ContentUnavailableView("Title", systemImage: "icon")
// iOS 16
VStack {
Image(systemName: "icon").font(.system(size: 48))
Text("Title").font(.title2.bold())
}
// iOS 17
AVAudioApplication.shared.recordPermission
// iOS 16
AVAudioSession.sharedInstance().recordPermission- Regenerate:
xcodegen generate
- 更新:
project.yml
yaml
deploymentTarget:
iOS: "16.0"- 修复不兼容的API:
swift
// iOS 17
.onChange(of: value) { oldValue, newValue in }
// iOS 16
.onChange(of: value) { newValue in }
// iOS 17
ContentUnavailableView("Title", systemImage: "icon")
// iOS 16
VStack {
Image(systemName: "icon").font(.system(size: 48))
Text("Title").font(.title2.bold())
}
// iOS 17
AVAudioApplication.shared.recordPermission
// iOS 16
AVAudioSession.sharedInstance().recordPermission- 重新生成项目:
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
- 通过USB连接设备
- 在设备上信任此电脑
- 在Xcode中:设置 → 账号 → 添加Apple ID
- 在方案下拉菜单中选择设备
- 运行()
Cmd + R - 在设备上:设置 → 通用 → VPN与设备管理 → 信任
Command-line Build (requires paid account)
命令行构建(需付费账号)
bash
xcodebuild \
-project App.xcodeproj \
-scheme App \
-destination 'platform=iOS,name=DeviceName' \
-allowProvisioningUpdates \
buildbash
xcodebuild \
-project App.xcodeproj \
-scheme App \
-destination 'platform=iOS,name=DeviceName' \
-allowProvisioningUpdates \
buildCommon 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: |
| 错误 | 解决方案 |
|---|---|
| "Library not loaded: @rpath/Framework" | SPM动态框架未嵌入。先在Xcode图形界面中构建,之后命令行构建即可正常工作 |
| "No Account for Team" | 在Xcode设置 → 账号中添加Apple ID |
| "Provisioning profile not found" | 免费账号限制。使用Xcode图形界面或购买付费账号 |
| 设备未列出 | 重新连接USB,在设备上信任电脑,重启Xcode |
| DerivedData无法删除 | 先关闭Xcode: |
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 | ❌ | ✅ |
| 功能 | 免费Apple ID | 付费(99美元/年) |
|---|---|---|
| Xcode图形界面构建 | ✅ | ✅ |
| 命令行构建 | ❌ | ✅ |
| 应用有效期 | 7天 | 1年 |
| App Store发布 | ❌ | ✅ |
| CI/CD支持 | ❌ | ✅ |
SPM Dependencies
SPM依赖
SPM Dynamic Framework Not Embedded
SPM动态框架未嵌入
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 foundWhy 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
- in project.yml causes build errors (XcodeGen limitation)
embed: true
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 () will work because Xcode persists the embed setting in project.pbxproj.
xcodebuildIdentifying Dynamic Frameworks:
bash
undefined根本原因:XcodeGen不会为SPM动态框架(如RealmSwift、Realm)生成「嵌入框架」构建阶段。应用构建成功但启动时崩溃,报错:
dyld: Library not loaded: @rpath/RealmSwift.framework/RealmSwift
Referenced from: /var/containers/Bundle/Application/.../App.app/App
Reason: image not found问题原因:
- 静态框架(大多数SPM包)会链接到二进制文件中 - 无需嵌入
- 动态框架(RealmSwift等)必须复制到应用包中
- XcodeGen会生成链接阶段,但不会为SPM包生成嵌入阶段
- 在project.yml中设置会导致构建错误(XcodeGen限制)
embed: true
修复方案(手动操作,每个项目仅需一次):
- 在Xcode图形界面中打开项目
- 选择target → 通用 → 框架、库和嵌入式内容
- 找到动态框架(RealmSwift)
- 将「不嵌入」改为「嵌入并签名」
- 先在Xcode图形界面中构建并运行
手动修复后:命令行构建()将可以正常工作,因为Xcode会将嵌入设置保留在project.pbxproj中。
xcodebuild识别动态框架:
bash
undefinedCheck if a framework is dynamic
检查框架是否为动态
file ~/Library/Developer/Xcode/DerivedData/PROJECT-*/Build/Products/Debug-iphoneos/FRAMEWORK.framework/FRAMEWORK
file ~/Library/Developer/Xcode/DerivedData/PROJECT-*/Build/Products/Debug-iphoneos/FRAMEWORK.framework/FRAMEWORK
Dynamic: "Mach-O 64-bit dynamically linked shared library"
动态:"Mach-O 64-bit dynamically linked shared library"
Static: "current ar archive"
静态:"current ar archive"
undefinedundefinedAdding Packages
添加包
yaml
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 # Explicit product name when package has multipleyaml
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)
解决依赖问题(中国地区代理)
bash
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 -resolvePackageDependenciesNever clear global SPM cache (). Re-downloading is slow.
~/Library/Caches/org.swift.swiftpmbash
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不要清理全局SPM缓存()。重新下载速度很慢。
~/Library/Caches/org.swift.swiftpmCamera / AVFoundation
相机 / AVFoundation
Camera preview requires real device (simulator has no camera).
相机预览需要真实设备(模拟器没有相机)。
Quick Debugging Checklist
快速调试清单
- Permission: Added to Info.plist?
NSCameraUsageDescription - Device: Running on real device, not simulator?
- Session running: called on background thread?
session.startRunning() - View size: UIViewRepresentable has non-zero bounds?
- Video mirroring: Disabled before setting
automaticallyAdjustsVideoMirroring?isVideoMirrored
- 权限:是否已在Info.plist中添加?
NSCameraUsageDescription - 设备:是否在真实设备上运行,而非模拟器?
- 会话运行:是否在后台线程调用?
session.startRunning() - 视图尺寸:UIViewRepresentable是否有非零边界?
- 视频镜像:在设置之前是否已禁用
isVideoMirrored?automaticallyAdjustsVideoMirroring
Video Mirroring (Front Camera)
视频镜像(前置摄像头)
CRITICAL: Must disable automatic adjustment before setting manual mirroring:
swift
// WRONG - crashes with "Cannot be set when automaticallyAdjustsVideoMirroring is YES"
connection.isVideoMirrored = true
// CORRECT - disable automatic first
connection.automaticallyAdjustsVideoMirroring = false
connection.isVideoMirrored = true重要提示:必须先禁用自动调整,再设置手动镜像:
swift
// 错误 - 会崩溃并提示"Cannot be set when automaticallyAdjustsVideoMirroring is YES"
connection.isVideoMirrored = true
// 正确 - 先禁用自动调整
connection.automaticallyAdjustsVideoMirroring = false
connection.isVideoMirrored = trueUIViewRepresentable Sizing Issue
UIViewRepresentable尺寸问题
UIViewRepresentable in ZStack may have zero bounds. Fix with explicit frame:
swift
// BAD: UIViewRepresentable may get zero size in ZStack
ZStack {
CameraPreviewView(session: session) // May be invisible!
OtherContent()
}
// GOOD: Explicit sizing
ZStack {
GeometryReader { geo in
CameraPreviewView(session: session)
.frame(width: geo.size.width, height: geo.size.height)
}
.ignoresSafeArea()
OtherContent()
}ZStack中的UIViewRepresentable可能会有零边界。使用显式框架修复:
swift
// 错误:UIViewRepresentable在ZStack中可能会获得零尺寸
ZStack {
CameraPreviewView(session: session) // 可能不可见!
OtherContent()
}
// 正确:显式设置尺寸
ZStack {
GeometryReader { geo in
CameraPreviewView(session: session)
.frame(width: geo.size.width, height: geo.size.height)
}
.ignoresSafeArea()
OtherContent()
}Debug Logging Pattern
调试日志模式
Add logging to trace camera flow:
swift
import os
private let logger = Logger(subsystem: "com.app", category: "Camera")
func start() async {
logger.info("start() called, isRunning=\(self.isRunning)")
// ... setup code ...
logger.info("session.startRunning() completed")
}
// For CGRect (doesn't conform to CustomStringConvertible)
logger.info("bounds=\(NSCoder.string(for: self.bounds))")Filter in Console.app by subsystem.
For detailed camera implementation: See references/camera-avfoundation.md
添加日志以跟踪相机流程:
swift
import os
private let logger = Logger(subsystem: "com.app", category: "Camera")
func start() async {
logger.info("start() called, isRunning=\(self.isRunning)")
// ... 配置代码 ...
logger.info("session.startRunning() completed")
}
// 针对CGRect(未遵循CustomStringConvertible)
logger.info("bounds=\(NSCoder.string(for: self.bounds))")在Console.app中按子系统过滤日志。
详细相机实现:查看references/camera-avfoundation.md
Resources
资源
- references/xcodegen-full.md - Complete project.yml options
- references/swiftui-compatibility.md - iOS version API differences
- references/camera-avfoundation.md - Camera preview debugging
- references/testing-mainactor.md - Testing @MainActor classes (state machines, regression tests)
- references/xcodegen-full.md - 完整的project.yml选项
- references/swiftui-compatibility.md - iOS版本API差异
- references/camera-avfoundation.md - 相机预览调试
- references/testing-mainactor.md - 测试@MainActor类(状态机、回归测试)