tooldura

Image & Media

What iOS and Android Actually Do With Your App Icon

T
tooldura editorial
10 min readUpdated August 23, 2026Open tool →

Both stores ask for a square image, which is where the similarity ends. Apple takes one picture and does the rest itself. Android takes two layers, discards a third of each, and lets the launcher decide what shape the result is. An icon exported once and resized for both platforms will be wrong on at least one of them, and usually in a way nobody notices until it is on a phone.

iOS Stopped Wanting a Ladder of Sizes

For a decade an iOS project carried seventeen images: 40, 58, 60, 80, 87, 120, 152, 167, 180 and the rest, each one slotted into AppIcon.appiconset by device and scale factor. Xcode 14 ended that. A project built with it can declare a single 1024-pixel universal image, and the asset catalog compiler produces every smaller size during the build.

The difference lives in Contents.json. A single-size catalog has one entry with "idiom": "universal" and "platform": "ios" and no scale key at all. A legacy catalog has one entry per device and scale, each naming its own file. They are not interchangeable: dropping a single-size Contents.json into a project whose entries are still per-device leaves Xcode looking for images that are no longer listed, and the build fails on a missing app icon rather than falling back.

So the first thing to check before generating anything is which of the two your project has. Open ios/YourApp/Images.xcassets/AppIcon.appiconset and count the files. One image means single-size; a folder full of them means the ladder, and you either keep it or migrate the whole thing deliberately.

🚫

The alpha channel rejection, and why it catches web tools

App Store Connect returns ITMS-90717 when the app icon has an alpha channel, and it means the channel itself, not visible transparency. A fully opaque RGBA PNG is rejected the same as one with a hole in it. This catches almost every browser-based generator, because canvas.toBlob writes RGBA by default — the fix is to create the drawing context with { alpha: false }, which makes the encoder emit a 24-bit RGB PNG instead.

Android Icons Are Two Layers and a Crop

Android 8 replaced the flat launcher icon with the adaptive icon, and the model is genuinely different rather than a new size. You supply a background and a foreground, each a square drawable 108dp on a side. The launcher supplies the mask — a circle on stock Pixel, a squircle on Samsung, a rounded square elsewhere — and applies it to a 72dp viewport in the middle of your layers.

That leaves 18dp of your artwork outside the visible area on every side, and it is not padding you get to use. It is the room the system moves the layers through: on a home screen scroll the two layers shift against each other by a few dp to produce a parallax effect, and when you open the app they scale up out of the mask. Anything drawn in that border exists only to fill the gap during those animations.

Google's guidance narrows it further. The 72dp viewport is a square, but a circular mask inscribed in it cuts the corners off, so the region guaranteed to survive every mask is a circle 66dp across. In practice that means the foreground artwork should span roughly two thirds of the 108dp layer, not fill it.

The Five Density Buckets

Every mipmap folder holds the same icon at the scale its density needs. The adaptive layer is 2.25 times the legacy icon because 108dp is 2.25 times 48dp.

FolderLegacy pxAdaptive layerScreen density
mipmap-mdpi48108 × 108160 dpi, the baseline nothing ships at any more
mipmap-hdpi72162 × 162240 dpi
mipmap-xhdpi96216 × 216320 dpi
mipmap-xxhdpi144324 × 324480 dpi, most current phones
mipmap-xxxhdpi192432 × 432640 dpi, flagship displays

The Three Files That Are Not Images

An adaptive icon needs a declaration, and it lives in res/mipmap-anydpi-v26/ic_launcher.xml. The -v26 suffix is the whole trick: Android 8 is API 26, so devices from that version up read the XML and older ones never see it, falling through to the PNG of the same name in the density folders. One project, two icon systems, no code.

The XML itself is three lines — a background and a foreground drawable, and since Android 13 an optional monochrome one. The background is almost always a colour rather than an image, declared as @color/ic_launcher_background and defined in res/values/ic_launcher_background.xml. Android Studio's own asset tool writes it to that separate file rather than into colors.xml, which is worth matching so a regenerated icon does not collide with your theme colours.

There is a second XML, ic_launcher_round.xml, with identical contents. It exists because android:roundIcon was added in Android 7.1, one version before adaptive icons, for launchers that wanted a circular icon and had no way to mask a square one. On any device new enough to read the adaptive XML the two resolve to the same thing, and it stays in the project for the year of devices between the two releases.

The Themed Icon Nobody Can Generate For You

Android 13 added themed icons: the launcher takes a monochrome layer from your adaptive icon and recolours it to match the wallpaper. Where an app supplies one, the home screen looks coherent; where it does not, that app keeps its full-colour icon and stands out as the one that was not updated.

The layer is a silhouette. The system ignores the colours in it entirely and paints the shape in whatever tone the theme calls for, which means the only information it carries is the alpha channel. That is also why it cannot be derived from an arbitrary logo: if your source is a JPEG, a screenshot, or a PNG with a filled background, its alpha channel is a solid rectangle, and a solid rectangle is what the launcher will draw.

So a monochrome layer is worth generating from a transparent SVG or PNG, where the silhouette is the mark itself, and worth skipping otherwise. Skipping is safe: Android falls back to the normal adaptive icon, which looks like every app that has not adopted themed icons yet.

Generate both sets from one logo

The Xcode asset catalog, five mipmap densities, adaptive-icon XML, iOS 18 dark and tinted variants — laid out at the paths a React Native project already uses.

Open the App Icon Generator →

iOS 18 Made One Icon Into Three

iOS 18 added dark and tinted appearances of the app icon, keyed off the home screen's own setting rather than the system theme. An app that supplies only the default icon still works — iOS derives something automatically — but the derived version is a guess, and it shows on artwork with a light background, which comes back as a bright tile on a dark home screen.

Both variants attach to the asset catalog as appearance-keyed entries: an appearances array with "appearance": "luminosity" and a value of dark or tinted, alongside the default entry for the same 1024 size. Because they hang off the universal single-size entry, they are only available in a single-size catalog. There is no way to attach an appearance to a per-device ladder.

The two variants have different rules. A dark icon may keep transparency, since the system composites it over its own dark surface, and Apple's advice is to darken the background rather than the mark. A tinted icon must be greyscale: iOS applies the colour, so anything left in the artwork fights the tint it is given.

The Six Ways App Icons Go Wrong

None of these is a rendering bug, and every one of them survives review to reach a phone.

1

The same image is used for both platforms

An iOS icon fills its square, because Apple masks it. Fed to Android as an adaptive foreground, that same full-bleed square loses its outer third to the mask. The two need different framing, which is why a good generator gives them separate controls rather than one padding slider.

2

The corners are already rounded

Baking a squircle into the file gives iOS a rounded shape to mask a second time, and the result is a thin dark halo where the two curves disagree. Ship a hard square and let the system round it. The same applies to the Play Store listing image, which Google masks itself.

3

The icon has an alpha channel

Rejected by App Store Connect before review, with an error that names the file but not the cause. Any tool that exports through an HTML canvas produces this unless it explicitly disables alpha, which is why the same logo can pass from Photoshop and fail from a web generator.

4

The adaptive background is left transparent

A missing background layer is not a transparent icon, it is an undefined one, and launchers differ on what they paint behind it. Declare a colour resource even when the foreground covers most of the tile, because the parallax animation exposes the edges of the background during a scroll.

5

Only some density folders were updated

Android resolves mipmaps per device, so replacing xxhdpi and forgetting mdpi leaves half your install base on the old icon with no error anywhere. Whatever generates the set has to write all five, and whatever copies it has to overwrite all five.

6

The app was reloaded instead of rebuilt

Icons live in the native bundle, so Fast Refresh and a Metro reload cannot change one. Rebuild through Xcode or Gradle, and on Android uninstall first if the launcher is still showing the old artwork, since launcher icon caches survive a reinstall more often than they should.

Expo Collapses All of This Into Three Files

An Expo project does not carry native folders unless you have run prebuild, so there is nowhere to put an asset catalog. Instead app.json names a handful of images and the build generates the platform sets from them.

The fields worth filling are icon, which is the 1024 square used for iOS and the stores, and android.adaptiveIcon, which takes a foregroundImage and a backgroundColor — the same two layers as a bare project, expressed as JSON. From SDK 52, ios.icon can be an object with light, dark and tinted keys instead of a single path, which is how the iOS 18 appearances are declared without touching Xcode.

The images still have to be framed correctly. Expo resizes them, it does not recompose them, so a foreground image that fills its square is cropped by the launcher exactly as it would be in a bare project. The 66 percent rule applies unchanged.

Where Each File Belongs

Paths for a React Native CLI project. An Expo project uses assets/ and the app.json keys instead.

FileSizePathRead by
icon-1024.png1024ios/YourApp/Images.xcassets/AppIcon.appiconset/Xcode, which scales every other iOS size from it
Contents.jsonThe same folder, beside the imagesThe asset catalog compiler, at build time
ic_launcher.png48–192res/mipmap-{density}/Launchers older than Android 8
ic_launcher_foreground.png108–432res/mipmap-{density}/The adaptive icon, masked to its middle 72dp
ic_launcher.xmlres/mipmap-anydpi-v26/Android 8 and later, in place of the PNG
ic_launcher_background.xmlres/values/The colour behind the foreground layer
play-store-icon.png512Nowhere in the projectThe Play Console listing, uploaded by hand

Designing for a Tile Somebody Taps Without Looking

An app icon is seen at roughly 60 by 60 points on a phone, in a grid of twenty others, by someone who is not reading it so much as recognising it. That is a different problem from a logo on a website.

What works is a single shape and two or three colours. What fails is a wordmark, an outline, a gradient with subtle stops, and anything with a thin stroke — at 60 points a one-pixel line is below the threshold where anti-aliasing can keep it crisp, and it turns into grey haze. If your brand mark is a horizontal lockup, the icon is the symbol from it, not the lockup shrunk.

The second constraint is the mask you do not control. On Android your artwork will appear as a circle on one phone and a rounded square on another, so a composition anchored to the corners falls apart on half of them. Centre the mark, keep it inside the safe circle, and check it against all four masks before shipping rather than against the one your own phone uses.

Frequently Asked Questions

Related Tools

Keep Reading