SwiftGenで、StencilのテンプレートのカスタマイズなしにSwiftUI向けに色やフォントを取り出せるようになっていた

8月にリリースされたSwiftGenの6.6.0でSwiftUIに対するサポートが手厚くなり、関わるプロジェクトでカスタマイズしていたテンプレートがいくつか削除できました。
2022.08.31

表題の通りです。 SwiftUI製のアプリでSwiftGenを使用しているのですが、SwiftUIは6.6.0以前はSwiftUIに対してUIKitほど手厚くサポートしていませんでした。

SwiftGenではStencilというコードテンプレートを生成できる言語を使っています。 8月にリリースされたSwiftGen6.6.0以前はSwiftUIのサポートの途中で、デフォルトのテンプレートをカスタマイズしながらColorやFontを取り出せるようにしていました。

Stencilについて

Stencilについて知っておくとSwiftGenで生成できるテンプレートをいくらか拡張できます。

swiftgen template catコマンドでベースにするテンプレートの中身を見ることができます。

swiftgen template docで特定のテンプレートのドキュメントを閲覧できます。

6.6.0より前

ColorとFontを例にして説明します。

SwiftUI向けに拡張するために、デフォルトのテンプレートをベースにプロパティを生やすように記述して利用していました。

また、テンプレート作成の際にはxcassetsのテンプレートに手を入れました。ドキュメントがテンプレートに対して提供されているので参考になります。

プロジェクトでColorは以下のように取り出していました。 {}の中でUIColorを変換しています。これでAsset.Colors.hogeColor.colorのように取り出してSwiftUI製のコンポーネントで使用します。

  {{accessModifier}} private(set) lazy var color: Color = {
    Color(systemColor)
  }()

色を表すクラスは以下のように定義されていたので、その中に上の実装を追加できます。 ドキュメントでも既存のテンプレートに独自のカスタマイズをするのがおすすめと記載されていて、生成されるファイルと比較しながらであればやりたいことの実現はそう難しくないと思います。

{{accessModifier}} final class {{colorType}} {
  {{accessModifier}} fileprivate(set) var name: String

  #if os(macOS)
  {{accessModifier}} typealias SystemColor = NSColor
  #elseif os(iOS) || os(tvOS) || os(watchOS)
  {{accessModifier}} typealias SystemColor = UIColor
  #endif

  @available(iOS 11.0, tvOS 11.0, watchOS 4.0, macOS 10.13, *)
  {{accessModifier}} private(set) lazy var systemColor: SystemColor = {
    guard let color = SystemColor(asset: self) else {
      fatalError("Unable to load color asset named \(name).")
    }
    return color
  }()

  fileprivate init(name: String) {
    self.name = name
  }
}

Fontは以下のように取り出していました。Colorと少しだけ異なりますがmappedFont(from:textStyle:)というメソッドをextensionで定義してテンプレートファイルには以下のように記載します。

{{accessModifier}} func textStyle(_ textStyle: Font.TextStyle) -> Font {
  Font.mappedFont(from: name, textStyle: textStyle)
}

6.6.0以後

6.6.0のリリースノートにSwiftUIの一部サポートが記載されています。

XCAssets & Fonts: added support for SwiftUI. You can now easily access colors images, symbols and custom fonts from your SwiftUI code.

今回は6.6.2で試しました。

internal struct ColorName {
  internal let rgbaValue: UInt32
  internal var color: Color { return Color(named: self) }

  /// <span style="display:block;width:3em;height:2em;border:1px solid black;background:#339666"></span>
  /// Alpha: 100% <br/> (0x339666ff)
  internal static let articleBody = ColorName(rgbaValue: 0x339666ff)
  /// <span style="display:block;width:3em;height:2em;border:1px solid black;background:#ff66cc"></span>
  /// Alpha: 100% <br/> (0xff66ccff)
  internal static let articleFootnote = ColorName(rgbaValue: 0xff66ccff)
  ...
}

上記のようにcolorプロパティでColorが取り出せるようになっています。

Fontも以下のように取り出せるようになっています。

FontFamily.HogeHoge.regular.font(size: 20.0)

今回の変更により一部のカスタムテンプレートは不要になったのでxcassets向けのテンプレートは削除しました。

Localizable.stringについて

どのように実装をするかまだ議論の途中で、まだこちらについては適宜カスタマイズが必要になります。

以下などを参考にプロジェクトで利用できるようにカスタマイズしました。

  • https://github.com/onmyway133/blog/issues/798
  • https://github.com/SwiftGen/SwiftGen/issues/685#issuecomment-782893242

SwiftUI向けのサポートに関する議論はこちらで行っているのでsubscribeすると良いかもしれません。

まとめ

テンプレート言語は強力ですが、濫用は属人化やメンテナンスコストの上昇を招くと思うのでなるべくデフォルトの機能で何とかできたら良いなと思っていました。今回は8月のアップデートでいくつかカスタムテンプレートを削除できたので記事にしました。 何かご指摘があればSNS等でお願いいたします。