アイコンを変えたくなったら申請しなきゃ

アプリを作っていて、ホーム画面に表示されるアプリのアイコンを変更したくなったことはないでしょうか?

デザインを大幅にリニューアルするということならともかく、イベントや期間限定の演出として一時的に変更したいという場合でも、 iOS の場合は、 Apple に対してアップデート申請と審査を行わないといけません。

実際、デザイン変更を演出の一部としている LINE などのアプリは、都度アプリがバージョンアップされています。

しかしバージョンアップを行うとなると、審査期間やリジェクトリスクなどがつきまとうため、スピーディーな変更には対応できない可能性もありますね。

しかし、 iOS10.3 から、アプリのアイコンを任意のタイミングで変更できるようになりました。

ここでは、その設定方法などを紹介していきます。

Alternate Icons について

今回紹介する Alternate Icons は、これまでのようにアプリのバージョンアップを伴わず、任意のタイミングで、Apple の審査なしにアイコンファイルを変更するための、iOS 10.3 の新機能です。

とはいえ、いつでもどこでもどんな画像でも変更できるということではなく、以下のようなルールが存在します。

  • 変更可能なのは iOS10.3 以降
  • 変更対象のアイコンは、事前にアプリにバンドルしておく必要がある
    • 変更対象のアイコンデザインも、審査対象の一つということかと思われる
    • サーバ経由で取得した画像などは対象外
  • アイコンを変更したタイミングで、ユーザにはダイアログで通知される
  • アプリがフォアグラウンドにある状態でのみ変更可能 ※
    • タイマーやサイレントプッシュ通知経由での変更はできない ※

※ 2017年3月28日時点では、ドキュメント含め情報がなく、実現できませんでした。
もしかしたら、やり方があるのかもしれません。

設定・実装方法について

それでは、実際にアイコンを変更するアプリを作っていきましょう。 大まかな流れは以下のとおりです。

  • アイコンをプロジェクトに組み込む
  • Info.plist に必要事項を記述
  • アイコンを変更する処理をプログラムに記述する

アイコンをプロジェクトに組み込む

まずは、デフォルトで利用するアイコンファイルと、変更対象となるアイコンファイルを、プロジェクト内に組み込みます。

今回利用しているのは、以下のようなアイコンです。サイズはそれぞれ120 * 120, 180 * 180 を、@2x, @3x として用意しています。

プロジェクトへは、画像ファイルを直接追加します。

デフォルトのアイコン画像については、通常通りアセットカタログを利用しても問題ないようでしたが、変更対象のアイコン画像ファイルは、アセットカタログを利用できませんでした。

Info.plist に必要事項を記述

変更したいアイコンファイルについては、 Info.plist に記述します。

具体的な設定内容は以下の通りです。なお、デフォルトアイコンをアセットカタログで設定する場合は、Primary Icon 要素は不要です

  • Icon files (iOS 5)
    • Primary Icon
      • Icon files
        • Item 0 → デフォルトアイコンのファイル名を指定する
    • CFBundleAlternateIcons
      • Other Icon 001 → 任意の名前を設定する
        • CFBundleIconFiles
          • Item 0 → 変更対象アイコンのファイル名を指定する
      • Other Icon 002 → 任意の名前を設定する
        • CFBundleIconFiles
          • Item 0 → 変更対象アイコンのファイル名を指定する

XML 表示については、以下の通りです(一部抜粋)。

<?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>
      :
    (省略)
      :
    <key>CFBundleIcons</key>
    <dict>
        <key>CFBundlePrimaryIcon</key>
        <dict>
            <key>CFBundleIconFiles</key>
            <array>
                <string>Icon-Default</string>
            </array>
        </dict>
        <key>CFBundleAlternateIcons</key>
        <dict>
            <key>Other Icon 001</key>
            <dict>
                <key>CFBundleIconFiles</key>
                <array>
                    <string>Icon-01</string>
                </array>
            </dict>
            <key>Other Icon 002</key>
            <dict>
                <key>CFBundleIconFiles</key>
                <array>
                    <string>Icon-02</string>
                </array>
            </dict>
        </dict>
    </dict>
      :
    (省略)
      :
</dict>
</plist>

アイコンを変更する処理をプログラムに記述する

実際に、アイコンを変更するプログラムを記述します。

利用する API は、UIApplicationに extension として追加された、以下のプロパティ・メソッドです。

extension UIApplication {

    // 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?) -> Swift.Void)? = nil)


    // If `nil`, the primary application icon is being used.
    @available(iOS 10.3, *)
    open var alternateIconName: String? { get }
}

今回は、画面上にボタンを3つ用意し、ボタンを押したら対応するアイコンに切り替わる、というアプリを作ってみます。

ViewControllerの内容は以下の通りです。 UI の調整に関する部分は省略しています。

import UIKit

class ViewController: UIViewController {

    // 現在設定されているアイコン.
    private var currentIconName: String {
        // (2). 現在のアイコン名を取得する.
        return UIApplication.shared.alternateIconName ?? "デフォルトアイコン"
    }

    override func viewDidLoad() {
        super.viewDidLoad()

        print("現在のアイコンは「\(self.currentIconName)」です。")
    }

    @IBAction func tapButton(_ sender: UIButton) {

        var iconName: String?

        // 押されたボタンによってアイコンを切り替える.
        switch sender.tag {
        case 0:
            iconName = nil // デフォルトにしたい場合は nil を指定する.
        case 1:
            iconName = "Other Icon 001" // Info.plist で設定した任意の名前を指定する.
        case 2:
            iconName = "Other Icon 002"
        default:
            fatalError()
        }

        // (1). アイコンの変更処理.
        UIApplication.shared.setAlternateIconName(iconName) { error in
            if let error = error {
                print("error = \(error)")
            }
            print("現在のアイコンは「\(self.currentIconName)」です。")
        }
    }
}

(1). アイコンの変更について

アイコンを変更する場合は、メソッドsetAlternateIconName(alternateIconName:completionHandler:)を利用します。第一引数には、 Info.plist で設定した任意のアイコン名を指定します。デフォルトアイコンを設定したい場合には、nilを指定します。

処理が終わったら、第二引数のクロージャが呼び出されます。引数に値がある場合は、変更に失敗している場合です。存在しないアイコン名を渡した場合などは、エラーのインスタンスが渡されてきますので、確認してください。

(2). 現在のアイコン名について

現在設定されているアイコンを確認するには、プロパティalternateIconNameを利用します。

このプロパティからは、 Info.plist に設定した任意のアイコン名、もしくは nil が取得可能です( nil の場合はデフォルトアイコンが設定されています)。

動作確認

ボタンを押すことで、確認のダイアログが表示され、対応するアイコンに変更されていることが変わると思います。簡単ですね!

まとめ

iOS10.3以降、という縛りはありますが、期間限定の変更だけにとどまらず、トロフィーやアチーブメントとしての利用なども考えられますね。

アプリ内課金で、有料のアイコンなどを販売することもできるかもしれません( Apple が許せば、ですが…)。

ぜひ、利用してみてはどうでしょうか!