
1. 为什么App Store截图这么麻烦尺寸、文案与审核的隐性规则如果你独立开发过App一定经历过这个场景功能做完了代码提交到TestFlight了结果在准备App Store上架材料时卡在了截图这一步。一套截图要适配不同尺寸的设备、不同语言的文案、不同页面的状态手工操作一遍至少要半天。改个UI要重截全部改句文案也要重截全部要是赶上发版前一天发现某个页面的数据状态不对那就更酸爽了。我自己第一次提交App的时候就干过这种事凌晨两点了还在Sketch里拖着尺寸参考线一张一张把截图拖进设备边框里然后输入文字、调整字号导出时还要盯着“请使用1290 x 2796像素”的提示。最后App本身没有任何被拒的理由愣是因为截图里的某个数字和实际版本不符被审核打了个“截图不匹配”的反馈。从那之后我就下定决心要找一条更工程化的路。今天想仔细聊聊一个解决这类痛点的开源命令行工具app-store-screenshots。简单说它把“启动模拟器—打开App—截图—加边框—叠加文案—按语言和机型归档”这一整套流程自动化了。你只需要写一份YAML配置剩下的事情交给脚本跑一条命令就能产出一整套“看起来挺专业”的商店截图。适合独立开发者、小团队以及需要频繁发版的同学。如果你现在还停留在手工截图的阶段这篇文章可以帮你把这块耗时省下来而且省下来的不是一次是后面每一次发版。1.1 一套截图要适配多少种屏幕尺寸先说清楚为什么这件事天然就麻烦。Apple的审核后台虽然会提示“上传最大分辨率的截图即可系统会按比例适配”但实际操作中不同尺寸的截图仍然要分别准备。因为iPhone的屏幕比例并不是完全统一的从老一点的5.5英寸到现在的6.7英寸虽然大部分是19.5:9的比例但中间也出现过16:9的时代。而iPad的4:3比例又是另一套逻辑iPad最常用的尺寸需要单独截。目前App Store主要需要的尺寸大概是这样设备类别分辨率像素对应机型示例iPhone 6.7英寸1290 x 2796iPhone 16 Pro Max / 15 Pro MaxiPhone 6.5英寸1242 x 2688iPhone 11 Pro Max / XS MaxiPhone 5.5英寸1242 x 2208iPhone 8 Plus / 7 PlusiPad Pro 12.9英寸2048 x 2732iPad Pro第3代及以后iPad 10.2英寸1620 x 2160iPad第8代及以后注意上面列的是常见规格Apple偶尔会调整“必须上传的尺寸”列表比如新机型发布后旧的5.5英寸截图在某些时候会被标记为“可选”。但不管怎么变你的截图总不能只在一种机型下好看至少大屏iPhone和iPad这两类要覆盖。1.2 截图里的文字本地化与审核的隐性要求第二个麻烦点是文案。很多开发者以为截图嘛就是把App画面截下来传上去就完了。这个理解可以说大错特错。App Store的截图区域是审核团队重点关注的地方也是影响用户下载转化率最大的视觉元素之一。Apple对截图内容有明确的规则要求截图必须来自你的App的真实界面不能展示不存在的功能文案不得包含最高级、虚假宣传、以及未授权的品牌信息如果截图上出现了其他平台或产品的Logo那基本就是送上门的拒绝理由。更隐蔽的一点是截图上的文字如果太小、看不清审核人员会觉得你的App“没准备好”尤其在iPad尺寸下文字密度过高很容易被盯上。如果你做海外市场还需要考虑多语言截图。每个地区的App Store页面可以设置不同的截图文案而不是简单把中文翻译成英文就结束。实际上英文文案直接翻译成德语、日语、韩语在不同的市场上转化效果可能差很多文案长度也会完全不同。这也是为什么大厂往往针对每个市场单独做一套截图。手工维护十几套不同语言的截图基本等于请了一个专门的美工师傅天天和文案版本纠缠。1.3 手工流程为何不可持续手工截图的完整流程大概是用真机或者模拟器切到指定页面保证状态栏是干净的不能显示运营商、Wi-Fi图标、电池量按电源键加音量键截图传到电脑上拖进Sketch或Photoshop套上设备边框写上标题栏文案再按每个语言、每个尺寸导出。这里面每一步都有坑。真机的状态栏很难每次都保持一致模拟器的截图分辨率要和目标尺寸对应光这一个对应关系就够记一阵子加上边框之后素材质量高度依赖排版经验最后还要按语言建子目录一个文件都不许放错位置。我自己干过几轮之后意识到这件事本质上和写代码一样是高度重复、高度可模板化的。如果每次发版都手工做一遍不仅低效而且容易出错。最不能忍的是一旦产品页面文案改了所有尺寸、所有语言都要重新拼一遍。这也是我转向自动化工具的核心理由——重复的事情就应该让脚本去跑人应该去干更有价值的判断和设计。2. app-store-screenshots是什么它怎么把截图“自动化”了这个工具的名字起得非常直白app-store-screenshots做的事情就是把App Store截图这件事从一个手工流程转成一个可配置、可重复执行的流水线。它最核心的思路可以概括成一句话用Xcode的模拟器作为截图环境用配置文件描述你要什么然后由脚本完成截取、加工、输出。具体到一次运行它背后的步骤大致是这样读取一份YAML格式的配置文件理解你要为哪些设备、哪些语言生成截图。根据配置启动对应的iOS模拟器并安装你的App。通过启动参数或Deep Link进入指定的页面。用模拟器自带的截图命令截取当前画面。对原始截图做处理套上设备边框、叠加标题和描述性文案。按“语言/设备/页面”的目录结构输出最终PNG文件。如果你用过Fastlane的snapshot可能会觉得听起来有点像。确实思路有交集但snapshot更偏重“把截图截出来”而app-store-screenshots更偏重“直接产出一套能上传商店的成品图”。它把设备边框、文案排版、多语言目录这些事情都打包进了同一个工具里对不想再折腾PS的人非常友好。2.1 为什么必须用macOS环境跑这里要先给还在Windows上开发朋友打个预防针这工具只能在macOS上跑。原因很简单iOS模拟器是Xcode的一部分而Xcode只存在于macOS。虽然可以用云端的macOS虚拟机但那也是macOS普通Windows机器是没办法直接跑出真·iOS模拟器的。所以如果你的团队是跨平台的建议把“截图生成”这步放到CI里的macOS runner上跑而不是让某个同事的Mac成为唯一的生产力工具。这一点后面讲CI接入的时候还会再提到。2.2 安装之前需要准备什么工欲善其事必先利其器。在安装工具之前你至少要保证下面这些条件都满足一台运行macOS的机器系统版本支持你当前使用的Xcode。安装了完整的Xcode而不是只装了Command Line Tools因为模拟器在Xcode里。在Xcode的Components页面下载了你需要的iOS模拟器运行时比如iOS 17.0、iOS 16.4之类。有一个已经能用模拟器跑起来的App工程。在本地命令行验证一下模拟器是否可用最直接的方式是用xcrun simctl list devices查看已安装的设备列表。如果这条命令能正常输出说明基础环境没问题。2.3 安装工具与跑通最小流程以pip安装为例不同版本的实现细节略有差异建议以项目README为准大致是这样# 使用pip安装 pip install app-store-screenshots装完之后你可以先写一个最小的配置文件比如只给iPhone 6.7英寸生成一张“任务列表页”的截图screenshots: - device: iPhone 6.7 frame: true simulator: iPhone 16 Pro Max language: zh-Hans title: 任务列表 subtitle: 一目了然管理每天要做的事 deep_link: mytasks://tasks然后运行app-store-screenshots --config config.yml如果配置没问题工具会开始启动模拟器安装App根据deep_link打开任务列表页截一张图套上iPhone设备的黑色边框再加上下面那行标题文字最后输出到类似./output/zh-Hans/iphone-6.7/tasks.png的路径。我第一次跑通这个流程的时候说实话有点震撼。之前手工做一套图要两三个小时现在一杯咖啡的时间就批量生成了而且每张图都带着统一的边框和排版风格。3. 核心配置解析config.yml里的每个字段该怎么填这个工具的地基就是配置文件。你把想要的结果描述清楚工具才能干对活。这一节的配置内容是经验之谈我会把每个关键字段怎么填、为什么这么填都说清楚。3.1 设备、模拟器与截图尺寸的对应关系先看第一组字段screenshots: - device: iPhone 6.7 simulator: iPhone 16 Pro Maxdevice对应的是你最终产出截图所归属的“商品规格”simulator则是指定用哪个具体的模拟器机型来截。为什么需要两个字段因为Apple的截图规格是按尺寸分类的不是按机型。比如iPhone 6.7英寸这个尺寸最新机型可能是iPhone 16 Pro Max但明年出了新机型你只要把simulator换成新机型即可device保持不变。这也就意味着工具会在指定的模拟器里运行然后截取模拟器窗口的像素尺寸最终输出的文件名和目录都按device来命名。所以这两个字段的映射关系一定要查清楚让iPhone 6.7英寸的截图出现在5.5英寸的目录下这是很低级的错误。3.2 设备边框的开关与素材选择frame: trueframe字段控制是否给截图套上设备的外壳边框。套上边框的好处是很直观的用户看到截图时第一眼就能认出这是iPhone的形态而且边框会让内容区域显得更聚焦。缺点是有时候边框素材和实际截图尺寸不匹配会导致画面看起来不对劲。如果你用的是iPhone 6.7英寸的截图就该配6.7英寸的边框素材。工具通常内置了常见机型的边框但如果你追求更高的还原度也可以自己准备一套边框图。这里有个小技巧边框素材的透明区域要和屏幕内容严丝合缝最好直接用同一份渲染模板生成。我自己测试下来不用框架自带的边框而是自己从设计稿里导出一张与截图像素尺寸一致的边框PNG效果会更干净。3.3 多语言与文案排版再往下看language: zh-Hans title: 任务列表 subtitle: 一目了然管理每天要做的事language不仅影响输出目录还可能影响工具内部使用的模板文案来源。title和subtitle就是显示在截图上的主标题和副标题一般放在屏幕上方字号要比App界面里的文字大上几号。从转化率角度讲截图上的文字应当聚焦“你的App能帮用户解决什么问题”而不是“这个功能叫什么”。举个例子“任务列表”是功能名“一目了然管理每天要做的事”才是给用户的价值点。很多开发者写着写着就开始报功能列表这种截图对用户几乎没有吸引力。所以这块的文案值得花时间打磨。你也可以把文案设计做得更细比如给前两张图写品牌口号后面几张图写功能亮点每张图的视觉重心都不一样。工具会原样把你写的文案渲染到图上这给了你很大的设计自由度。3.4 页面导航Deep Link还是手动导航这是整个工具里最影响体验的一部分。截图不同页面的流程其实等价于“把App导航到指定位置然后按一下快门”。工具的配置里一般支持两种方式第一种是Deep Link方式actions: - name: 任务列表 deep_link: mytasks://tasks - name: 完成统计 deep_link: mytasks://stats如果App支持URL Scheme那么工具可以自动唤起并进入对应页面完全无人值守。适合页面结构稳定、每个核心页面都有Scheme或者Universal Link的团队。第二种是手动导航。工具会在截图前暂停等你在模拟器里点到目标页面后按回车继续。这种方式适合配置成本极低的场景但自动化程度也低。我建议两种都试一下一开始用手动导航快速验证视觉风格确认后再沉淀出Deep Link配置后面每次发版直接跑全自动。4. 实战从零到拿到一套成品截图的全过程这一节是完整的操作记录。如果你照着做基本可以在一台干净的macOS机器上跑出一套截图。4.1 准备测试工程我建议先不要拿线上正式App试而是搞一个简单的Demo工程或者用工具自带的示例工程。原因是第一次跑的时候可能会遇到模拟器、签名、编译等问题用Demo工程可以排除App本身的影响。命令行创建一个最简SwiftUI工程也是可以的比如xcodegen generate不过为了快直接用Xcode新建一个Single View App即可不需要配任何签名因为模拟器本来就不需要签名。注意Bundle Identifier要和你配置文件里的Deep Link对应上。4.2 安装并配置工具克隆项目到本地目录安装依赖git clone https://github.com/your-path/app-store-screenshots.git cd app-store-screenshots pip install -r requirements.txt然后写一份config.yml。这里给一份稍微完整一点的配置涵盖了三个设备、两个语言、每类设备两个页面screenshots: - device: iPhone 6.7 simulator: iPhone 16 Pro Max frame: true language: zh-Hans app_name: 我的任务 title: 一键管理每日任务 subtitle: 再也不会忘记重要的事 output_dir: ./output/zh-Hans actions: - name: 01-任务列表 deep_link: mytasks://tasks - name: 02-统计页 deep_link: mytasks://stats - device: iPhone 6.7 simulator: iPhone 16 Pro Max frame: true language: en-US title: Manage Your Tasks in One Tap subtitle: Never miss the important things again output_dir: ./output/en-US actions: - name: 01-task-list deep_link: mytasks://tasks - name: 02-stats deep_link: mytasks://stats - device: iPad Pro 12.9 simulator: iPad Pro (12.9-inch) frame: true language: zh-Hans title: 大屏一目了然 subtitle: 为iPad全面优化 output_dir: ./output/zh-Hans-ipad actions: - name: 01-任务列表 deep_link: mytasks://tasks4.3 运行并观察输出运行命令python run.py --config config.yml第一次运行时你会看到控制台里出现大量日志启动模拟器、等待Boot完成、安装App、等待启动、执行Deep Link、截图、合成边框、写文件。每个动作之间都有等待整个过程可能在几分钟到十几分钟不等主要取决于模拟器启动速度和App编译时间。在等待的时候有件事值得留意如果模拟器没有完全启动就执行Deep LinkApp可能还没注册Scheme工具就找不到目标页面。很多工具的机制是先等待Boot再等待App进程出现所以只要不是模拟器卡死一般问题不大。运行结束后输出目录结构大致长这样output/ zh-Hans/ iphone-6.7/ 01-任务列表.png 02-统计页.png ipad-pro-12.9/ 01-任务列表.png en-US/ iphone-6.7/ 01-task-list.png 02-stats.png拿到这些PNG之后你就可以直接拖进App Store Connect里上传了。4.4 跑通之后必须人工检查的三件事工具自动生成的截图不等于可以直接上传。我每次拿到自动截图后都会强制自己检查三件事第一页面上的数据和文案是不是最新版本。比如某些“示例数据”如果已经改了而App页面还显示旧数据截图就是一张废图。第二状态栏是不是干净。测试工程默认状态下状态栏可能显示着“无SIM卡”或“正在充电”这些都会破坏高级感。最好在App里统一处理状态栏或者用模拟器自带的方式隐藏状态栏。第三文字有没有被截断。自动叠加的标题文案如果某行的内容太长在截图边缘会被裁掉。工具一般不会自动换行所以你要在配置文件里就控制好文案长度。5. 绕不开的坑字体、尺寸与模拟器状态自动化工具又不是万能的神踩坑是必然的。这里总结几个我实际遇到、且非常典型的坑每一个都花了我不少时间排查。5.1 中文文字渲染成方块的解法如果你的截图文案是中文工具默认使用的字体可能不支持中文字符渲染出来就是一个个方块或者乱码。这个坑特别容易在英文环境下开发的机器上出现。解法是指定一个系统中文字体文件。比如macOS自带的苹方字体路径一般是/System/Library/Fonts/PingFang.ttc在配置里加一个字体字段具体字段名以你所用版本的文档为准font_path: /System/Library/Fonts/PingFang.ttc font_size: 56 font_color: #FFFFFF如果你需要更统一的跨平台效果也可以把思源黑体的OTF文件放到项目目录里用相对路径引用这样CI环境里也能稳定渲染。我实际用下来思源黑体的字形在商店截图上显得更现代比苹方更不容易出现笔画发虚的问题。5.2 模拟器分辨率与目标尺寸对不上这是第二种经典报错生成出来的PNG尺寸和上传要求不一致。原因多半是模拟器运行时的问题或者模拟器的缩放比例不是100%。解决思路很固定确认模拟器的Device Type和Runtime版本是你要的。使用xcrun simctl io命令截图后用sips -g pixelWidth -g pixelHeight查看尺寸。如果尺寸不对重置模拟器并确认没有通过“窗口 物理尺寸”改变了缩放。有些工具在截图时会强制设置模拟器的分辨率但版本之间行为可能不同。最稳妥的方式是每次运行前都让工具基于干净模拟器执行不要把之前手工调整过的模拟器状态带进去。5.3 串台上一轮截图残留导致画面不对当你连续跑多语言、多设备的配置时很容易遇到这种情况明明配的是“统计页”截出来的却是上一组的“任务列表”。原因通常是模拟器里App没有冷启动Deep Link没有生效画面停留在上一次结束的位置。解决办法有两种。第一种是在每个action之间让工具先杀掉App进程再通过Deep Link重新打开第二种是干脆在配置文件里给每个页面写一个独立启动场景确保App是从冷启动状态进入目标页面的。我个人的倾向是不用省那几秒钟冷启动更接近真实用户首次打开App的感觉截出来的画面也更干净。5.4 深色模式与浅色模式的显示差异很多App支持深色模式后用户在系统设置里选择的模式会直接影响截图的观感。而模拟器如果没有固定外观模式截图可能在深浅色之间来回跳导致一套截图中混着两种风格。Apple的审核并不禁止深色模式截图但同一套截图里前一张浅色、后一张深色会显得很不专业。这里建议在配置里显式指定外观模式或者在App内部固定截图的配色方案。至少保证同一批次的截图外观一致。如果工具没有直接提供外观参数你可以先在模拟器里通过“设置 开发者 Dark Appearance”固定为深色再开始运行。注意模拟器的外观设置对同一运行时是全局的跑完记得改回来。6. 进阶思路多语言、CI与模板复用当你把第一套截图跑通之后基本就打开了新世界的大门。接下来可以围绕“工程化”继续压榨这个工具的价值。6.1 用一个模板批量生成多语言截图多语言截图最容易踩的坑是每种语言的文案长度不一样同一个排版位置德语可能比中文长出一大截英文又可能太短显得空白。所以不要指望一套位置参数走天下。我的做法是把文案和排版参数都写在YAML里用循环脚本批量生成。比如写一个小脚本读一个文案表格自动生成对应的config片段再调用工具逐组运行。这样每次要加一种语言只需要在表格里加一行而不是手动改整个配置。6.2 在CI上自动跑截图如果团队有macOS的CI环境可以把截图生成加入发版流程。比如在GitHub Actions里用macOS runner在tag推送后自动安装依赖、编译App、运行截图工具、把产物传回Artifact。一个最小化的工作流思路是这样的name: generate-screenshots on: push: tags: [v*] jobs: generate: runs-on: macos-14 steps: - name: Checkout uses: actions/checkoutv4 - name: Install dependencies run: pip install -r requirements.txt - name: Generate screenshots run: python run.py --config config.yml - name: Upload artifacts uses: actions/upload-artifactv4 with: name: app-screenshots path: output/这里有个经验之谈CI runner的模拟器环境是冷启动的第一次simctl boot会比较慢所以流水线的超时时间建议放宽到20分钟以上不要用默认的10分钟否则大概率中途被kill。而且macOS runner的并发有限同一个时间点不要同时跑太多截图任务。6.3 模板复用与团队协作的落地建议最后聊一下团队协作。截图这东西看似只是产品发布前的小环节但在团队里往往牵扯到设计师、运营、开发三方。我见过不止一次因为截图版本不一致导致的发版事故设计师做了一版带边框的图运营改了文案开发又更新了页面数据最后上传的时候发现三者对不上。用配置化工具之后这个问题的解法和代码管理一样把config.yml纳入Git截图产物作为发版Checklist的一部分。改文案就走分支合并改页面就走代码评审一切都留下痕迹。唯一的注意点是生成的PNG不要轻易手工修图。一旦手工修了配置就失真了下次自动生成又会回到“没人记得住到底哪张是最终版”的状态。我个人实际用下来的体会是这类自动化工具真正解决的不是“从1张变成100张”的效率问题而是把整个流程变成可审计、可重复、可交接的工程资产。今天你写完这个配置半年后哪怕换了一个人只要照着跑一遍命令产出的标准仍然和当初一样。这才是工具最大的价值。