Adding alternate app icons to your app allows users to customize their home screen with an app icon that fits their style. An alternative icon could be a dark or light-mode version of the original icon or a collection of completely different styles.
iOS 10.3 was the first version to support alternative icons. In the early days, you had to do a lot of manual work to configure all app icons manually in your info.plist
file. Xcode provides better configuration options these days, which I’ll demonstrate in this article.
Configuring an alternative App Icon
Configuring an alternative App Icon starts by adding another icon set to your assets catalog. You could either add a new set or duplicate an existing one, after which you can configure the correct images.
In the following image, you can see that I’ve configured both a light and dark mode app icon:
Note that I purposely selected the preview image set. I’ve added this to be able to show a preview in the app later, allowing the user to choose an alternate app icon. To simplify our logic later, I’ve just added a -Preview
postfix to the original app icon name.
Adjusting Xcode’s build settings for alternative icons
Once we’ve configured our image assets, we can start configuring Xcode’s build settings. We need to tell Xcode about our alternative app icon sets to ensure our app recognizes them at runtime.
There are two options for us to configure inside the build settings:
The first setting allows us to configure alternate app icon sets specifically. I recommend using this setting to ensure you’re opting in to add an app icon set to your built product. The second setting allows you to include all app icon assets as alternative app icons, but that could lead to you including unused app icon sets that you no longer use.
Another reason to configure sets manually relates to the code you need to update to make the icon set available inside the UI. You’ll ensure you’ve correctly configured all icon sets by matching the build settings list of icon sets with your code.
Changing the App Icon in Swift
Now that we’ve configured alternate app icons in our build settings, we can start writing the code needed to change the app icon from within the UI.
We start by creating an enum that will reflect all available app icon sets:
enum AppIcon: String, CaseIterable, Identifiable {
case primary = "AppIcon"
case darkMode = "AppIcon-Dark"
var id: String { rawValue }
var iconName: String? {
switch self {
case .primary:
/// `nil` is used to reset the app icon back to its primary icon.
return nil
default:
return rawValue
}
}
var description: String {
switch self {
case .primary:
return "Default"
case .darkMode:
return "Dark mode"
}
}
var preview: UIImage {
UIImage(named: rawValue + "-Preview") ?? UIImage()
}
}
It’s essential to create an enum case for each icon set specifically. It would be possible to automatically detect any icon set using the CFBundleAlternateIcons
key to fetch all icon sets from your info.plist
. However, I want to configure a specific description per icon, which is easier to manage by manually maintaining the enum.
Note that we’ve created a computed property for fetching the preview image as defined earlier in our asset catalog. We’ve also added an icon name property which will return the icon name for changing the app icon later.
Creating a view model using ObservableObject
The next step requires us to create a view model conforming to ObservableObject
, allowing us to read out the currently selected icon:
final class ChangeAppIconViewModel: ObservableObject {
@Published private(set) var selectedAppIcon: AppIcon
init() {
if let iconName = UIApplication.shared.alternateIconName, let appIcon = AppIcon(rawValue: iconName) {
selectedAppIcon = appIcon
} else {
selectedAppIcon = .primary
}
}
func updateAppIcon(to icon: AppIcon) {
/// ...
}
}
I encourage you to read my article @StateObject vs. @ObservedObject: The differences explained if you’re new to observable objects.
By using the alternateIconName
property of our shared application, we can read out the currently configured alternate app icon. Our enum’s raw value should match the app icon’s name as configured in our asset catalog.
The view model also allows us to change the app icon, for which the implementation looks as follows:
func updateAppIcon(to icon: AppIcon) {
let previousAppIcon = selectedAppIcon
selectedAppIcon = icon
Task { @MainActor in
guard UIApplication.shared.alternateIconName != icon.iconName else {
/// No need to update since we're already using this icon.
return
}
do {
try await UIApplication.shared.setAlternateIconName(icon.iconName)
} catch {
/// We're only logging the error here and not actively handling the app icon failure
/// since it's very unlikely to fail.
print("Updating icon to \(String(describing: icon.iconName)) failed.")
/// Restore previous app icon
selectedAppIcon = previousAppIcon
}
}
}
We start by storing the currently configured app icon to ensure we can restore the selected icon property on failure. Inside our task, we guarantee not to update the icon if it’s already matching the currently configured icon. Lastly, we’re making use of the setAlternateIconName
method to alternate the icon. Using nil
for the icon name will reset the app icon back to its default app icon.
The alternate icon configuration method will automatically trigger an alert to tell the user about the change:
The localized alert is system-managed you can’t change it.
Creating a SwiftUI view to allow users to change the icon
The last step in our implementation would be to show a view that allows changing the alternate app icon. Each view is specific to your app, so you’ll likely have to change some parts to make it align with the rest of your views. Here’s the code for our view to give you a kickstart:
struct ChangeAppIconView: View {
@StateObject var viewModel = ChangeAppIconViewModel()
var body: some View {
VStack {
ScrollView {
VStack(spacing: 11) {
ForEach(ChangeAppIconViewModel.AppIcon.allCases) { appIcon in
HStack(spacing: 16) {
Image(uiImage: appIcon.preview)
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 60, height: 60)
.cornerRadius(12)
Text(appIcon.description)
.font(.body17Medium)
Spacer()
CheckboxView(isSelected: viewModel.selectedAppIcon == appIcon)
}
.padding(EdgeInsets(top: 14, leading: 16, bottom: 14, trailing: 16))
.background(Color.grey7)
.cornerRadius(20)
.onTapGesture {
withAnimation {
viewModel.updateAppIcon(to: appIcon)
}
}
}
}.padding(.horizontal)
.padding(.vertical, 40)
}
}
.background(Color(UIColor.pageBackground).ignoresSafeArea())
}
}
struct ChangeAppIconView_Previews: PreviewProvider {
static var previews: some View {
ChangeAppIconView()
}
}
struct CheckboxView: View {
let isSelected: Bool
private var image: UIImage {
let imageName = isSelected ? "icon-checked" : "icon-unchecked"
return UIImage(imageLiteralResourceName: imageName)
}
var body: some View {
Image(uiImage: image)
}
}
Conclusion
Your users will be happy being able to configure an alternate icon matching their home screen. You can configure alternate icons inside your assets catalog and Xcode’s build settings. Using a custom enum combined with UIApplication’s APIs for changing the icon allows users to customize the app icon.
If you like to improve your Swift knowledge, check out the Swift category page. Feel free to contact me or tweet me on Twitter if you have any additional tips or feedback.
Thanks!