惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

有赞技术团队
有赞技术团队
M
MIT News - Artificial intelligence
Hugging Face - Blog
Hugging Face - Blog
博客园 - 聂微东
量子位
S
SegmentFault 最新的问题
V
Visual Studio Blog
博客园 - 【当耐特】
Apple Machine Learning Research
Apple Machine Learning Research
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
小众软件
小众软件
Stack Overflow Blog
Stack Overflow Blog
Vercel News
Vercel News
D
Docker
J
Java Code Geeks
博客园 - 三生石上(FineUI控件)
博客园 - Franky
Recent Announcements
Recent Announcements
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
MongoDB | Blog
MongoDB | Blog
D
DataBreaches.Net
Y
Y Combinator Blog
云风的 BLOG
云风的 BLOG
V
V2EX

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant
How I auto-generate 800+ App Store screenshots across 39 ...
Yoshiaki Hirokawa · 2026-06-27 · via DEV Community

Yoshiaki Hirokawa

App Store screenshots are the highest-leverage marketing asset an app has — and the most painful to maintain. Now multiply that pain by 39 languages and 3 device classes. Doing that by hand is not "tedious," it's impossible to keep in sync.

So I built a pipeline that turns one command into ~800 finished, captioned, device-correct screenshots for Cadento, my SwiftUI focus timer. Here's the architecture.

The scale problem

The output target:

Device Shots per language Languages Total
iPhone 6.9" 10 39 390
iPad 13" 8 39 312
Apple Watch 3 39 117

That's 819 images, each needing the right language UI and the right localized caption. Change one screen design and every number above regenerates. Hand-editing is off the table — the only sane answer is "rebuild everything from source on demand."

The pipeline, end to end

XCUITest (per language)  →  raw localized PNGs
        ↓
extract from .xcresult
        ↓
Python + Pillow: compose background + device frame + caption
        ↓
AppStore画像/<device>/<lang>/1..N.png   (exact store dimensions)

Five stages. Each is independently re-runnable.

Stage 1 — Capture real localized screens with XCUITest

The key insight: don't fake screenshots, drive the real app. A UI test launches the app, forces a specific language/locale, navigates to each screen, and snapshots it.

Language and locale come in as environment variables so one test file covers every language:

let lang   = ProcessInfo.processInfo.environment["SHOT_LANG"]   ?? "en"
let locale = ProcessInfo.processInfo.environment["SHOT_LOCALE"] ?? "en_US"

app.launchArguments += ["-AppleLanguages", "(\(lang))"]
app.launchArguments += ["-AppleLocale", locale]
app.launch()

// navigate + snapshot each screen
let shot = XCTAttachment(screenshot: app.screenshot())
shot.lifetime = .keepAlways
add(shot)

A shell loop runs this once per language. Because it's the actual app, the screenshots are guaranteed to match what users see — including RTL flips for Arabic/Hebrew and text expansion in German.

Stage 2 — Extract PNGs from the .xcresult

XCUITest buries screenshots inside an .xcresult bundle. A small Python script walks the result and pulls out the raw PNGs into a flat per-language folder. Nothing clever — just plumbing so the next stage has clean inputs.

Stage 3 — Compose with Python + Pillow

This is where raw screens become marketing. For each shot, Pillow:

  1. Draws the branded background (generated separately, app-themed gradients)
  2. Places the device frame
  3. Drops the raw screenshot into the frame at the correct offset
  4. Renders the localized caption on top — pulled from a per-language strings map

The caption text is itself localized (39 languages of ASO copy), so the marketing message reads natively, not just the UI underneath it. Font fallback matters here: CJK, Arabic, Hebrew, Thai, and Devanagari all need the right font or you get tofu (□□□).

Stage 4 — Live Activity & Watch shots

Live Activity (Dynamic Island / lock screen) and Apple Watch screens are generated through their own paths and folded into the same compositor, so the final set is consistent across all surfaces.

Stage 5 — Output to exact store dimensions

Everything lands in a predictable tree at the exact pixel sizes App Store Connect requires:

AppStore画像/iPhone_6.9/<lang>/1..10.png   (1320×2868)
AppStore画像/iPad_13/<lang>/1..8.png       (2064×2752)
AppStore画像/AppleWatch/<lang>/1..3.png    (410×502)

From here it's a straight upload (I drive App Store Connect's API to swap a single device's set without touching the others — but that's another post).

Lessons from running it for real

  • Drive the real app, don't mock. The whole value is that screenshots can't lie about what the UI does in each language.
  • Environment variables > 39 test targets. One parameterized UI test beats copy-pasted code every time.
  • Font fallback is not optional. Test the hardest scripts (Arabic, Thai, Hindi, CJK) early or you'll ship boxes.
  • Make every stage idempotent. A design change should be one command away from 819 fresh images, not a weekend.
  • Separate UI capture from caption rendering. Redesign the screen? Re-run stage 1. Rewrite the marketing copy? Re-run stage 3. They shouldn't be coupled.

The payoff: when I change a screen or a tagline, I'm not dreading a manual marathon. I run the pipeline, and the entire localized store presence updates itself.


I'm a solo iOS developer from Japan building small, deeply localized apps. Cadento (focus timer, 39 languages) is on the App Store. Ask me anything about the pipeline in the comments.