How to incorporate app icon files for iOS into Xcode 14?

I have previously published my Qt app using Qt 5 + qmake.
I am now ready to release a new version using Qt 6 + CMake. (I am using Qt 6.4.2 and CMake 3.24.2)

However I am struggling to get app icons into Xcode 14.2 in a form that it will accept. Here are a couple of close examples.

In both cases, I have the following file structure for the app icons:

project-root
|- Images.xcassets
   |- Contents.json
   |- AppIcon.appiconset
      |- Contents.json
      |- AppIcon~iOS-Marketing-1024@1x.png
      |- AppIcon~iPad-20@1x.png
      |- AppIcon~iPad-20@2x.png
      |- AppIcon~iPad-29@1x.png
      |- AppIcon~iPad-29@2x.png
      ...
      |- AppIcon~iPhone-60@2x.png
      |- AppIcon~iPhone-60@3x.png

and both examples use the following CMake code

set_target_properties(MyApp PROPERTIES
    MACOSX_BUNDLE TRUE
    MACOSX_BUNDLE_GUI_IDENTIFIER org.myorg.myapp
    MACOSX_BUNDLE_BUNDLE_VERSION ${PROJECT_VERSION}
    MACOSX_BUNDLE_SHORT_VERSION_STRING ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}
    MACOSX_BUNDLE_INFO_PLIST "${CMAKE_CURRENT_SOURCE_DIR}/Info.plist.in"
    QT_IOS_LAUNCH_SCREEN "${CMAKE_CURRENT_SOURCE_DIR}/LaunchScreen.storyboard"
)

... code to include icons goes here ...

target_link_libraries(MyApp
    PRIVATE Qt6::Quick)

install(TARGETS MyApp
    BUNDLE DESTINATION .
    LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR})

In both examples, the app runs fine on my iPad and it can be archived, but the archive fails validation with the same errors:

Example 1
Here I include the icon image files directly (equivalent to how I did it with Qt 5 and Xcode 11(?)).

CMakeLists.txt

file(GLOB app_icon_files CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/Images.xcassets/AppIcon.appiconset/*.png")
if (app_icon_files)
    target_sources(MyApp PRIVATE ${app_icon_files})
    set_source_files_properties(${app_icon_files} PROPERTIES
        MACOSX_PACKAGE_LOCATION Resources
    )
else()
    message(WARNING "iOS app icon files not found")
endif()

Info.plist.in

    <key>CFBundleIconName</key>
    <string>AppIcon</string>
    <key>CFBundleIcons</key>
    <dict>
        <key>CFBundlePrimaryIcon</key>
        <dict>
            <key>CFBundleIconFiles</key>
            <array>
            <string>AppIcon~iPhone-20.png</string>
            <string>AppIcon~iPhone-29.png</string>
            <string>AppIcon~iPhone-40.png</string>
            <string>AppIcon~iPhone-57.png</string>
            <string>AppIcon~iPhone-60.png</string>
            <string>AppIcon~iPad-20.png</string>
            <string>AppIcon~iPad-29.png</string>
            <string>AppIcon~iPad-40.png</string>
            <string>AppIcon~iPad-76.png</string>
            <string>AppIcon~iPad-83.5.png</string>
            <string>AppIcon~iOS-Marketing-1024.png</string>
            </array>
        </dict>
    </dict>

Results

  • I can see the icon images individually listed in the project Resources folder.
  • Xcode doesn’t show the icon against the project or target.
  • However the app icon correctly appears on my iPad when I do a build and run in Xcode.
  • When I build an archive and run ‘Validate app’, the icon shows up on the validation popup on the “Review MyApp.ipa content” screen just before clicking ‘Validate’ even though it didn’t show up against the archive in the list of archives.

Example 2
Here I include the image files as an asset catalog. I used the CMake code snippet below suggested by @craig.scott that he acknowledges is not officially supported by CMake to achieve this.

CMakeLists.txt

target_sources(MyApp PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/Images.xcassets")
set_source_files_properties("${CMAKE_CURRENT_SOURCE_DIR}/Images.xcassets"
    PROPERTIES MACOSX_PACKAGE_LOCATION Resources
)

Info.plist.in

...
    <key>CFBundleIconName</key>
    <string>Images</string>
    <key>CFBundleIcons</key>
    <dict>
        <key>CFBundlePrimaryIcon</key>
        <dict>
            <key>CFBundleIconFiles</key>
            <array>
            	<string>Images</string>
            </array>
        </dict>
    </dict>
...

Results

  • I can see the Images asset catalog in the project Resources folder. Clicking it shows the name AppIcon with the correct icon and a sheet of the icon at all different sizes, as it should be.
  • Xcode doesn’t show the icon against the project or target.
  • The app icon does not appear on my iPad when I do a build and run in Xcode.
  • The icon does not appear anywhere in the archive or validation steps.

NB: changing the value for CFBundleIconName to “AppIcon” or removing this key entirely makes no difference.

It’s been a very long journey on this but I’ve finally got the answer.

Step 1: Create your app icon set.

My way of doing this has been to go into Xcode, go File > New > Asset Catalog. Put it all together with all the icons. Then go find this in Finder and copy the whole asset catalog folder into my project repo. This contains the right structure and also saves you writing the Content.json files.

Note that as of Xcode 14, for iOS you can have a single 1024x1024 image if you’re happy for Xcode to do all the resizing. See online for how to do this. This still generates a structure that can be used as before.

Let’s say your folder structure is: MyAppIcons.xcasset containing AppIcon.appiconset. This means your asset catalog is called MyAppIcons and your app icon set is called AppIcon. This distinction is crucial for getting this to work right.

Step 2: Put the asset catalog name into Info.plist.in

...
    <key>CFBundleIcons</key>
    <dict>
        <key>CFBundlePrimaryIcon</key>
        <dict>
            <key>CFBundleIconFiles</key>
            <array>
            <string>MyAppIcons</string>
            </array>
        </dict>
    </dict>
...

Note the name of the asset catalog goes in there as the only array element and you don’t include the file extension.

You also don’t need to have the CFBundleIconName key (despite the wording of the validation error I was getting). Xcode gets everything it needs from the asset catalog instead.

Step 3: In CMake, copy the asset catalog into the Resources folder

Unlike what @craig.scott suggested, it’s not sufficient to just include the asset catalog folder. You have to include all the folders and files in the structure and you have to preserve the folder structure.

# Asset catalog root
target_sources(MyApp PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/MyAppIcons.xcassets")
set_source_files_properties("${CMAKE_CURRENT_SOURCE_DIR}/MyAppIcons.xcassets" PROPERTIES
    MACOSX_PACKAGE_LOCATION Resources
)

# Asset catalog app icon set
list(APPEND app_icon_set "${CMAKE_CURRENT_SOURCE_DIR}/MyAppIcons.xcassets/AppIcon.appiconset")
list(APPEND app_icon_set "${CMAKE_CURRENT_SOURCE_DIR}/MyAppIcons.xcassets/Contents.json")
set_source_files_properties(${app_icon_set} PROPERTIES
    MACOSX_PACKAGE_LOCATION Resources/MyAppIcons.xcassets
)

# Asset catalog icon files
file(GLOB app_icon_files CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/MyAppIcons.xcassets/AppIcon.appiconset/*.png")
list(APPEND app_icon_files "${CMAKE_CURRENT_SOURCE_DIR}/MyAppIcons.xcassets/AppIcon.appiconset/Contents.json")
set_source_files_properties(${app_icon_set} PROPERTIES
    MACOSX_PACKAGE_LOCATION Resources/MyAppIcons.xcassets/AppIcon.appiconset
)

Step 4: Manually set the App Icon field

Run CMake to generate the Xcode project and open that in Xcode.

You can verify the following:

  • In your folder structure you’ll see Root folder > MyApp > Resources > MyAppIcons. Click this and you’ll see the AppIcon icon set.
  • In your folder structure you’ll see Root folder > MyApp > Resources > Info. Click this and you’ll see the contents of your generated Info.plist. You should see Icon files (iOS 5) > Primary icon > Icon files > Item 0: MyAppIcons

Now you have to do a manual step in Xcode that unfortunately CMake can’t do AFAIK:

  • Select the project root folder.
  • In the main panel, select your MyApp target. It’ll have a generic app icon to its left.
  • Select the General tab
  • Under App Icons and Launch Screen, set App Icon to AppIcon. This is what tells Xcode the name of your app icon within the asset catalog you’ve already identified in the Info.plist.

You should now find that if you build and run the app on a device, the app icon is present.

Step 5: Create a release build before you archive (if using CMake < 3.25)

There’s an issue with CMake 3.24 and below that means archives don’t get generated properly. Here’s the workaround:

  • In Xcode, go to Product > Scheme > Edit Scheme…
  • Select MyApp target (top left item of main white panel, even though it doesn’t look selectable)
  • Select Run on the left
  • Change Build Configuration to Release. (I uncheck Debug executable too but I don’t know if this matters.)
  • While you’re there confirm that the left pane now says Run (Release) and Archive (Release)
  • Close the panel
  • Go to Product > Build For > Running. This generates the necessary files both for running and for archive.

Now you can go Product > Archive and, assuming you’re code signing is properly set up, you should be able to Validate app successfully.

Phew! :sweat_smile: