关于 developing-ios-apps
developing-ios-apps 是使用 XcodeGen 和 Swift Package Manager 开发、配置及部署 iOS 应用的技能,面向 iOS 开发者。内容涵盖项目设置、版本兼容、设备构建、相机实现,以及 Apple 签名问题的排查。
主要功能
- 01使用 XcodeGen 生成项目
- 02管理 Swift Package Manager 依赖
- 03处理 iOS API 兼容性
- 04构建并部署到设备
- 05排查相机与签名问题
适用场景
- 01配置 iOS 项目及其依赖
- 02解决框架嵌入或签名故障
- 03检查设备上的相机预览
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ daymade/ claude-code-skills --skill developing-ios-apps安装后在 Claude Code 中输入/developing-ios-apps确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
references/apple-codesign-notarize.mdcamera-avfoundation.mdswiftui-compatibility.mdtesting-mainactor.mdxcodegen-full.md详细介绍
iOS 应用开发
使用 XcodeGen 和 Swift Package Manager 构建、配置和部署 iOS 应用。
重要警告
| 问题 | 原因 | 解决方案 |
|---|---|---|
| "Library not loaded: @rpath/Framework" | XcodeGen 不会自动嵌入 SPM 动态框架 | 先在 Xcode 图形界面中构建(不要使用 xcodebuild)。请参阅故障排除 |
xcodegen generate 丢失签名配置 | 覆盖项目设置 | 在 project.yml 的 target 设置中配置,而不是在全局设置中配置 |
| 命令行签名失败 | 免费 Apple ID 的限制 | 使用 Xcode 图形界面或付费开发者账户($99/年) |
| "Cannot be set when automaticallyAdjustsVideoMirroring is YES" | 未禁用自动调整就设置了 isVideoMirrored | 先设置 automaticallyAdjustsVideoMirroring = false。请参阅相机 |
| App 尽管有证书仍以 adhoc 方式签名 | @electron/packager 默认 continueOnError: true | 在 osxSign 中设置 continueOnError: false。请参阅代码签名 |
| "Cannot use password credentials, API key credentials..." | 使用 API key 身份验证时向 @electron/notarize 传入了 teamId | 移除 teamId。notarytool 会根据 API key 推断团队。请参阅代码签名 |
| 签名期间出现 EMFILE(嵌入式运行时较大) | @electron/osx-sign 会遍历 .app 包中的所有文件 | 在 CI 中添加 ignore 过滤器和 ulimit -n 65536。请参阅代码签名 |
快速参考
| 任务 | 命令 |
|---|---|
| 生成项目 | xcodegen generate |
| 构建模拟器 | xcodebuild -destination 'platform=iOS Simulator,name=iPhone 17' build |
| 构建设备(付费账户) | xcodebuild -destination 'platform=iOS,name=DEVICE' -allowProvisioningUpdates build |
| 清理 DerivedData | rm -rf ~/Library/Developer/Xcode/DerivedData/PROJECT-* |
| 查找设备名称 | xcrun xctrace list devices |
XcodeGen 配置
最简 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
代码签名配置
个人(免费)账户:仅可在 Xcode 图形界面中使用。命令行构建需要付费账户。
# In target settings
settings:
base:
CODE_SIGN_STYLE: Automatic
DEVELOPMENT_TEAM: TEAM_ID # Get from Xcode → Settings → Accounts
获取 Team ID:
security find-identity -v -p codesigning | head -3
iOS 版本兼容性
各版本 API 变更
| 仅适用于 iOS 17+ | 兼容 iOS 16 |
|---|---|
.onChange { old, new in } | .onChange { new in } |
ContentUnavailableView | 自定义 VStack |
AVAudioApplication | AVAudioSession |
@Observable 宏 | @ObservableObject |
| SwiftData | CoreData/Realm |
降低部署目标版本
- 更新
project.yml:
deploymentTarget:
iOS: "16.0"
- 修复不兼容的 API:
// 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
部署到设备
首次设置
- 通过 USB 连接设备
- 在设备上信任此电脑
- 在 Xcode 中:Settings → Accounts → Add Apple ID
- 在 scheme 下拉菜单中选择设备
- 运行(
Cmd + R) - 在设备上:Settings → General → VPN & Device Management → Trust
命令行构建(需要付费账户)
xcodebuild \
-project App.xcodeproj \
-scheme App \
-destination 'platform=iOS,name=DeviceName' \
-allowProvisioningUpdates \
build
常见问题
| 错误 | 解决方案 |
|---|---|
| "Library not loaded: @rpath/Framework" | 未嵌入 SPM 动态框架。先在 Xcode 图形界面中构建,之后即可通过命令行工作 |
| "No Account for Team" | 在 Xcode Settings → Accounts 中添加 Apple ID |
| "Provisioning profile not found" | 免费账户限制。使用 Xcode 图形界面或获取付费账户 |
| 未列出设备 | 重新连接 USB,在设备上信任此电脑,然后重启 Xcode |
| 无法删除 DerivedData | 先关闭 Xcode:pkill -9 Xcode && rm -rf ~/Library/Developer/Xcode/DerivedData/PROJECT-* |
免费与付费开发者账户
| 功能 | 免费 Apple ID | 付费($99/年) |
|---|---|---|
| Xcode 图形界面构建 | ✅ | ✅ |
| 命令行构建 | ❌ | ✅ |
| App 有效期 | 7 天 | 1 年 |
| App Store | ❌ | ✅ |
| CI/CD | ❌ | ✅ |
SPM 依赖项
SPM 动态框架未嵌入
根本原因:XcodeGen 不会为 SPM 动态框架(例如 RealmSwift、Realm)生成“Embed Frameworks”构建阶段。应用可以成功构建,但启动时会崩溃,并显示:
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 中设置
embed: true会导致构建错误(XcodeGen 的限制)
解决方法(手动操作,每个项目只需一次):
- 在 Xcode 图形界面中打开项目
- 选择目标 → General → Frameworks, Libraries
- 找到动态框架(RealmSwift)
- 将“Do Not Embed”改为“Embed & Sign”
- 首先在 Xcode 图形界面中构建并运行
手动修复后:命令行构建(xcodebuild)即可正常工作,因为 Xcode 会将嵌入设置保存在 project.pbxproj 中。
识别动态框架:
# Check if a framework is dynamic
file ~/Library/Developer/Xcode/DerivedData/PROJECT-*/Build/Products/Debug-iphoneos/FRAMEWORK.framework/FRAMEWORK
# Dynamic: "Mach-O 64-bit dynamically linked shared library"
# Static: "current ar archive"
添加软件包
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 multiple
解析依赖项(中国代理)
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.swiftpm)。重新下载很慢。
Camera / AVFoundation
相机预览需要使用真实设备(模拟器没有相机)。
快速调试清单
- 权限:是否已将
NSCameraUsageDescription添加到 Info.plist? - 设备:是否在真实设备上运行,而非模拟器?
- 会话运行:是否在后台线程上调用了
session.startRunning()? - 视图大小:UIViewRepresentable 是否具有非零边界?
- 视频镜像:是否在设置
isVideoMirrored前禁用了automaticallyAdjustsVideoMirroring?
视频镜像(前置摄像头)
重要:设置手动镜像前,必须先禁用自动调整:
// WRONG - crashes with "Cannot be set when automaticallyAdjustsVideoMirroring is YES"
connection.isVideoMirrored = true
// CORRECT - disable automatic first
connection.automaticallyAdjustsVideoMirroring = false
connection.isVideoMirrored = true
UIViewRepresentable 尺寸问题
ZStack 中的 UIViewRepresentable 可能具有零边界。可通过设置显式 frame 修复:
// 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()
}
调试日志模式
添加日志以跟踪相机流程:
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))")
在 Console.app 中按子系统筛选。
相机实现详情:请参阅 references/camera-avfoundation.md
macOS 代码签名与公证
要在 App Store 之外分发 macOS 应用(Electron 或原生应用),必须进行签名和公证。否则,用户会看到“Apple 无法检查此 App 是否包含恶意软件。”
5 步检查清单:
| 步骤 | 操作 | 关键细节 |
|---|---|---|
| 1 | 在 Keychain Access 中创建 CSR | 通用名称无关紧要;选择“存储到磁盘” |
| 2 | 在 developer.apple.com 申请 Developer ID Application 证书 | 选择 G2 Sub-CA(而非 Previous Sub-CA) |
| 3 | 安装 .cer → 必须选择 login 钥匙串 | iCloud/System → 错误 -25294(私钥不匹配) |
| 4 | 从 login 钥匙串导出带密码的 P12 | Base64:base64 -i cert.p12 | pbcopy |
| 5 | 创建 App Store Connect API Key(Developer 角色) | .p8 只能下载一次;记录 Key ID 和 Issuer ID |
所需的 GitHub Secrets(5 个):
| Secret | 来源 |
|---|---|
MACOS_CERT_P12 | 步骤 4 的 base64 |
MACOS_CERT_PASSWORD | 步骤 4 的密码 |
APPLE_API_KEY | 步骤 5 的 .p8 base64 |
APPLE_API_KEY_ID | 步骤 5 的 Key ID |
APPLE_API_ISSUER | 步骤 5 的 Issuer ID |
不需要
APPLE_TEAM_ID。notarytool会根据 API key 推断团队。向@electron/notarizev2.5.0 传递teamId会导致凭据冲突错误。
Electron Forge osxSign 关键设置:
osxSign: {
identity: 'Developer ID Application',
hardenedRuntime: true,
entitlements: 'entitlements.mac.plist',
entitlementsInherit: 'entitlements.mac.plist',
continueOnError: false, // CRITICAL: default is true, silently falls back to adhoc
// Skip non-binary files in large embedded runtimes (prevents EMFILE)
ignore: (filePath: string) => {
if (!filePath.includes('python-runtime')) return false;
if (/\.(so|dylib|node)$/.test(filePath)) return false;
return true;
},
// CI: explicitly specify keychain (apple-actions/import-codesign-certs uses signing_temp.keychain)
...(process.env.MACOS_SIGNING_KEYCHAIN
? { keychain: process.env.MACOS_SIGNING_KEYCHAIN }
: {}),
},
快速失败的三层防护:
@electron/osx-sign:continueOnError: false— 签名错误会立即抛出postPackagehook:codesign --verify --deep --strict+ adhoc 检测- 发布触发脚本:触发前验证本地 HEAD 是否与远程一致
验证签名:
security find-identity -v -p codesigning | grep "Developer ID Application"
完整的分步指南、entitlements、工作流示例以及详细故障排查(7 个真实错误及其根本原因):references/apple-codesign-notarize.md
资源
- references/xcodegen-full.md - 完整的 project.yml 选项
- references/swiftui-compatibility.md - iOS 版本 API 差异
- references/camera-avfoundation.md - 相机预览故障排查
- references/testing-mainactor.md - 测试 @MainActor 类(状态机、回归测试)
- references/apple-codesign-notarize.md - Apple Developer 面向 macOS/Electron CI/CD 的签名与公证