VersionIcon adds an overlay to your iOS app icon showing the build variant and the app version. The overlay can include a ribbon with the build variant (Devel, Staging, Production…), the version and build number, or both. You can customize the overlay in many ways or supply your own graphics. VersionIcon ships as a prebuilt binary, so it doesn't depend on how your project is set up.
- macOS 14+ on Apple silicon (the prebuilt binary is arm64-only)
- Xcode 16+ (Swift 6 toolchain)
- An iOS app whose app icon lives in an asset catalog
VersionIcon is distributed as a Swift package. Add the package to your project. In Xcode, choose File › Add Package Dependencies… and enter the URL, or add it in Package.swift:
.package(url: "https://github.com/strvcom/ios-version-icon.git", from: "1.2.3")The package ships a prebuilt VersionIcon binary and its resources in the Bin folder of the package checkout. Point your Run Script phase at that folder:
| Setup | Bin folder path in the Run Script phase |
|---|---|
| Xcode package dependency | "${BUILD_DIR%/Build/*}/SourcePackages/checkouts/ios-version-icon/Bin" |
Tuist (Tuist/Package.swift) |
"${SRCROOT}/../Tuist/.build/checkouts/ios-version-icon/Bin" |
-
Duplicate your app icon in the asset catalog and name the two sets
AppIconandAppIconOriginal:AppIconOriginalis the clean source image. VersionIcon only reads it and never changes it.AppIconstays the target's app icon (Primary App Icon Set Name). VersionIcon overwrites its images on every build: with the overlay for development builds, or with the clean original when you pass--originalfor production builds.
Keep the same entries (size, scale, idiom, platform and appearance, such as Dark and Tinted variants) in both sets. VersionIcon skips any entry that's in only one of them and prints a warning. It reads the entries from the
Contents.jsonof both sets, so it works with modern single-size asset catalogs as well as legacy multi-size ones.If your icon sets have other names, pass them with
--appIconand--appIconOriginal. The names must be unique in the project, because VersionIcon uses the first.appiconsetfolder it finds with each name. -
Add a Run Script phase in your target's Build Phases with this script:
VERSION_ICON_PATH="${BUILD_DIR%/Build/*}/SourcePackages/checkouts/ios-version-icon/Bin" if [ "${CONFIGURATION}" = "Release" ]; then "$VERSION_ICON_PATH/VersionIcon" --resources "$VERSION_ICON_PATH" --original else "$VERSION_ICON_PATH/VersionIcon" --resources "$VERSION_ICON_PATH" \ --ribbon Blue-TopRight.png --title Devel-TopRight.png --on-error warn fi
If your project uses other configuration names, adjust the conditions. If you use Tuist, change
VERSION_ICON_PATHto the Tuist path from Installation. To use your own ribbon or title artwork, pass an absolute path to a.pngfile. -
Move the Run Script phase from step 2 before the Copy Bundle Resources phase. The script must run before the icon is copied into the app.
This is the setup used by the JustFlip app shown above, which installs VersionIcon through Tuist. It uses one ribbon per environment, a rotated two-line version label, and no overlay on the App Store build:
VERSION_ICON_PATH="$SRCROOT/../Tuist/.build/checkouts/ios-version-icon/Bin"
OUTPUT_ASSET_CATALOG="$SRCROOT/JustFlip/Application/Resources/VersionIconGenerated.xcassets"
COMMON_ARGS=(--appIcon "AppIcon-${CONFIGURATION}" --appIconOriginal AppIconOriginal \
--outputAssetCatalog "$OUTPUT_ASSET_CATALOG" --resources "$VERSION_ICON_PATH")
STYLE_ARGS=(--titleSize 0.17 --fillColor "#000000" --strokeColor "#FFFFFF" --strokeWidth 0.05 \
--titleRotation 18 --horizontalTitlePosition 0.35 --verticalTitlePosition 0.7 \
--versionStyle parenthesisTwoLines)
case "${CONFIGURATION}" in
"Production-Release")
"$VERSION_ICON_PATH/VersionIcon" "${COMMON_ARGS[@]}" --original ;;
"Production-Debug")
"$VERSION_ICON_PATH/VersionIcon" "${COMMON_ARGS[@]}" --ribbon Red-TopRight.png --title Prod-TopRight.png "${STYLE_ARGS[@]}" ;;
"Staging-"*)
"$VERSION_ICON_PATH/VersionIcon" "${COMMON_ARGS[@]}" --ribbon Gold-TopRight.png --title Staging-TopRight.png "${STYLE_ARGS[@]}" ;;
"Development-"*)
"$VERSION_ICON_PATH/VersionIcon" "${COMMON_ARGS[@]}" --ribbon Blue-TopRight.png --title Devel-TopRight.png "${STYLE_ARGS[@]}" ;;
*)
echo "error: VersionIcon does not recognize CONFIGURATION '${CONFIGURATION}'"
exit 1 ;;
esacBy default, VersionIcon writes the generated images into the --appIcon icon set in your sources, so every build changes tracked files. For new projects, add --outputAssetCatalog so your source assets stay unchanged:
"$VERSION_ICON_PATH/VersionIcon" \
--appIcon "AppIcon-${CONFIGURATION}" \
--appIconOriginal AppIconOriginal \
--outputAssetCatalog "${SRCROOT}/VersionIconGenerated.xcassets" \
--resources "$VERSION_ICON_PATH" \
--ribbon Blue-TopRight.png \
--title Devel-TopRight.png \
--on-error warn- VersionIcon creates the catalog and its icon sets as needed. The output catalog must be separate from the one that holds
AppIconOriginal. - Add
VersionIconGenerated.xcassetsto the target's Copy Bundle Resources phase, and keep the VersionIcon phase before it. - Set the target's Primary App Icon Set Name (
ASSETCATALOG_COMPILER_APPICON_NAME) toAppIcon-$(CONFIGURATION). Using a separate icon name per configuration means switching configurations doesn't overwrite another configuration's generated files. - The generated catalog is a build artifact. Add
VersionIconGenerated.xcassets/*.appiconset/to.gitignore.
Run VersionIcon --help for the full list.
| Parameter | Description | Default |
|---|---|---|
--ribbon <file> |
Ribbon image. Bin/Ribbons contains ribbons in six colors (Blue, Cyan, Gold, Green, Purple, Red) for every corner (TopLeft, TopRight, BottomLeft, BottomRight). You can also pass an absolute path to your own .png. |
none |
--title <file> |
Title image placed on the ribbon. Bin/Titles has Alpha, Beta, Debug, Demo, Dev, Devel, MVP, Prod and Staging for every corner, e.g. Devel-TopRight.png. You can also pass an absolute path to your own .png. Titles are transparent images; custom text isn't supported yet. |
none |
| Parameter | Description | Default |
|---|---|---|
--versionStyle <style> |
Format of the version label: dash (1.2.0 - 16), parenthesis, parenthesisTwoLines, twoLines, versionOnly, buildOnly or empty. See Examples. |
dash |
--fillColor <#RRGGBB> |
Text fill color. | #FFFFFF |
--strokeColor <#RRGGBB> |
Text stroke color. | #000000 |
--strokeWidth <ratio> |
Stroke width as a fraction of the icon width. | 0.03 |
--font <name> |
Font of the label. | Impact |
--titleSize <ratio> |
Text size as a fraction of the icon width. | 0.25 |
--horizontalTitlePosition <ratio> |
Horizontal center of the label as a fraction of the icon width, measured from the left edge. | 0.5 |
--verticalTitlePosition <ratio> |
Vertical center of the label as a fraction of the icon height, measured from the bottom edge. | 0.2 |
--titleRotation <degrees> |
Label rotation, from -180 to 180. |
0 |
--titleAlignment <alignment> |
left, center or right. |
center |
| Parameter | Description | Default |
|---|---|---|
--resources <path> |
The package's Bin folder, which contains Ribbons and Titles. |
required |
--appIcon <name> |
Icon set that VersionIcon writes to. By default it must already exist in the project and be the target's app icon. With --outputAssetCatalog, VersionIcon creates it inside that catalog. |
AppIcon |
--appIconOriginal <name> |
Source icon set with the clean images. VersionIcon only reads it. | AppIconOriginal |
--outputAssetCatalog <path> |
Optional .xcassets folder for the generated icon. See Generated asset catalog mode. |
none |
--original |
Copy the clean images from --appIconOriginal into --appIcon without any overlay, typically for production builds. |
off |
--on-error <fail|warn> |
fail stops the build when VersionIcon fails; warn prints the error and lets the build continue. |
fail |
--help |
Print all parameters. |
The icons below are from the JustFlip app (version 1.2.0, build 16). They all share these arguments:
COMMON=(--resources "$VERSION_ICON_PATH" --titleSize 0.17 \
--fillColor "#000000" --strokeColor "#FFFFFF" --strokeWidth 0.05)
HORIZONTAL=(--horizontalTitlePosition 0.5 --verticalTitlePosition 0.2)
ROTATED=(--titleRotation 18 --horizontalTitlePosition 0.35 --verticalTitlePosition 0.7)Each icon adds its own ribbon, title, label layout and --versionStyle. For example, the first one is:
"$VERSION_ICON_PATH/VersionIcon" "${COMMON[@]}" "${HORIZONTAL[@]}" \
--ribbon Blue-TopRight.png --title Devel-TopRight.png --versionStyle dash
--ribbon Blue-TopRight.png
--title Devel-TopRight.png
"${HORIZONTAL[@]}" |
--ribbon Gold-TopRight.png
--title Staging-TopRight.png
"${HORIZONTAL[@]}" |
--ribbon Red-TopRight.png
--title Prod-TopRight.png
"${ROTATED[@]}" |
--ribbon Green-TopRight.png
--title Demo-TopRight.png
"${ROTATED[@]}" |
--ribbon Purple-TopRight.png
--title Beta-TopRight.png
"${ROTATED[@]}" |
--ribbon Cyan-TopRight.png
--title MVP-TopRight.png
"${ROTATED[@]}" |
--ribbon Blue-TopRight.png
--title Devel-TopRight.png
"${ROTATED[@]}" |
To change VersionIcon and debug it against your own project, set up the VersionIcon scheme like this:
- Arguments Passed On Launch: copy the parameters from your project's VersionIcon build phase.
- Environment Variables: set
SRCROOT,PROJECT_DIRandINFOPLIST_FILE. You can find their values in the build log in Xcode's Report navigator.
Issues and pull requests are welcome!
Daniel Čech (GitHub)
VersionIcon is released under the MIT license. See LICENSE for details.