For Quiet I need to make 975 screenshots for the App Store. It is way way too much to do that by hand. And waaaay to much work to put it in a nice frame with a label.
Quick math
- 2 iPad models * 4 screenshots = 8
- 3 iPhone models * 4 screenshots = 12
- 1 Mac model * 5 screenshots = 5
- 25 screenshots per language * 39 languages = 975
1. Write UI Tests and make screenshots
Start with a function which makes the screenshots.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
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)
}
|
In this example I only switch tabs and only show the iPhone part. Part for iPad is similar.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
// Device is an iPhone
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)
// Tap on tabbar to go to second view
tabBar.buttons.element(boundBy: 1).tap()
sleep(2)
takeScreenshot(name: "1")
sleep(2)
// Tap on tabbar to go to third view
tabBar.buttons.element(boundBy: 2).tap()
sleep(2)
takeScreenshot(name: "2")
sleep(2)
// Tap on tabbar to go to fourth view
tabBar.buttons.element(boundBy: 3).tap()
sleep(2)
takeScreenshot(name: "3")
sleep(2)
}
|
With Xcode’s record test functionality it is easy to navigate through an app and learn
how to write UITests.
2. Run UITest automated
With a bit of help from the internet I tweaked a shell script
that runs the UITest created above on all devices and languages specified.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
|
#!/bin/bash
# The Xcode project to create screenshots for
projectName="/path/to/project/AppName/AppName.xcodeproj"
# The scheme to run tests for
schemeName="AppNameUITests"
# Temporary Data Folder
tempFolder="/tmp/AppNameData"
# Save final screenshots into this folder (it will be created)
targetFolder="/path/to/project/AppName/fastlane/screenshots/ios"
# All the simulators we want to screenshot
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)"
)
# All the languages we want to screenshot (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
# Boot simulator
echo "$simulator booting"
xcrun simctl boot "$simulator"
# Get UUID of simulator
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"
# Override statusbar
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: you need xcparce and Xcode command line tools installed for this script.
3. Remove UUID’s from screenshot file names
I reused an AppleScript that I was using for another automated flows.
1
2
3
4
5
6
7
8
9
10
11
12
|
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
|
Now we have a nice file name that we can use in the next step.
4. Framing
ScreenshotFramer an app made by the people of MindNode.
I has a nice UI for you (or designer) can make a nice framing of the screenshot. You can add text (localised), background colours, and other images.

To create framed screenshot for all devices simply type this command in the Terminal.
Screenshot-Framer-CLI -project .
5. Sending it to App Store Connect
Currently I use fastlane deliver to send the framed screenshots to App Store Connect.
Their website has the best documentation on how to set it up.
I am however writing a Xcode Project Plugin with the App Store Connect API to send screenshots and metadata to App Store Connect.
With all the exciting Vision Pro stuff and experimenting that plugin is on hold for now, but will continue when the dust has settled down a bit.
After thoughts
I did it a few times by hand, I took me a whole day and some RSI issues 😝
Now the wole process takes 30 minutes… (on a M1 Mac Mini)
- 20 minutes taking screenshots
- 6 minutes framing
- 4 minutes uploading
Run this flow every time I release a new version of Quiet, so it always has up to date screenshots.
Initial setup took me approximately half a day.