Asset Catalog の type-safe コードを public import アクセスできるようにする Workaround
Xcode 15から搭載されたAsset Catalogをtype-safeにアクセスできる機能便利ですよね。これでSwiftGenを利用せずに標準ツールを用いた選択肢が提供されました。
まだご存知ない方はぜひWWDCの動画を見てください。
しかし、ここでコード生成されるtype-safeにリソースアクセスできるSwiftファイル(GeneratedAssetSymbols.swift
)は現状internal
修飾子がつけられています。そのため、リソースがあるPackageをImportして外部からアクセスすることができません。これはプロジェクトをマルチモジュールで構成する際に、Asset丸ごと単位の粒度で切り出している人にとって障壁となるでしょう。
1つの選択肢として、画面を構成するFeature内にリソースを配置してinternal
にアクセスできる構成に変更するということが検討できます。
本記事ではそれは行わず、生成されたAsset CatalogのコードをpublicにアクセスできるようにするPolyfillを紹介します。
Polyfill 方法
ユースケースとして、example
という画像を外部パッケージからUIKit.UIColor
とSwiftUI.Color
で今回のtype-safeな書き方でアクセスできるようにしたいです。
let uiColor = UIColor(resource: .example)
let swiftUIColor = Color(.example)
以下のようなPFColorResource
をリソースが置かれたPackageに定義することで、外部パッケージでImportしても、標準APIと同じ書き方のまま参照することができます。
public enum PFColorResource {
// 継続的にアップデートするのはここ!!
case example
var mapping: ColorResource {
switch self {
// 継続的にアップデートするのはここ!!
case .example: .example
}
}
#if canImport(UIKit)
@available(iOS 11.0, tvOS 11.0, *)
@available(watchOS, unavailable)
public extension UIKit.UIColor {
convenience init(resource: PFColorResource) {
#if !os(watchOS)
self.init(resource: resource.mapping)
#else
self.init()
#endif
}
}
#endif
#if canImport(SwiftUI)
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
public extension SwiftUI.Color {
init(_ resource: PFColorResource) {
self.init(resource.mapping)
}
}
#endif
Point
-
UIColor
やColor
のextensionでColorResoure
と全く同じ関数のインターフェースをPFColorResource
に対して用意する。-
mapping
というinternal
プロパティでinternal
にしかさわれないColorResourceに変換する。
-
- 色の追加・削除・名前変更時に、caseの更新を手動で対応する。
- これだけがWorkaroundに必要なコスト。
- caseに記載する名前は
GeneratedAssetSymbols.swift
を開いて標準のコード生成からコピーすると楽
GeneratedAssetSymbols.swift を覗く方法
convenience init(resource: PFColorResource) {
#if !os(watchOS)
// ↓ resource 部分をクリックするとコードジャンプできる!
self.init(resource: resource.mapping)
#else
self.init()
#endif
}
コードのresource部分を cmd+click
すると簡単にコード生成ファイルにアクセスできます。コメントつけておくと忘れずに済んでいいと思います。
Pros / Cons
- Asset Catalogに定義された画像やカラーが削除・名前変更されるとビルドが適切に落ちる。
- type-safeにすることで防ぎたい事故をコンパイラで保証する仕組みは無事担保されている。
- 標準SDKと同じ
init()
でリソースにアクセスできる!!- 将来公式publicサポートされた際、このワークアラウンドを消すだけで済む。
- App本体で標準のAPIで先に対応しておいて、後々Packageにリソースを移す際にもこの方法を用いればコード側の変更を最小限に抑えられる(
import XXX
を足すだけで済むはず)
- でも、リソースのcase書き出すのがダルいよ
-
GeneratedAssetSymbols.swift
からコピーすると楽だよ!! - VSCodeとかでcaseの行を
cmd+D
で一括選択して複数行編集するといいよ!!
-
(マクロでmappingの自動生成とかもできるんじゃないかと思うんですが、それならもうSwiftGenを継続すればいいかなと...)
以上、なるべくライブラリを剥がして標準SDKを活用してプロジェクト構成を改善していきたい人向けのTipsでした。
Discussion