apple-app-icon — the icon as a build artefact, not a drawing
On iOS 26 an app icon is no longer a flattened PNG: it is a small bundle of layers the system lights, tints and renders as Liquid Glass. Icon Composer draws them by hand; this skill generates the same document from the brand artwork, so the icon is rebuilt when the brand changes rather than redrawn from memory.
Reach for it when the icon and the app disagree — different mark, different gradient, or nine PNGs whose source nobody can find.
Workflow
1. Find the real artwork before drawing anything
Two attempts went the wrong way on one project before the file turned up: rebuilding the mark from the app's own button geometry (beautiful, and a different mark), then tracing it off a 1024 PNG by measuring pixels (the right mark, at second-hand accuracy).
find ~ -maxdepth 7 \( -iname "*logo*" -o -iname "*icon*" \) \
\( -iname "*.svg" -o -iname "*.ai" -o -iname "*.sketch" \) 2>/dev/null
mdfind -name "logo" # useless if Spotlight is off — check `mdutil -s /`
Look in abandoned web projects and old zips; a Figma export usually survives
somewhere even when the designer's archive link is dead. What you want is one
artboard, already composed: the mark on its tile, with the background
gradient in a <linearGradient>. That file is the source of truth — copy it
into the repo (Resources/logo-source.svg) so the icon has a provenance.
2. Generate
python3 "${SKILL_DIR}/scripts/make_icon.py" \
--source Resources/logo-source.svg \
--icon Resources/App.icon --render
It reads the stops, the viewBox and the paths out of the artwork, splits the
paths into layers, writes icon.json, and exports all six appearances through
ictool. Nothing is retyped — a hex copied into a script is a second source of
truth that drifts on the next export.
3. Look at all six, not at one
Default · Dark · TintedLight · TintedDark · ClearLight · ClearDark
The dark and tinted appearances are where a mark falls apart: the system re-colours the layers, and thin white lines with dark gaps — the shape of most marks — are exactly what a translucent treatment fills in. Rendering is three seconds; guessing costs a release.
4. Then see it among other icons
xcrun simctl install <UDID> path/to/App.app
xcrun simctl io <UDID> screenshot home.png # after `axe button home`
A tile that looks confident at 512 points can be mush at 60 next to Photos and Safari. That is the only test that matters.
Wiring it into the project
# project.yml (XcodeGen)
sources:
- path: Resources/App.icon
settings:
base:
ASSETCATALOG_COMPILER_APPICON_NAME: App # the .icon's name, no extension
Keep the old AppIcon.appiconset beside it while the deployment target is
below 26 — older systems fall back to it.
Gotchas
Each of these cost a render, and none is documented — the full set, with the
measurements, is in references/icon-json.md.
- Icon Composer fills contours and ignores strokes.
<circle fill="none" stroke="…">arrives as a filled disc: the first build of one icon came out as a white blob. Hand it outlines — a ring is an annulus withevenodd, an arc is a band with rounded ends. - Groups are drawn front to back. The background written first covers everything; it goes last.
fillbelongs to the layer. On a group it is accepted and silently ignored —ictoolreturns 0 either way.fill-specializationsdoes nothing from JSON (Icon Composer 1.6). On a group it parses and changes nothing; on a layer the document stops loading. Per-appearance colours are a thing the GUI writes — if dark or monochrome must differ, open the.iconand set them there, then stop regenerating it.- A colour is
display-p3:r,g,b,a, all four components. Three fail with "Expected four comma separated color components"; a bare hex with "missing ':' delimiter", which names the delimiter and not the prefix. linear-gradienttakes exactly two colours. A three-stop brand gradient has to be drawn as a background layer — at the cost that the system can no longer dim it for dark. Two colours it can dim; three it cannot touch. Pick which matters more and say so in a comment.- The artboard is already composed. Scaling it to "72% of the canvas, so it breathes" shrinks the mark twice — the designer's margin plus yours. Scale 1:1; use ~0.9 only if watchOS is claimed, because a circular mask eats the corners of a square's margin.
- Do not claim platforms you do not ship.
supported-platformswithcircles: [watchOS]puts a round preview in front of you that no user will ever see, and tempts you into re-composing the icon for it.
Don't
- Don't redraw the mark from the app's own code. The button inside the app and the icon on the home screen are two different objects; making the icon "match the code" produces a mark nobody recognises.
- Don't trace a PNG when a vector exists. Measuring pixels gets the radii right and the end caps wrong.
- Don't ship without looking at Dark. It is the appearance most likely to be ugly and the one nobody checks.
- Don't leave the probes in the repo. Scripts written to interrogate the
format belong in the commit message or in
references/, not inscripts/.