Skip to content

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

目标

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

前提条件

  1. 你的Apple ID已加入苹果开发者计划
  2. 一个Github账户

创建一个 Xcode 项目

创建一个新的Bundle ID

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

创建一个App Store Connect App

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

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

创建Xcode项目

  1. 打开Xcode, 创建一个新的项目.
  2. 然后将此项目的Bundle ID修改为我们新创建的Bundle ID. 修改位置在 左侧导航栏 > app.xcodeproj > Signing & Capabilities > Signing > Bundle Identifier. picture 2
  3. 修改Bundle ID后, Xcode会自动获取Profile. 点击 Provisioning Profile 右边的i图标, 如果全部是打勾, 则说明获取成功. picture 3
  4. 在 iOS 模拟器上运行该项目, 保证该项目可以正常运行.

手动构建并上传

注意

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

首次上传

设置应用图标

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

设置加密方式

设置 Info.plistITSAppUsesNonExemptEncryption 键的值.

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

构建, 上传

  1. 设置 Run DestinationAny 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.

创建 Github 仓库

创建一个main分支, 然后将这个分支上传Github仓库.

创建 Xcode Cloud workflow

  1. 创建 workflow 的按钮在 Xcode > 左侧导航栏 > 最右边那个标签 > Cloud > Get Startedpicture 1
  2. 创建过程中需要为 workflow 增加一个 Archive 类型的 Action. 其中 Distribution Preparation 需要选为 App Store Connect, 因为这个构建最终可能会被发布到 App Storepicture 10
  3. 此时如果我们向 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

自动加大版本号

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

sh
#!/bin/sh

# Note: Xcode will run ci_post_clone.sh at ci_scripts directory

export HOMEBREW_NO_INSTALL_CLEANUP=TRUE

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

# Change App info
node changeAppInfo.mjs
js
#!/usr/bin/env node
import fs from "node:fs/promises";
import path from "node:path";
import process from "node:process";

async function main() {
  try {
    // TODO: Replace config path. Note: Xcode will run ci_post_clone.sh at ci_scripts directory
    const configPath = path.resolve('../app.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 finalBundleVersion = `${bundleVersion}.${process.env.CI_BUILD_NUMBER}`
  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通道.

参考材料