My App Store Screenshots Flow
Creating 975 screenshots for the App Store is a daunting task if done manually. For my app, Quiet, I need screenshots for multiple devices and languages, and framing them nicely with labels adds even more work. Doing this by hand is simply not feasible. Here’s how I automated the process to save time and effort.
The Scale of the Task
Here’s the breakdown of the screenshot requirements:
- 2 iPad models × 4 screenshots = 8 screenshots
- 3 iPhone models × 4 screenshots = 12 screenshots
- 1 Mac model × 5 screenshots = 5 screenshots
- Total: 25 screenshots per language × 39 languages = 975 screenshots
Manually capturing, naming, and framing these would take hours and risk repetitive strain injuries. Automation is the solution.
Step 1: Writing UI Tests for Screenshots
The first step is to write UI tests to capture screenshots automatically. I created a Swift function to handle this.
func takeScreenshot(name: String) {
let fullScreenshot = XCUIScreen.main.screenshot()
let type = "public.png"
let payload = fullScreenshot.pngRepresentation
let name = "\(UIDevice.current.name)-\(name).png"
let screenshot = XCTAttachment(
uniformTypeIdentifier: type,
name: name,
payload: payload,
userInfo: nil
)
screenshot.lifetime = .keepAlways
add(screenshot)
}
For iPhones, I wrote a test that navigates through the app’s tabs and takes screenshots. The iPad version follows a similar approach.
if UIDevice.current.userInterfaceIdiom == .phone {
let app = XCUIApplication()
app.launchArguments = ["enable-screenshot-data"]
app.launch()
let tabBar = app.tabBars.element(boundBy: 0)
// First view
takeScreenshot(name: "0")
sleep(2)
// Second view
tabBar.buttons.element(boundBy: 1).tap()
sleep(2)
takeScreenshot(name: "1")
sleep(2)
// Third view
tabBar.buttons.element(boundBy: 2).tap()
sleep(2)
takeScreenshot(name: "2")
sleep(2)
// Fourth view
tabBar.buttons.element(boundBy: 3).tap()
sleep(2)
takeScreenshot(name: "3")
sleep(2)
}
Xcode’s test recording feature simplifies this process by letting you navigate the app and generate UI test code automatically.
Step 2: Running UI Tests Automatically
To run the tests across all devices and languages, I adapted a shell script. It boots simulators, sets up the status bar, and runs the tests for each combination.
#!/bin/bash
# Xcode project path
projectName="/path/to/project/AppName/AppName.xcodeproj"
# Scheme for UI tests
schemeName="AppNameUITests"
# Temporary data folder
tempFolder="/tmp/AppNameData"
# Output folder for screenshots
targetFolder="/path/to/project/AppName/fastlane/screenshots/ios"
# Simulators to use
simulators=(
"iPhone 11 Pro Max"
"iPhone 14 Pro Max"
"iPhone 8 Plus"
"iPad Pro (12.9-inch) (6th generation)"
"iPad Pro (12.9-inch) (2nd generation)"
)
# Languages (ISO 3166-1 codes)
languages=(
"ar-SA"
"de-DE"
"en-GB"
"es-ES"
"es-MX"
"fr-CA"
"fr-FR"
"he"
"ja"
"ko"
"nl-NL"
"pt-BR"
"pt-PT"
"zh-Hans"
"zh-Hant"
)
for simulator in "${simulators[@]}"
do
echo "$simulator booting"
xcrun simctl boot "$simulator"
# Get simulator UUID
deviceUUID=$(xcrun simctl list devices | grep "(Booted)" | grep -E -o -i "([0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12})")
echo "uuid: $deviceUUID"
# Set status bar
echo "Override statusbar"
xcrun simctl status_bar $deviceUUID override --time "2007-01-09T09:41:00+01:00" --dataNetwork wifi --wifiMode active --wifiBars 3 --cellularMode active --cellularBars 4 --batteryState charged --batteryLevel 100 &> /dev/null
for language in "${languages[@]}"
do
rm -rf $tempFolder/Logs/Test
echo "Building and Running for $simulator in $language"
xcodebuild -testLanguage $language -scheme $schemeName -project $projectName -derivedDataPath $tempFolder -destination "platform=iOS Simulator,name=$simulator" build test
echo "Collecting Results..."
mkdir -p "$targetFolder/$language"
find $tempFolder/Logs/Test -maxdepth 1 -type d -exec xcparse screenshots {} "$targetFolder/$language" \;
done
xcrun simctl shutdown $deviceUUID
echo "$simulator shutdown"
done
Note: This script requires xcparse
and Xcode command line tools to be installed.
Step 3: Cleaning Up File Names
The screenshots include simulator UUIDs in their file names, which need to be removed for clarity. I used an AppleScript to rename them.
on run {input, parameters}
repeat with myFile in input
tell application "System Events"
set myName to the characters 1 thru ((offset of ".png" in (name of myFile as text)) - 1) of (name of myFile as text)
tell application "Finder"
set myExtention to name extension of (myFile as alias)
set myNewName to characters 1 thru (((length of myName) - 37) as number) of (myName as text)
set name of file (myFile as text) to (myNewName & "." & myExtention as text)
end tell
end tell
end repeat
end run
This produces clean file names for the next step.
Step 4: Framing Screenshots
To make screenshots look professional, I use ScreenshotFramer, an app by the MindNode team. It offers a user-friendly interface to add localised text, background colours, and other design elements. To frame screenshots for all devices, run this Terminal command:
Screenshot-Framer-CLI -project .
Step 5: Uploading to App Store Connect
I use fastlane deliver
to upload the framed screenshots to App Store Connect. Their website provides excellent setup instructions. I’m also working on an Xcode Project Plugin using the App Store Connect API to handle screenshots and metadata, but it’s on hold while I explore Vision Pro development. I’ll resume it later.
The Results
Manually creating screenshots took me a full day and caused wrist strain. Now, the automated process takes just 30 minutes on an M1 Mac Mini:
- 20 minutes for capturing screenshots
- 6 minutes for framing
- 4 minutes for uploading
I run this workflow with every Quiet release to keep screenshots up to date. The initial setup took about half a day, but the time savings make it worthwhile.
Category:
Tags:
Year: