From apple-dev-skills
Rasterize a designer-authored 1024×1024 SVG into the PNG that Apple's asset catalog wants — no Homebrew dependency, no Cloud service, just `qlmanage` already on the Mac.
How this skill is triggered — by the user, by Claude, or both
Slash command
/apple-dev-skills:app-icon-rasterizeThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Use when a designer hands off the icon as SVG and you need the PNG that `AppIcon.appiconset/Contents.json` references.
Use when a designer hands off the icon as SVG and you need the PNG that AppIcon.appiconset/Contents.json references.
This is the gap discovered during first-pass icon production on a real project — the SVG → 1024 PNG step was undocumented (only the downstream Pillow Lanczos downscale to the macOS ladder was written up). Each appearance — Light, Dark, Tinted — is a single 1024 universal PNG produced by exactly one rasterize step, documented here. (Tinted is now a standard third appearance alongside Light and Dark; all apps in the reference project ship AppIcon-Tinted.png.)
light.svg + dark.svg + tinted.svg matching the icon specAppIcon-Light.png + AppIcon-Dark.png in <app>/Assets.xcassets/AppIcon.appiconset/rsvg-convert, inkscape, or imagemagick (verified absent on the project Mac as of 2026-06-01; Homebrew is not installed)<filter> blurs, <text>, embedded fonts, or other features QuickLook's SVG generator may render unreliably. Re-author the SVG with flat shapes first.Xcode 26 / macOS Sequoia still requires the explicit 16/32/128/256/512 ladder for macOS app icons. "Single Size" is iOS-only. The Appearances Inspector for macOS targets does not expose a single-asset option. Without the ladder, Xcode emits "AppIcon has N unassigned children" on the macOS target because the universal idiom does not satisfy AppKit's expected slots.
After producing the 1024 master:
SRC=<App>/Assets.xcassets/AppIcon.appiconset/AppIcon-Light.png
DST=<App>/Assets.xcassets/AppIcon-macOS.appiconset
mkdir -p "$DST"
cp "$SRC" "$DST/[email protected]" # 1024
sips -Z 512 "$SRC" --out "$DST/[email protected]" # 512
sips -Z 512 "$SRC" --out "$DST/icon_512x512.png" # 512
sips -Z 256 "$SRC" --out "$DST/[email protected]" # 256
sips -Z 256 "$SRC" --out "$DST/icon_256x256.png" # 256
sips -Z 128 "$SRC" --out "$DST/icon_128x128.png" # 128
sips -Z 64 "$SRC" --out "$DST/[email protected]" # 64
sips -Z 32 "$SRC" --out "$DST/icon_32x32.png" # 32
sips -Z 32 "$SRC" --out "$DST/[email protected]" # 32
sips -Z 16 "$SRC" --out "$DST/icon_16x16.png" # 16
sips -Z <max> resizes the longest dimension to <max> (Lanczos-equivalent quality). Each downscale is from the 1024 master, not the previous step.
Project.swift needs the SDK-scoped override so iOS targets keep the universal AppIcon and macOS targets pick up the ladder:
let appTargetSettings: SettingsDictionary = swiftSettings.merging([
"ASSETCATALOG_COMPILER_APPICON_NAME": "AppIcon",
"ASSETCATALOG_COMPILER_APPICON_NAME[sdk=macosx*]": "AppIcon-macOS",
]) { _, new in new }
The designer's SVG must NOT bake rounded corners into the artwork. Apple's compositor applies the squircle mask at render time (iOS Springboard, macOS Dock, every preview surface). Baking corners produces a double-mask look — the icon shows up smaller than its peers with visible inner padding.
Common mistakes that cause this:
<!-- WRONG — corners baked into the artwork via clipPath -->
<defs>
<clipPath id="iconMask">
<rect width="1024" height="1024" rx="246" ry="246"/>
</clipPath>
</defs>
<g clip-path="url(#iconMask)">
<rect width="1024" height="1024" fill="#FAF8F3"/>
...
</g>
<!-- ALSO WRONG — rx/ry directly on the background rect -->
<rect width="1024" height="1024" rx="246" ry="246" fill="#FAF8F3"/>
<!-- RIGHT — background fills 1024×1024 to the edges, no clip, no rx/ry -->
<rect x="0" y="0" width="1024" height="1024" fill="#FAF8F3"/>
<!-- artwork on top, also no clip -->
After rasterize, sips -g hasAlpha …png will still report yes because QuickLook writes 8-bit RGBA — but every pixel including the corners must be opaque. Quick check: open the PNG in Preview at 100%, hover the corners — Digital Color Meter should report the background color (e.g. #FAF8F3), not transparency.
This rule applies to Light, Dark, and Tinted variants. There is no platform on which the artwork should pre-apply its own squircle.
# 1. Designer writes SVG to ../tmp/ (project-scope scratch
# dir, NOT /tmp). Each SVG must declare
# width="1024" height="1024" viewBox="0 0 1024 1024".
ls -la ../tmp/<app>-icon-light.svg ../tmp/<app>-icon-dark.svg
# 2. Rasterize via QuickLook thumbnail generator.
# -t thumbnail mode
# -s 1024 thumbnail size (square)
# -o <dir> output directory (-o cannot rename the file, see step 3)
qlmanage -t -s 1024 -o ../tmp \
../tmp/<app>-icon-light.svg \
../tmp/<app>-icon-dark.svg \
../tmp/<app>-icon-tinted.svg
# 3. qlmanage's quirk: it APPENDS `.png` to the source filename instead of
# swapping the extension. So `light.svg` becomes `light.svg.png`. Rename
# directly into the asset catalog so the .svg.png artifact never lingers.
mv ../tmp/<app>-icon-light.svg.png \
<App>/Assets.xcassets/AppIcon.appiconset/AppIcon-Light.png
mv ../tmp/<app>-icon-dark.svg.png \
<App>/Assets.xcassets/AppIcon.appiconset/AppIcon-Dark.png
mv ../tmp/<app>-icon-tinted.svg.png \
<App>/Assets.xcassets/AppIcon.appiconset/AppIcon-Tinted.png
# 4. Verify dimensions + format.
file <App>/Assets.xcassets/AppIcon.appiconset/AppIcon-Light.png
# expected: PNG image data, 1024 x 1024, 8-bit/color RGBA, non-interlaced
sips -g pixelWidth -g pixelHeight <App>/Assets.xcassets/AppIcon.appiconset/AppIcon-Light.png
# expected: pixelWidth: 1024 / pixelHeight: 1024
# 5. Commit the SVG source-of-truth alongside, under docs/app-store/icons/.
mkdir -p docs/app-store/icons/<app>/
cp ../tmp/<app>-icon-light.svg docs/app-store/icons/<app>/light.svg
cp ../tmp/<app>-icon-dark.svg docs/app-store/icons/<app>/dark.svg
cp ../tmp/<app>-icon-tinted.svg docs/app-store/icons/<app>/tinted.svg
Open the produced PNGs in Preview.app and confirm:
#FAF8F3 background#15171A — replace with your project's dark background hex); pixel-meter the corners against it<filter> effects (rare with QuickLook, but inspect the highlight + spark ring areas)If any check fails, the fix lives in the SVG, not in a post-process pass — re-edit, re-rasterize.
AppIcon.appiconset/Contents.json must reference both PNGs as universal idiom (no "platform": "ios"), so Apple auto-adapts the 1024 master across iOS + macOS:
{
"images" : [
{
"filename" : "AppIcon-Light.png",
"idiom" : "universal",
"size" : "1024x1024"
},
{
"appearances" : [
{ "appearance" : "luminosity", "value" : "dark" }
],
"filename" : "AppIcon-Dark.png",
"idiom" : "universal",
"size" : "1024x1024"
}
],
"info" : { "author" : "xcode", "version" : 1 }
}
Tinted entry present — AppIcon-Tinted.png, appearance: luminosity / value: tinted, universal idiom. No AppIcon-macOS.appiconset/ (universal idiom covers macOS via auto-scale).
| Tool | Reason rejected |
|---|---|
rsvg-convert (librsvg) | Requires Homebrew, not installed on this Mac |
inkscape --export-png | Requires Homebrew or .app install |
imagemagick convert | Requires Homebrew |
sips | Does not read SVG input on macOS as of Sequoia |
cairosvg (Python) | Requires Homebrew (Cairo system lib) |
| Pillow | No SVG support natively |
| Swift + WebKit CLI | Works but ~80 LOC of bespoke code for a one-step thumbnail render that qlmanage already does |
| Browser screenshot | Manual, unreproducible across machines |
qlmanage ships with macOS, takes one command, outputs the exact PNG shape Apple wants. The trade-off is that QuickLook's SVG generator may diverge slightly from full-spec SVG 1.1 — keep the SVG simple (flat shapes, no filters, no text) and the output is faithful.
The macOS size-ladder downscale workflow is documented in the "macOS ladder reality check" section above; it is superseded by the universal single-PNG approach for new projects, but remains useful as a reference for older Xcode targets that still require the explicit size ladder.
npx claudepluginhub wei18/apple-dev-skills --plugin apple-dev-skillsProvides behavioral guidelines to reduce common LLM coding mistakes, focusing on simplicity, surgical changes, assumption surfacing, and verifiable success criteria.
Searches, retrieves, and installs Agent Skills from prompts.chat registry using MCP tools like search_skills and get_skill. Activates for finding skills, browsing catalogs, or extending Claude.