iosuikit

Get high res icon image as UIImage in iOS 18


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:

  1. In my app setup, I use asset catelog (Assets.xcassets -> AppIcon), with a image file called icon1024.png that is 1024x1024.

  2. In General tab of the target setting, I set AppIcon as the app icon.

  3. 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?


Solution

  • 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.

    enter image description here

    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:

    enter image description here

    and now finally we get the glorious 1024 icon freestanding in the bundle:

    enter image description here

    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.