mcpField
D
developing-ios-apps

developing-ios-apps

github.com/daymade开发1.5k1.5k 次安装今天更新
代码仓库

关于 developing-ios-apps

developing-ios-apps 是使用 XcodeGen 和 Swift Package Manager 开发、配置及部署 iOS 应用的技能,面向 iOS 开发者。内容涵盖项目设置、版本兼容、设备构建、相机实现,以及 Apple 签名问题的排查。

主要功能

  1. 01使用 XcodeGen 生成项目
  2. 02管理 Swift Package Manager 依赖
  3. 03处理 iOS API 兼容性
  4. 04构建并部署到设备
  5. 05排查相机与签名问题

适用场景

  1. 01配置 iOS 项目及其依赖
  2. 02解决框架嵌入或签名故障
  3. 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.md
camera-avfoundation.md
swiftui-compatibility.md
testing-mainactor.md
xcodegen-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
清理 DerivedDatarm -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
AVAudioApplicationAVAudioSession
@Observable 宏@ObservableObject
SwiftDataCoreData/Realm
降低部署目标版本
  1. 更新 project.yml:
deploymentTarget:
  iOS: "16.0"
  1. 修复不兼容的 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
  1. 重新生成:xcodegen generate

部署到设备

首次设置
  1. 通过 USB 连接设备
  2. 在设备上信任此电脑
  3. 在 Xcode 中:Settings → Accounts → Add Apple ID
  4. 在 scheme 下拉菜单中选择设备
  5. 运行(Cmd + R)
  6. 在设备上: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 的限制)

解决方法(手动操作,每个项目只需一次):

  1. 在 Xcode 图形界面中打开项目
  2. 选择目标 → General → Frameworks, Libraries
  3. 找到动态框架(RealmSwift)
  4. 将“Do Not Embed”改为“Embed & Sign”
  5. 首先在 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

相机预览需要使用真实设备(模拟器没有相机)。

快速调试清单
  1. 权限:是否已将 NSCameraUsageDescription 添加到 Info.plist?
  2. 设备:是否在真实设备上运行,而非模拟器?
  3. 会话运行:是否在后台线程上调用了 session.startRunning()?
  4. 视图大小:UIViewRepresentable 是否具有非零边界?
  5. 视频镜像:是否在设置 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 钥匙串导出带密码的 P12Base64: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/notarize v2.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 }
    : {}),
},

快速失败的三层防护:

  1. @electron/osx-sign:continueOnError: false — 签名错误会立即抛出
  2. postPackage hook:codesign --verify --deep --strict + adhoc 检测
  3. 发布触发脚本:触发前验证本地 HEAD 是否与远程一致

验证签名:

security find-identity -v -p codesigning | grep "Developer ID Application"

完整的分步指南、entitlements、工作流示例以及详细故障排查(7 个真实错误及其根本原因):references/apple-codesign-notarize.md


资源

评论

支持 Markdown