iPadawan
iPadawan

Reputation: 1110

iOS/macOS - best way to draw an image/icon ready to be tinted by system

What is the best way to create a set of icons influenced by the operating system to display a colored / accent version?

As with the;  

I have about 10 black versions, but would like to have them adjusted with either selected or system chosen accent color. What should I have to do with my icons to let them coloured by the OS

Any road map?

Upvotes: 0

Views: 238

Answers (1)

elliott-io
elliott-io

Reputation: 1414

This code shows you how to add set an alternative icon based on iOS theme, and your settings bundle. I've also added accent1Icon and accent2Icon as examples.

In your Settings.bundle, you need to add a Multi Value - App item to Preference Items (Apple requires that users can change the theme, even if you automatically set it based on their iOS settings):

<dict>
    <key>Type</key>
    <string>PSMultiValueSpecifier</string>
    <key>Values</key>
    <array>
        <string>0</string>
        <string>1</string>
        <string>2</string>
        <string>3</string>
        <string>4</string>
    </array>
    <key>Title</key>
    <string>App Icon Theme</string>
    <key>Key</key>
    <string>app_icon_theme</string>
    <key>DefaultValue</key>
    <string>0</string>
    <key>Titles</key>
    <array>
        <string>Use iOS Theme</string>
        <string>Dark Theme</string>
        <string>Light Theme</string>
        <string>Accent1 Theme</string>
        <string>Accent2 Theme</string>
    </array>
</dict>

Create a class that minimally has your app_icon_theme setting:

class SettingsBundleHelper {
    struct SettingsBundleKeys {
        static let AppIconThemeKey = "app_icon_theme"
    }
}

Add this to your info.plist:

<dict>
    <key>CFBundleAlternateIcons</key>
    <dict>
        <key>darkIcon</key>
        <dict>
            <key>CFBundleIconFiles</key>
            <array>
                <string>name-of-dark-icon</string>
            </array>
            <key>UIPrerenderedIcon</key>
            <false/>
        </dict>
        <key>accent1Icon</key>
        <dict>
            <key>CFBundleIconFiles</key>
            <array>
                <string>name-of-accent1-icon</string>
            </array>
            <key>UIPrerenderedIcon</key>
            <false/>
        </dict>  
        <key>accent2Icon</key>
        <dict>
            <key>CFBundleIconFiles</key>
            <array>
                <string>name-of-accent2-icon</string>
            </array>
            <key>UIPrerenderedIcon</key>
            <false/>
        </dict>     
    </dict>
    <key>CFBundlePrimaryIcon</key>
    <dict>
        <key>CFBundleIconFiles</key>
        <array>
            <string>AppIcon</string>
        </array>
        <key>UIPrerenderedIcon</key>
        <false/>
    </dict>
</dict>

Create a folder in your project called Alternate Icons and save a [email protected] and [email protected] in it.

YourProject 
|
+-- Alternate Icons (folder)
   |
   +-- [email protected]
   +-- [email protected]
   +-- [email protected]
   +-- [email protected]
   +-- [email protected]
   +-- [email protected]

You could run the changeIcon code in your AppDelegate applicationDidBecomeActive (or elsewhere). If you choose to run it there, you'll need to add this to your AppDelegate file:

enum AppIconTheme: String {
   case UseiOSTheme = "0"
   case DarkTheme = "1"
   case LightTheme = "2"
   case Accent1Theme = "3"
   case Accent2Theme = "4"
}

func applicationDidBecomeActive(_ application: UIApplication) {
// update icon if dark mode
if #available(iOS 12.0, *) {
    var theme = AppIconTheme.UseiOSTheme
    if let iconTheme = UserDefaults.standard.string(forKey: SettingsBundleHelper.SettingsBundleKeys.AppIconThemeKey) {
        if let themeSettings = AppIconTheme(rawValue: iconTheme) {
            theme = themeSettings
        }
    }
    print(theme as Any)
    switch (theme) {
        case .UseiOSTheme:
            if UIApplication.shared.windows[0].rootViewController?.traitCollection.userInterfaceStyle == .dark {
                self.changeIcon(to: "darkIcon")
            } else {
                self.changeIcon(to: nil)
            }
        case .LightTheme:
            self.changeIcon(to: nil)
        case .DarkTheme:
            self.changeIcon(to: "darkIcon")
        case .Accent1Theme:
            self.changeIcon(to: "accent1Icon")
        case .Accent2Theme:
            self.changeIcon(to: "accent2Icon")
        }
    } else {
        // Fallback on earlier versions
        var theme = AppIconTheme.UseiOSTheme
        if let iconTheme = UserDefaults.standard.string(forKey: SettingsBundleHelper.SettingsBundleKeys.AppIconThemeKey) {
            theme = AppIconTheme(rawValue: iconTheme)!
        }
        print(theme as Any)
        switch (theme) {
        case .UseiOSTheme:
            self.changeIcon(to: nil)
        case .LightTheme:
            self.changeIcon(to: nil)
        case .DarkTheme:
            self.changeIcon(to: "darkIcon")
        case .Accent1Theme:
            self.changeIcon(to: "accent1Icon")
        case .Accent2Theme:
            self.changeIcon(to: "accent2Icon")
        }
    }
}

func changeIcon(to name: String?) {
    //Check if the app supports alternating icons
    guard UIApplication.shared.supportsAlternateIcons else {
        return;
    }

    if UIApplication.shared.alternateIconName != name {
        //Change the icon to a specific image with given name
        // if name is nil, the app icon will be the default app icon
        UIApplication.shared.setAlternateIconName(name) { (error) in
             //After app icon changed, print our error or success message
             if let error = error {
                 print("App icon failed to due to \(error.localizedDescription)")
             } else {
                 print("App icon changed successfully.")
             }
        }
    }
}

Here is the documentation from Apple on setAlternativeIcon: https://developer.apple.com/documentation/uikit/uiapplication/2806818-setalternateiconname

Upvotes: 1

Related Questions