Skip to content

构建一个 iOS 应用并上传到 TestFlight ​

目标 ​

  1. 提交一个commit到Github仓库的main分支后.
    bash
    git push
  2. 观察到Xcode Cloud自动开始构建应用, 并将构建结果发布到TestFlight上.

前提条件 ​

  1. 一个苹果开发者帐号. 指你的Apple ID已加入苹果开发者计划
  2. 一个Github账户.
  3. 一台未在其他苹果开发者帐号的苹果设备(iPhone, iPad, M芯片的Mac).

新建 Xcode 项目 ​

新建 Bundle ID ​

每一个苹果应用都有一个ID, 苹果称之为Bundle ID. 这个ID可以在苹果开发者网站中申请, 具体的网址为 Apple Developer > Account > Certificates, IDs & Profiles > Identifiers. picture 0

小技巧

在 Xcode 上也可以创建 Bundle ID, 但是有一点概率失败. 此时可以打开 Apple Developer 网站手动创建. 依据: issue#16

新建 App Store Connect App ​

想要在App Store发布应用, 就必须用到 App Store Connect 网站. 之后像是什么更新应用, 添加TestFlight测试者, 查看Xcode Cloud日志, 全部都是在这个网站上操作的.

  1. 在 Apps 页面点击 New App.
  2. 填写 New App 表格. 你可能会问 SKU 怎么写, 其实可以随便写, 我一般直接填入Bundle ID. picture 1

获取证书及描述文件 ​

  1. 打开Xcode, 创建一个新的项目.
  2. 修改Bundle ID. 就用在 Apple Developer 网站上新建的Bundle ID. 修改位置在 左侧导航栏 > app.xcodeproj > Signing & Capabilities > Signing > Bundle Identifier. picture 2
  3. 获取证书及描述文件. 修改Bundle ID后, Xcode会自动获取Profile. 点击 Provisioning Profile 右边的i图标, 如果全部是打勾, 则说明获取成功. picture 3
  4. 在物理设备(iPhone, M芯片Mac)上运行该项目, 保证该项目可以正常运行. 注意, 不能是 iOS 模拟器.

注意

你需要一台全新的苹果设备来激活苹果开发者帐号. 原因是想要在你的 iPhone 上运行 Xcode 编译出的应用, 需要描述文件, 想要描述文件, 就需要在 Apple Developer 上注册设备. 尽管在 Xcode 上修改 Bundle ID 并按下回车键后 Xcode 会自动获取证书, 注册设备, 获取描述文件, 但是已经被注册到其他苹果开发者帐号的 iPhone 注册到新的苹果开发者帐号会报错. 依据: issue#14

手动构建并上传 ​

注意

在设置 Xcode Cloud 自动构建之前, 请务必先完成一次手动上传. 因为这能保证您在设置 Xcode Cloud 自动构建的时候遇到的问题全部都是 Xcode Cloud 造成的, 而不是由于源码或者 TestFlight 导致的.

设置应用图标 ​

  • 必须有应用图标. 没有应用图标的应用上传到TestFlight会失败.
  • 应用图标不得有透明像素. 因为应用图标如有alpha通道, 将导致Xcode Cloud构建. 您可以通过将应用图标转换为jpeg格式来解决这个问题, 因为jpeg格式的图片没有alpha通道.

设置加密方式 ​

设置 Info.plist 中 ITSAppUsesNonExemptEncryption 键的值.

  • 这个键的值反应了当前应用的加密方式, 如果您不知道那是什么, 可以将其设置为 false.
  • 这一操作是必须的, 因为如果没有在 Info.plist 中设置该键值, 每次发布 TestFlight 前, 您将需要在 App Store Connect 中手动设置应用的加密方式.
xml
<dict>
   ...
   <key>ITSAppUsesNonExemptEncryption</key>
   <false/>
</dict>

构建, 上传 ​

  1. 设置 Run Destination 为 Any iOS Device
  2. 点击 顶部菜单栏 > Product > Archive 以开始构建
  3. 在构建完成后一个名为 Archives 的窗口会自动弹出(该窗口亦可通过 顶部菜单栏 > Window > Organizer 打开). 选择刚刚生成的 Archive, 然后点击 Distribute App. 如此, 应用便开始上传到TestFlight. picture 0

创建 TestFlight 测试组 ​

  1. 当Archive上传完成后, 你就能在 App Store Connect > TestFlight 网页中找到你刚刚上传的项目了.
  2. 在TestFlight中创建一个内部测试组(INTERNAL TESTING).
  3. 将测试者的Apple ID加到组里. 这一操作会使得 TestFlight 向你的 Apple ID 对应的邮箱发送一封邀请邮件.
    • 注意, 如果测试者不是当前开发者帐号的成员, 需要先要求他的Apple ID成为开发者帐号的成员.

接受 TestFlight 邀请 ​

  1. 测试者首先需要保证自己的设备已安装 TestFlight 应用. 如尚未安装, 请到 App Store 安装 picture 0
  2. 测试者检查其 Apple ID 对应的邮箱, 找到来自 TestFlight 的邀请邮件.
  3. 点击邮件中的 View in TestFlight 按钮, 这将打开 TestFlight 应用, TestFlight 应用 会询问你是否接受邀请, 点击 Accept. 如此测试者将能够在 TestFlight 应用 中找到测试应用, 测试应用更新时测试者的设备将会收到通知. picture 5

再次上传 ​

加大版本号 ​

如果你想上传一个船新版本新的版本到TestFlight, 那么你必须先到 左侧导航栏 > app.xcodeproj > General > Identity 加大应用的版本号. 因为 TestFlight 只接受比当前版本大的 Archive. picture 2

Xcode Cloud 自动构建并上传 ​

在设置完 Xcode Cloud workflow 后, 每次 Github仓库 的 main 分支变化时, Xcode Cloud 就会自动开始构建 Archive, 完成后自动将其上传到 TestFlight.

自动加大版本号 ​

根据苹果的文档 Writing custom build scripts | Apple, Xcode Cloud 在构建 Archive 之前, 会运行项目文件夹下的 ci_scripts/ci_post_clone.sh 文件. 所以我们可以在该文件中运行一个js脚本来加大版本号.

sh
#!/bin/sh

export HOMEBREW_NO_INSTALL_CLEANUP=TRUE

# Install node start
brew install node
brew link node
# Install node end

# TODO: Replace "app" with the product name of Xcode project
node changeAppInfo.mjs app
js
#!/usr/bin/env node
import fs from "node:fs/promises";
import path from "node:path";
import process from "node:process";

/**
 * Alert! Must pass Xcode project name as first param of the script.
 * ```sh
 * # app is Xcode project name
 * node changeAppInfo.mjs app
 * ```
 */
async function main() {
  try {
    const args = process.argv.slice(2);  // Skip the first two elements
    const projectName = args.at(0)
    if(projectName === undefined) {
      throw new Error("[changeAppInfo] Must pass Xcode project name as first param of the script");
    }

    const configPath = path.resolve(`../${projectName}.xcodeproj/project.pbxproj`)
    await increaseVersion(configPath)
  }
  catch (error) {
    console.error(error)
    process.exit(1)
  }
}

/**
 * Increase version automatically in project.pbxproj file
 * Build #26 1.2 -> 1.2.26
 * Build #27 1.2 -> 1.2.27
 */
async function increaseVersion(configPath) {
  const configText = await fs.readFile(configPath, { encoding: 'utf-8' })
  print(`Changing ${configPath}`)

  const regex = /MARKETING_VERSION = (.+?);/g
  const versionMatch = configText.match(regex)
  if (versionMatch === null) {
    throw new Error(`Cant get iOS bundle version in ${configPath}, terminate build`)
  }

  const bundleVersion = versionMatch[0].replace('MARKETING_VERSION = ', '').replace(';', '')

  const ciBuildNumber = process.env.CI_BUILD_NUMBER
  if(ciBuildNumber === undefined) {
    throw new Error("[changeAppInfo] CI_BUILD_NUMBER is required, but it's undefined");
  }

  const finalBundleVersion = `${bundleVersion}.${ciBuildNumber}`
  print(`Overwrite version: ${bundleVersion} -> ${finalBundleVersion}`)

  const updatedConfigText = configText
    .replace(regex, `MARKETING_VERSION = ${finalBundleVersion};`)

  await fs.writeFile(configPath, updatedConfigText, { encoding: 'utf-8' })
}

function print(message) {
  console.log(`[changeAppInfo] ${message}`)
}

main()

应用图标没有透明像素 ​

使用Xcode Cloud构建时, 应用图标不得有alpha通道, 其实就是不能有透明的像素. 你可以通过将应用图标转换为jpeg格式来解决这个问题, 因为jpeg格式的图片没有alpha通道.

创建 Github 仓库 ​

  1. 创建一个main分支, 然后将这个分支上传Github仓库.
  2. (可选)创建一个build_ios分支专门用于触发构建.

创建 Xcode Cloud workflow ​

  1. 创建 workflow 的按钮在 Xcode > 左侧导航栏 > 最右边那个标签 > Cloud > Get Startedpicture 1
  2. 创建过程中需要为 workflow 增加一个 Archive 类型的 Action. 其中 Distribution Preparation 需要选为 App Store Connect, 因为这个构建最终可能会被发布到 App Storepicture 10
  3. 首次设置需要授权 Xcode Cloud 访问 Github 仓库 picture 0
  4. 此时如果我们向 Github仓库 的 main 分支提交一个 commit, 应该就会触发 Xcode Cloud 的自动构建了. 你可以在 App Store Connect > Xcode Cloud 观察构建过程.

构建完成后自动发布到 TestFlight ​

上述 workflow 配置了自动构建, 但是没有配置自动发布. 我们现在跳转到 App Store Connect > Xcode Cloud.

打开要编辑的 workflow, 然后增加一个 Post-Action, 类型为 TestFlight Internal Testing. 选择你之前创建的测试组, 然后每次 Xcode Cloud 构建完成后就会自动发布到 TestFlight 了. picture 9

参考材料 ​