Before iOS 18, I have the following code that works well:
let icons = Bundle.main.infoDictionary!["CFBundleIcons"] as! [String: Any]
let primaryIcon = icons["CFBundlePrimaryIcon"] as! [String: Any]
let iconName = primaryIcon["CFBundleIconName"] as! String
return UIImage(named: iconName)!
The iconName
is "AppIcon"
, and it returns the 1024x1024 image. Now using iOS 18 base SDK, UIImage(named: iconName)
is nil, causing a crash in my app.
More details:
I have tried the answers from How to get UIImage of AppIcon?, but the result isn't as good. Specifically, the following code that uses CFBundleIconFiles
instead of CFBundleIconName
:
let icons = Bundle.main.infoDictionary!["CFBundleIcons"] as! [String: Any]
let primaryIcon = icons["CFBundlePrimaryIcon"] as! [String: Any]
let iconFiles = primaryIcon["CFBundleIconFiles"] as! [String]
return UIImage(named: iconFiles[0])!
The iconFiles[0]
returns AppIcon60x60
, which uses AppIcon60x60@2x.png
in main bundle which is 120x120, a lot lower than 1024x1024.
I have done the following research:
In my app setup, I use asset catelog (Assets.xcassets -> AppIcon
), with a image file called icon1024.png
that is 1024x1024.
In General
tab of the target setting, I set AppIcon
as the app icon.
in the resulting product (.app
file), I see 3 related files:
Then I use "Asset Catelog Tinker" app to inspect Assets.car
, I got a single file icon1024.png
.
I tried building with both iOS 17 and iOS 18, and got the same .app
folder structure.
This means that when building with base SDK iOS 17, when calling UIImage(named: "AppIcon")
, it somehow magically loads Assets.car/icon1024.png
file. However, if I do UIImage(named: "icon1024")
, or UIImage(named: "icon1024.png")
, or UIImage(named: "Assets.car/icon1024.png")
or UIImage(named: "Assets.car/icon1024.png")
, it returns nil.
I don't know why I worked before (anyone knows?), but this doesn't work anymore when building for iOS 18. So what should I do?
That was admittedly an interesting challenge.
Indeed, app icons seem to be packaged in the car
file with no way to access them in runtime.
It also appears that we have only some small sized freestanding icons in the bundle... but
Looking through the compiler options I've found this setting:
ASSETCATALOG_COMPILER_STANDALONE_ICON_BEHAVIOR
which can be found in:
Build Settings > Asset Catalog Compiler - Options > Standalone Icon File Behavior
and it seems to enable pretty much what we want:
Controls whether loose PNG or ICNS files are created for the primary app icon, in addition to including the content in the Assets.car file. By default, a small subset of sizes are included as loose files, allowing external management tools to display a representative icon without reading the CAR file. This can be set to 'all' or 'none' to include more or fewer icon sizes as loose files.
So after setting this to all
we are golden but keep in mind that it only seems to work (I did all the testing) if you include at least one scheme-specific variant (for lack of a better term).
For example, if you only set the Any Appearance
slot it doesn't include a copy of the 1024 icon.
So you want something like this:
and now finally we get the glorious 1024 icon freestanding in the bundle:
which of course you can load either with your existing code (since the icons are now included in CFBundleIcons/CFBundleIconFiles
) or via UIImage(named: "AppIcon1024x1024")
directly or by using any other Bundle
related API.
Oh, by the way there is one more flag (Include All App Icon Assets
- ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS
) that exposes the alternative icons (i.e. icon assets that are not the primary one) as well.