I met the requirement of dynamically switching the icon of the App once when I was making a customized App in my last company. This time, the leader said it might be necessary, so he did it again. Although it is not what very difficult knowledge point, here also record the process that oneself do.

  • Info. Plist file editing
  • Change the Icon
  • The silent switch

The info. The file

To change the icon dynamically, we need to configure the info.plist file of our project:

  1. Add Icon Files (iOS5), which will have two items by default:
    1. Newsstand Icon
    2. Primary Icon
  2. We need to add the key that we want, CFBundleAlternateIcons, of type Dictionary.
  3. Let’s add some more dictionaries. The key of the dictionary is the name of the Icon you want to change. In the CFBundleIconFiles array below, write the name of the Icon you want to change.

Primary Icon: The main Icon of the App can be set, which is generally ignored. The general main Icon is set in assets.xcassets.

Newsstand Icon: This setting is generally used for display in Newsstand. We don’t have to.

Here we have finished editing info.plist, and now we add the corresponding images to the project. The images here need to be directly added to the project, not in assets.xcassets.

Change the Icon

In iOS 10.3, Apple opened up this API that lets us change our App ICONS dynamically.

// If false, alternate ICONS are not supported for the current process. @available(iOS 10.3, *) open var supportsAlternateIcons: Bool { get } // Pass `nil` to use the primary application icon. The completion handler will be invoked asynchronously on  an arbitrary background queue; Be sure to dispatch back to the main queue before doing any further UI work. @available(iOS 10.3, *) open func setAlternateIconName(_ alternateIconName: String? , completionHandler: ((Error?) -> Void)? // If 'nil', the primary application icon is being used. @available(iOS 10.3, *) open var alternateIconName: String? {get} copies the codeCopy the code

Switch to the Icon we need

@IBAction func changeOneClick(_ sender: Any) { if UIApplication.shared.supportsAlternateIcons { UIApplication.shared.setAlternateIconName("lambot") { (error) in  if error ! = nil {print(" replace icon error ")}}}} copy codeCopy the code

The iconName here is passed directly into the icon name in the project. The important thing to note here is that the names in the project, the names stored in info.plist, and the names passed in here need to be the same.

Reset to the original Icon

@IBAction func resetClick(_ sender: Any) { if UIApplication.shared.supportsAlternateIcons { UIApplication.shared.setAlternateIconName(nil) { (error) in if error ! = nil {print(" replace icon error ")}}}} copy codeCopy the code

If you need to revert to the original icon, you simply pass in nil where the iconName was passed.

You are now ready to switch Icon. But every time we switch, there will be a popbox, so let’s try to get rid of this popbox.

The silent switch

We can use the Runtime method instead of the pop-up box method.

Method Swizzling used to be a load or initialize Method, but it can’t be used in Swift. Then you have to define one.

extension UIViewController { public class func initializeMethod() { if self ! = UIViewController.self { return } // Method Swizzling DispatchQueue.once(token: "ChangeIcon") { let orignal = class_getInstanceMethod(self, #selector(UIViewController.present(_:animated:completion:))) let swizzling = class_getInstanceMethod(self, #selector(UIViewController.jt_present(_:animated:completion:))) if let old = orignal, let new = swizzling { method_exchangeImplementations(old, new) } } } @objc private func jt_present(_ viewControllerToPresent: UIViewController, animated flag: Bool, completion: (() -> Void)? = nil) {viewControllerToPresent is UIAlertController {let alertTitle = (viewControllerToPresent as! UIAlertController).title let alertMessage = (viewControllerToPresent as! UIAlertController).message // popup when replacing icon, both strings are nil. If alertTitle == nil && alertMessage == nil {return}} // Because methods have been swapped, This call is equivalent to calling the original system's present self_jt_present (viewControllerToPresent, animated: flag, completion: completion)}} copy codeCopy the code

Once you’ve defined an extension method for UIViewController, remember to call it in an AppDelegate.

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) - > Bool {UIViewController. InitializeMethod () return true} copy codeCopy the code

In Swift, the once function before GCD is removed.

extension DispatchQueue { private static var _onceTracker = [String]() public class func once(token: String, block: () -> ()) { objc_sync_enter(self) defer { objc_sync_exit(self) } if _onceTracker.contains(token) { return } _onceTracker.append(token) block()}} Copies the codeCopy the code

The code in the defer block will execute before the function return, regardless of which branch the function returns from, whether it has a throw, or whether it goes naturally to the last line.

Now, when we change the Icon again, the popup will not appear.

conclusion

Simple knowledge points, a long time is not likely to forget. I hope I can keep learning, keep recording and keep growing.

Reference links:

Information Property List Key Reference

Citation: Original address