はじめに
こんにちは。カイザーです。Swiftでのコードジェネレータでは、SwiftGenやRSwiftが有名ですね。
今回は、SwiftGenを紹介します。
SwiftGenとは、stringsやStoryboard、Asset Catalog等のリソースから、Swiftファイルをジェネレートする、CLIツールです。
iOSでのリソース使用にありがちな、文字列での名前指定では無く、ジェネレートされた定数定義や構造体を使用することにより、typoを無くすことができます。
今回は、簡単なスライドショーアプリをサンプルに、SwiftGenを使っていきたいと思います。
SwiftGenの特徴
先に、SwiftGenの特徴を説明しておきましょう。
コードジェネレートするリソースを選択することができる
様々なリソースの中から、必要な種類、ファイル、ディレクトリなどに絞り込む設定が可能です。
そのため、コードジェネレートに余計な時間はかかりません。
(R.swiftでは対応するすべてのリソースが自動的に含まれます。)
コードジェネレートするのにビルドが必要ない
自前でリソースファイルをパースし、コードジェネレートします。そのため、ビルドは発生しません。
設定ファイルベースである
swiftgen.yml
ファイルをプロジェクトファイルと同じ階層に配置し、設定を記載することができます。
この設定ファイルさえGitで共有すれば、誰でも同じルールでコードがジェネレートされます。
インストール
Cocoapods, Homebrew, Mintからインストール可能ですが、今回はCocoapodsを使用します。プロジェクトごとに、SwiftGenのバージョンを管理したいためです。
Podfileに
pod 'SwiftGen'
を追記し、
$ pod install
すれば、インストール完了です。
利用するには、プロジェクトルートまで移動し、
$ Pods/SwiftGen/bin/swiftgen
で実行可能です。
Localizable.strings
多言語アプリでは必須のファイルですね。
swiftgen.ymlでの設定
以下の設定を行います。
1 2 3 4 5 | strings: inputs: swiftgen-ex/Base.lproj/Localizable.strings outputs: - templateName: structured-swift4 output: swiftgen-ex/Generated/strings.swift |
なお
swiftgen-ex
はサンプルプロジェクト名なので、皆さんのプロジェクト名に読み替えてください。
strings
1番初めの
strings
はリソースの種類を表します。SwiftGenが対応しているもののみ、指定可能です。
inputs
inputs
は、リソースファイルそのものを示します。ファイル単体を指定することもできますし、ディレクトリを指定することもできます。
指定は単数でも、複数でも可能です。
ディレクトリの場合、その中の全てのファイルが対象となりますが、別途
filter
を設定することで、正規表現でファイル名を指定することもできます。(Interface Builderで説明します)
outputs
outputs
はコードジェネレータの出力方法や、出力先を設定します。
テンプレート
SwiftGenでは「テンプレート」によって、ジェネレートされたSwiftコードの構造が決まります。それを指定するのが
templateName
です。
stringsでは、
structured-swift4
と
flat-swift4
が使用可能です。
structured-swift4
stringsファイルで、キーをドット区切りにすると、その指定に沿って構造化された定数がジェネレートされます。(実際はenumです)
例えば、以下のようなstringsファイルを作ると
1 2 3 | "cities.italy.name" = "Italy"; "cities.london.name" = "London"; "cities.paris.name" = "Paris"; |
Swift側からは、以下のように呼び出せます。
1 2 3 | L10n.Cities.London.name L10n.Cities.Paris.name L10n.Cities.Italy.name |
さらに、
L10n
でピンと来たかもしれませんが、この呼び出しにより
Localizable.strings
での多言語も反映されます。
flat-swift4
今回は使用しませんが、
structured-swift4
とは対象的に、構造的にならず、フラットな構造でコードジェネレートされます。
output
ジェネレートされたSwiftファイルの出力先です。今回は
Generated
ディレクトリ配下にします。
このディレクトリ配下は、コンフリクト防止のため、gitignoreしておくと良いでしょう。
コードジェネレート
リソースファイルの準備と、
swiftgen.yml
の編集が完了したら、プロジェクトルートで
$ Pods/SwiftGen/bin/swiftgen
を実行します。
すると、outputで指定したディレクトリにSwiftファイルが出てきます。
これらをプロジェクトに追加し、使用します。
Interface Builder
Storyboardには様々な「Identifier」という設定項目があり、全て文字列のため、誰しも間違えたことはあるでしょう。SwiftGenではStoryboardとSegueを定数化することができます。
swiftgen.ymlでの設定
以下を追記します。
1 2 3 4 5 6 7 8 | ib: inputs: swiftgen-ex/Base.lproj filter: .+\.storyboard$ outputs: - templateName: scenes-swift4 output: swiftgen-ex/Generated/Storyboard Scenes.swift - templateName: segues-swift4 output: swiftgen-ex/GEnerated/Storyboard Segues.swift |
1番初めは
ib
となります。
inputs/filter
filter
では、
inputs
のディレクトリの内容を、正規表現で絞り込むことができます。今回は、拡張子がstoryboardのものを絞り込んでいます。
outputs
今回は
outputs
が2つあります。
scenes-swift4
はStoryboardや画面、
segues-swift4
はSegueを定数化します。
コードジェネレート
Storyboard
StoryboardのViewControllerなどをインスタンス化するには、以下のようにします
1 2 3 4 5 | // Main.storyboardのInitial Sceneのインスタンスを生成 let initialViewController = StoryboardScene.Main.initialScene.instantiate() // Main.storyboardのIdentifier"viewController"のインスタンスを生成 let viewController = StoryboardScene.Main.viewController.instantiate() |
Storyboardのファイル名や、Identifierを文字列として渡す必要が無いので、すっきりとしています。
Segue
コードからSegueで画面遷移するときに、とても便利です。
1 2 | // Main.storyboardのSegue Identifier"toPhoto" で遷移する。 perform(segue: StoryboardSegue.Main.toPhoto) |
値を渡すときは、これまで通り
prepare(for segue:)
を実装しますが、Segueの判定にSwiftGenが使えます。
1 2 3 4 5 6 7 8 9 10 11 12 | override func prepare(for segue: UIStoryboardSegue, sender: Any?) { // UIStoryboardSegueからStoryboardSegue.Mainのインスタンス生成 switch StoryboardSegue.Main(segue) { case .toPhoto?: // "toPhoto" Segueの遷移 if let photoViewController = segue.destination as? PhotoViewController { photoViewController.imageAsset = photos[index].asset } break default: break } } |
このコードは、サンプルアプリで写真がタップされたときに、写真画面に遷移する内容です。
残念な点が1つだけあり、destinationのキャストが従来通り発生してしまうことです。ここは、R.Swiftの方が、一枚上手かなと思います。
Asset Catalog
最近のXcodeでは、
#imageLeteral
などを使用することによって簡単にAsset Catalogにアクセスできるようになりましたが、SwiftGenでもAsset Catalogの定数化が可能なので、紹介します。
普段のIDEにAppCodeを使用している場合は重宝します。
swiftgen.ymlの設定
以下を追記します。
1 2 3 4 5 6 | xcassets: inputs: - swiftgen-ex/Assets.xcassets outputs: - templateName: swift4 output: swiftgen-ex/Generated/assets-images.swift |
1番初めは
xcassets
となります。AssetCatalogの設定はシンプルですね。ちなみに、複数のAssetCatalogを使用している場合は、
inputs
に書き足したり、複数のAssetCatalogが入っているディレクトリごとまとめて指定してしまっても構いません。
コードジェネレート
今回は、
Assets.xcassets
直下に、”london”, “paris”, “italy” の3つの画像を配置しました。そのため、それぞれの呼び出しは以下となります。
1 2 3 | let londonImage = Asset.london.image let parisImage = Asset.paris.image let italyImage = Asset.italy.image |
これで、UIImageを取得することができます。
その他のリソース
ここで紹介した以外にも、様々なリソースに対応しています。以下にそれぞれのジェネレート仕様が記載されています。
https://github.com/SwiftGen/SwiftGen/tree/master/Documentation/SwiftGenKit%20Contexts
また、出力テンプレート仕様については、以下に記載されています。
https://github.com/SwiftGen/SwiftGen/tree/master/Documentation/templates
さいごに
今回は、SwiftGenを試しましたが、同種であるR.Swiftも調べながら調査しました。どちらにも、長所短所があるため、R.Swiftとの比較記事も書いてみたいと思います!