every Tech Blog

株式会社エブリーのTech Blogです。

Xcode 15の画像/色のシンボル自動生成機能をSPMマルチモジュール環境で使う

この記事は every Tech Blog Advent Calendar 2024(夏) 12 日目の記事です。

今週はWWDCでiOS 18やXcode 16の情報が公開されていますが、この記事では昨年9月にリリースされたXcode 15で実装されたアセットカタログの画像/色のシンボル自動生成機能についての説明と、トモニテアプリへの適用(途上です)について書きます。

Xcode 15の画像/色のシンボル自動生成機能

Xcode 15から、アセットカタログ内の画像/色に対してSwiftのシンボルを生成できるようになりました。今までの名前文字列で指定する方法と比較すると、コード補完が効き、コンパイラの型チェックがされるため安全です。

例えば post_icon という画像がアセットカタログに含まれる時、これまでは以下のようにアセットの名前を文字列で記述していました。

// Swift UI
Image("post_icon")

// UIKit
UIImage(named: "post_icon")

Xcode 15では、build settingsの Generate Asset Symbols (ASSETCATALOG_COMPILER_GENERATE_ASSET_SYMBOLS) がYESの時、アセットカタログに含まれる画像/色に対応するシンボルが自動的に生成されます。(デフォルトでYESですが、NOに設定することで無効化できます)

生成されたシンボルを使って以下のように書くことができます。

// SwiftUI
Image(.postIcon)

// UIKit
UIImage(resouce: .postIcon)

さらに、 Generate Swift Asset Symbol Extensions」(ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS) をYESにするとUIImageのExtensionが生成されて以下のように書けるようになります。(デフォルトは無効です)

// UIKit
UIImage.postIcon

(ここまで画像の場合しか説明していませんが、色についてもほぼ同様です)

SwiftGenなどを導入しているプロジェクトも多いと思いますが、Xcode 15からは画像/色に関してはXcode単体で同様のことが可能です。

トモニテアプリへの適用

トモニテアプリはSPMによるマルチモジュール構成になっており、機能別に分割され独立したパッケージになっています。 一方アセットカタログはメインバンドルに1つだけ存在し、アプリで使う全てのアセットを含んでおり、各パッケージから参照されています。

この環境で、生成されたシンボルを利用しようとした時に問題がありました。 生成されたシンボルはアセットカタログが含まれるモジュール内でしか利用できないため、各機能モジュールから画像を参照できません。

// モジュールから
Image(.postIcon) // Type 'ImageResource' has no member 'postIcon'

この問題には以下の方針で対応することにしました。

  • 特定のモジュールだけで利用するアセットは、モジュール毎に作成したアセットカタログに移動する。
  • 複数のモジュールで利用するアセットは、 Common モジュールに作成したアセットカタログに移動する。Common にはアセットにアクセスするためのpublicなextensionを用意する

元々の構成ではパッケージ外のアセットに暗黙的に依存しているという問題意識もあったので、この際依存関係の整理を兼ねて修正したいと考えています。

各モジュールのアセットカタログ

各パッケージのSource以下にアセットカタログファイルを作成し、画像を格納します。 SPMパッケージのSources以下のxcassetsファイルは自動的にシンボル生成の対象となるようです。そのため同パッケージ内では以下のように画像にアクセスできるようになります。

// 名前で指定
Image("post_icon", bundle: .module)

//シンボルで指定
Image(.postIcon) // bundle指定は不要

ただしSPMパッケージではExtensionsは生成されないようなので、 UIImage.postIcon といった記述はできません。

Commonのアセットカタログ

Commonパッケージ(UIの共通部分、デザインシステムを扱う)にアセットカタログを作成し、共通アセットを格納します。

また、以下のようなExtensionを定義して外部から画像にアクセスできるようにします。

extension Image {
    public static var close: Image {
        Image(.close)
    }
}

extension UIImage {
    public static var close: UIImage {
        UIImage(resource: .close)
    }
}

共通画像は数が少なく、変更頻度も低いのでこのような運用でも許容できると考えていますが、画像が多くなったら課題になりそうです。

最後に

Xcode 15の画像/色シンボル自動生成機能をSPMによるマルチモジュール環境へ適用する方法について書きました。どなたかの参考になれば幸いです。