BLACK FRIDAY SALE: Save big on all my Swift books and bundles! >>

SOLVED: Programmatically Changing Icon Unsuccessful Despite "Success" Prompt

Forums > iOS

I want my users to be able to choose an app icon from a list of options I'll provide. I'm running into an issue where, when I select an icon that is not the default, the system prompts me saying the change was successful, but the icon it changes to is the default blank placeholder one.

I followed this guide primarily, but have tried other guides with the same results. https://www.hackingwithswift.com/example-code/uikit/how-to-change-your-app-icon-dynamically-with-setalternateiconname

I also downloaded some code showing off this feature and compared it to what I have. Apart from the fact it's not Swift, all of the inner workings appear to be the same, only their's works and mine does. https://github.com/ivanhjel/change-appicon-programatically-ios

Here is an example of what I'm getting as a result. As you can see, the icon does not change successfully, but the system says it does. I assume this is because the keys I setup in the info.plist match, but it can't find the image? Example

I'm fairly new to Swift/SwiftUI and app programming in general, so I assume this is a beginners mistake, but I've been troubleshooting this all day without success, so at this point I'm happy to feel like a noob if someone can show me what I'm doing wrong.

Here is my info.plist:

<key>CFBundleIcons</key>
    <dict>
        <key>CFBundlePrimaryIcon</key>
        <dict>
            <key>CFBundleIconFiles</key>
            <array>
                <string>AppIconDefault</string>
            </array>
            <key>UIPrerenderedIcon</key>
            <false/>
        </dict>
        <key>CFBundleAlternateIcons</key>
        <dict>
            <key>AppIcon1</key>
            <dict>
                <key>CFBundleIconFiles</key>
                <array>
                    <string>AppIcon1</string>
                </array>
                <key>UIPrerenderedIcon</key>
                <false/>
            </dict>
            <key>AppIcon2</key>
            <dict>
                <key>CFBundleIconFiles</key>
                <array>
                    <string>AppIcon2</string>
                </array>
                <key>UIPrerenderedIcon</key>
                <false/>
            </dict>
        </dict>
    </dict>

Here is my ContentView

import SwiftUI

struct ContentView: View {
    let iconsList: [String : String] = ["AppIcon1":"Icon 1", "AppIcon2":"Icon 2"]
    let defaultAppIcon = "AppIconDefault"

    var body: some View {
        VStack {
            Text("Choose An Icon")
                .font(.largeTitle).bold()

            List {
                Button {
                    changeIcon(defaultAppIcon)
                } label: {
                    HStack {
                        Image(defaultAppIcon)
                            .resizable()
                            .scaledToFit()
                            .frame(width: 75)
                            .clipShape(RoundedRectangle(cornerRadius: 15))

                        Spacer()

                        Text("Default Icon")
                            .bold()
                    }
                    .foregroundColor(.white)
                }

                ForEach(iconsList.sorted(by: <), id: \.key) { iconID, iconName in
                    Button {
                        changeIcon(iconID)
                    } label: {
                        HStack {
                            Image(iconID)
                                .resizable()
                                .scaledToFit()
                                .frame(width: 75)
                                .clipShape(RoundedRectangle(cornerRadius: 15))

                            Spacer()

                            Text(iconName)
                                .bold()
                        }
                        .foregroundColor(.white)
                    }
                }
            }
        }
    }

    func changeIcon(_ iconID: String) {
        print("Tapped icon \(iconID)")
        // Change icon
        if iconID != defaultAppIcon {
            UIApplication.shared.setAlternateIconName(iconID) { error in
                if let error = error {
                    print("ERROR: Could not set custom icon \(iconID). \(error)")
                    print(error.localizedDescription)
                }
            }
        } else {
            UIApplication.shared.setAlternateIconName(nil) { error in
                if let error = error {
                    print("ERROR: Could not set custom icon \(iconID). \(error)")
                    print(error.localizedDescription)
                }
            }
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
            .preferredColorScheme(.dark)
    }
}

Here is a photo of my files to show that I have the @x2 and @x3 files loose, as well as just some in the asset catalog for previewing in the list for the user.

Files

Finally, here is a video showing the app in use. Moving to the default icon works, because I'm setting the alternate icon property back to nil, but anytime I change it to a custom icon, it goes to the blank one instead. https://vimeo.com/manage/videos/704987687/privacy

Has anyone else run into this or is someone seeing some step I missed?

   

Take AppIcon1 and AppIcon2 out of the asset catalog. It looks like you have them in two places.

   

Thanks! I was doing that just to make the naming scheme easy, but I just tried changing those to "CustomIcon1" and "CustomIcon2" in the asset catalog, did a clean build, and rebooted the simulator, but I'm still getting the same results.

If I was getting an error at least I would know where to start looking. Does anyone know if there is a way to get some further debug info from the app icon change that might point me to where the disconnect is?

   

Hacking with Swift is sponsored by RevenueCat

SPONSORED In-app subscriptions are a pain to implement, hard to test, and full of edge cases. RevenueCat makes it straightforward and reliable so you can get back to building your app. Oh, and it's free if your app makes less than $10k/mo.

Learn more

Sponsor Hacking with Swift and reach the world's largest Swift community!

As @roosterboy said, take the Icons out of the Asset catalog. Your alternate icons cannot be in a Asset catalog. Place them loosely in your project as you've done. I have done this in my app and it works just fine as long as all the icons are in the project and not an asset catalog.

The only icon I have in my asset cataglog is the main icon but I also have it in a folder called Icons.

   

I've removed the icons from the Asset catalog so that the only references to images are those placed loosely in the project. I still seem to be getting the same results. Am I misunderstanding the solution?

I'll post my new code (excuse the mess, it's just a concept) and a screenshot of the project folder in case that helps.

import SwiftUI

struct ContentView: View {
    let iconsList: [String : String] = ["AppIcon1":"Icon1", "AppIcon2":"Icon2"]
    let defaultAppIcon = "AppIconDefault"
    let defaultIconName = "CustomIconDefault"

    var body: some View {
        VStack {
            Text("Choose An Icon")
                .font(.largeTitle).bold()

            List {
                Button {
                    changeIcon(defaultAppIcon)
                } label: {
                        Text("Default Icon")
                            .bold()
                            .foregroundColor(.white)
                }

                ForEach(iconsList.sorted(by: <), id: \.key) { iconID, iconName in
                    Button {
                        changeIcon(iconID)
                    } label: {
                        Text(iconName)
                            .bold()
                            .foregroundColor(.white)
                    }
                }
            }
        }
    }

    func changeIcon(_ iconID: String) {
        print("Tapped icon \(iconID)")
        // Change icon
        if iconID != defaultAppIcon {
            UIApplication.shared.setAlternateIconName(iconID) { error in
                if let error = error {
                    print("ERROR: Could not set custom icon \(iconID). \(error)")
                    print(error.localizedDescription)
                }
            }
        } else {
            UIApplication.shared.setAlternateIconName(nil) { error in
                if let error = error {
                    print("ERROR: Could not set custom icon \(iconID). \(error)")
                    print(error.localizedDescription)
                }
            }
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
            .preferredColorScheme(.dark)
    }
}

Info.plist

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <!-- CODE ADDED MANUALLY -->
    <key>CFBundleIcons</key>
    <dict>
        <key>CFBundlePrimaryIcon</key>
        <dict>
            <key>CFBundleIconFiles</key>
            <array>
                <string>AppIconDefault</string>
            </array>
            <key>UIPrerenderedIcon</key>
            <false/>
        </dict>
        <key>CFBundleAlternateIcons</key>
        <dict>
            <key>AppIcon1</key>
            <dict>
                <key>CFBundleIconFiles</key>
                <array>
                    <string>Icon1</string>
                </array>
                <key>UIPrerenderedIcon</key>
                <false/>
            </dict>
            <key>AppIcon2</key>
            <dict>
                <key>CFBundleIconFiles</key>
                <array>
                    <string>Icon2</string>
                </array>
                <key>UIPrerenderedIcon</key>
                <false/>
            </dict>
        </dict>
    </dict>
</dict>
</plist>

Current project folder

Project_Folder

If anyone wants the project files they're HERE

   

I have to admit that changing an app's icon isn't something I've ever done myself, but...

I notice in the example project you linked to in your original post that that AppIcons folder contains:

Icon1.png
Icon1@2x.png
Icon1@3x.png
...

But you only have:

Icon1@2x.png
Icon1@3x.png
...

So perhaps try adding an Icon1.png (with 60x60 dimensions)?

   

I appreciate the ideas! I put in a 60x60 version of Icon1 and Icon2, but with the same results unfortunately. Here's a view of the updated icons just to show they are PNGs and that the new ones are 60x60.

Icon-Types

I can't figure out how I messed this up with only about 3 steps.

   

Have you done a check for supportsAlternateIcons. I cannot see it your code.

if UIApplication.shared.supportsAlternateIcons {
    // let the user choose a new icon
}

Like @roosterboy I have not done this, but looking at Paul article it seem that this is need and might give you reason it not working by putting

else {
print("not supported")
}

   

Try this first (this is for Xcode 13.x).

In the compiler options, select your target and under the General tab find the section App Icons and Launch Images and enable Include all app Icon assets. This will add all the icon assets to the build. If you want to add only some icon assets, make sure that this option is unchecked and use the Alternate App Icon Sets build option instead, specifying the icons to be included.

Not sure if this is entirely required, but it is the way I have done it - creating appiconsets, to keep the icon groupings tidy. In your Icons group, create an appiconset for Icon1 and Icon2 and put the corresponding icon files (alternate, @2x, @3x) in each appiconset.

If you are going to sell the app at some point in the future, to meet Apple's requirements, make sure that the alternative, @2x and @3x versions of the icons are 60x60px, 120x120px and 180x180px respectively (this for the iPhone - not sure if other sizes are needed for other devices such as a the iPad, however I guess the answer will be yes, because the the default size of the app icon for iPads is different).

if UIApplication.shared.supportsAlternateIcons {

is only needed if your app runs on older OSes, for backwards compatibility, and therefore app needs to know if the alternate icons are supported. I built my app for iOS 15+, didn't use this check, and it was accepted in to AppStore.

   

@NigelGee:

Thanks! I had that in my original project, but not the simplified version I made for this post. I added it in without any change to the results, but good thought!

@Greenamberred:

That's interesting you bring that setting up. I saw something similar in this guide: https://iosexample.com/simple-alternate-app-icons-with-xcode-13-and-swiftui/

I enabled Include all app Icon assets and tested it, but I got the same results. I then created two new Iconsets in the Asset catalogue and tested it again, but again, it didn't work. I am not sure if I'm doing that right thought. Do you have your Iconsets loose in your project? If so, I'm not sure I have figured out how to do that.

If you have only done those additional steps compared to the guide by Paul, and your Iconsets are in the asset catalogue, I must have missed a step. I can start back through the steps and see if anything is set wrong and update this post.

   

Just to make sure, did you include the alternate icons in your target?

   

The alternate icons are in a group called Icons, with a sub-group of appiconset for each type of alternate icons set.

The Icons group is not in Assets.xcassets where the main appicon is, so the alternate icons are 'loose' in the project.

@roosterboy raises a good point that the alternate icons target membership should be set.

   

The only thing I can suggest is to put the project on GitHub and then people can clone it and debug it.

   

I verified that the icons are set to target the project, but man would I have felt silly if that was the cause of all this.

As suggested, I uploaded the file to GitHub so you don't have to troubleshoot-by-proxy. https://github.com/RapidWolf95/ChangeMyIcon

   

There is nothing wrong with the code on GitHub, so have you tried reseting (removing all contents) on the simulator, or used another simulator that has not been used with this app?

BTW - foregroundColor being white is hard to read on a white background, so might be better to change it to .primary.

   

Gif

Oh my goodness, you're right. I deleted the app on my simulator, re-installed it, and it works perfectly! I can't believe I've been troubleshooting working code for the last three days!

(Also, I will change that to .primary, I always just used dark mode since it was just a test app, and never noticed. Good thought!)

Thank you everyone for your help!

   

Hacking with Swift is sponsored by RevenueCat

SPONSORED In-app subscriptions are a pain to implement, hard to test, and full of edge cases. RevenueCat makes it straightforward and reliable so you can get back to building your app. Oh, and it's free if your app makes less than $10k/mo.

Learn more

Sponsor Hacking with Swift and reach the world's largest Swift community!

Reply to this topic…

You need to create an account or log in to reply.

All interactions here are governed by our code of conduct.

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.