SwiftGenとSwift-DocCを活用してコード上の画像を一瞬でプレビューする方法 - Mirrativ Tech Blog

Mirrativ Tech Blog

株式会社ミラティブの開発者(バックエンド,iOS,Android,Unity,機械学習,インフラ, etc.)によるブログです

SwiftGenとSwift-DocCを活用してコード上の画像を一瞬でプレビューする方法

こんにちはiOS開発をしている福山です。

この記事では、SwiftGenとSwift-DocCを用いて、xcassetsを開かずにコード内の画像をQuick Help(右パネルまたは⌥オプションキー+クリック)で直接確認する方法を紹介します。

各ツールの簡単な説明

  • Swift-DocCは、Swift言語用のドキュメント生成ツールです。Swiftコード内に書かれたコメントから、ナビゲーションが容易で、見た目も美しいドキュメントを生成することができます。

  • SwiftGenは、Xcodeプロジェクトで使われるリソース(画像、フォント、色、ローカライズなど)を静的に型付けされたSwiftコードに変換するツールです。自動生成された安全なコードを通じてリソースにアクセスできるため、タイプミスによるエラーや存在しないリソースへの参照をコンパイル時に検出するのに役立ちます。

SwiftGenの生成コード例

enum Asset {
  static let mirrabit1 = ImageAsset(name: "mirrabit1")
  static let mirrabit2 = ImageAsset(name: "mirrabit2")
  static let mirrabit3 = ImageAsset(name: "mirrabit3")
}

画像呼び出し例

imageView.image = Asset.mirrabit1.image

ただ、Swiftの定数から対象の画像を確認するには、いちいち元のxcassetsを開いて画像名を検索をするという無意識に時間を削る作業が発生していたため、なんとか対処します。

処理の流れ

  1. Xcode から .docc フォルダを作成
  2. SwiftGen の画像用設定
  3. xcassets から .docc フォルダへ画像を自動コピー

Xcode から.doccフォルダを作成

SwiftGenImageDocCという新しいプロジェクトを作成したところから始めます。使用しているXcodeは15.2です。

プロジェクトフォルダから New File... > Documentation Catalog で.doccフォルダを追加します。

.docc 内のResources内にCopyOfAssetsImagesというフォルダを追加します。 後の作業でCopyOfAssetsImagesにAssets.xcassetsの画像を自動でコピーすることでQuick Helpで画像を参照できるようになります。

開発時に使う機能なので、自動コピーした画像をgitで管理したくない場合は、 CopyOfAssetsXcassets内に.gitignoreを追加します。

$ touch SwiftGenImageDocC/Documentation.docc/Resources/CopyOfAssetsXcassets/.gitignore

.gitignoreの内容

*.jpeg
*.jpg
*.png

SwiftGenの画像用設定

既にあるAssets.xcassetsにいくつかの画像を準備します。

プロジェクトフォルダに SwiftGen フォルダとその中の Generated, Templates フォルダを作成します。 そして自動生成されたファイルを Xcode に参照させるため、Generated の中に Assets.swift を作成しておきます。

SwiftGenの標準テンプレートをTemplatesフォルダへ出力します。 swiftgenコマンドが入っていない場合は brew install swiftgen などで導入します。

$ swiftgen template cat xcassets swift5 >SwiftGenImageDocC/SwiftGen/Templates/Assets.stencil

出力した SwiftGenImageDocC/SwiftGen/Templates/Assets.stencil の107行目あたり ({% elif asset.type == "image" %} の後) に以下の1行追加します。

  {% elif asset.type == "image" %}
+  /// ![xcassets image]({{asset.value}})
  {{accessModifier}} static let {{asset.name|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}} = {{imageType}}(name: "{{asset.value}}")
  {% elif asset.type == "symbol" %}

次にプロジェクトルートにswiftgen.ymlファイルを作成します。

$ touch swiftgen.yml

swiftgen.ymlの内容

xcassets:
  - inputs: SwiftGenImageDocC/Assets.xcassets
    outputs:
      - templatePath: SwiftGenImageDocC/SwiftGen/Templates/Assets.stencil
        output: SwiftGenImageDocC/SwiftGen/Generated/Assets.swift
        params:
          publicAccess: false

xcassetsから.doccフォルダへ画像を自動コピー

スクリプトファイルを作成し、実行権限を付与します。

$ touch SwiftGenImageDocC/SwiftGen/copy-images-for-docc.sh
$ chmod +x SwiftGenImageDocC/SwiftGen/copy-images-for-docc.sh

copy-images-for-docc.shの内容

#!/bin/bash

# Assets.xcassets の画像をSwift-DocC用フォルダへコピーするスクリプト

copyAndRenameImages() {
    local xcassets_path="$1"
    local target_folder_path="$2"

    # 引数パスが指定されているか確認
    if [[ -z "$xcassets_path" || -z "$target_folder_path" ]]; then
        echo "使用方法: copyAndRenameImages <xcassets_path> <target_folder_path>"
        return 1
    fi

    # .imagesetフォルダを捜索
    find "$xcassets_path" -name "*.imageset" -print0 | while IFS= read -r -d '' imageset; do
        imageset_name=$(basename "$imageset" .imageset)
        # .imagesetフォルダ内の画像を捜索
        find "$imageset" -type f \( -name "*.png" -o -name "*.jpeg" -o -name "*.jpg" -o -name "*.svg" -o -name "*.pdf" \) -print0 | while IFS= read -r -d '' image; do
            base_name=$(basename "$image" | sed -E 's/(@[0-9]x)?\.(jpg|jpeg|png|svg|pdf)$//')
            # 拡張子を検出
            ext="${image##*.}"
            # target_folder_pathへ画像を変換orコピー(高い解像度を優先)
            for res in "@3x" "@2x" ""; do
                file="$imageset/$base_name$res.$ext"
                if [[ -f "$file" ]]; then
                    # Swift DocCで使えるように変換orコピー & 画像ファイル名を調整
                    if [[ "$ext" == "pdf" || "$ext" == "svg" ]]; then
                        # pdf,svg画像はpngへ変換
                        sips -s format png "$file" --out "$target_folder_path/$imageset_name.png"
                    else
                        cp "$file" "$target_folder_path/$imageset_name.$ext"
                    fi
                    break 2
                fi
            done
        done
    done
}

# パスを定義
xcassets_path="SwiftGenImageDocC/Assets.xcassets"
target_folder_path="SwiftGenImageDocC/Documentation.docc/Resources/CopyOfAssetsXcassets"

# target_folder_pathの存在とディレクトリかどうかを確認
if [[ -d "$target_folder_path" ]]; then
    if [[ "$(ls -A "$target_folder_path")" ]]; then
        # 隠しファイル(.gitignore等)以外の全てをtarget_folder_pathから削除
        rm -rf "$target_folder_path"/*
    fi
else
    echo "$target_folder_path は存在しない、あるいはディレクトリではありません"
fi

copyAndRenameImages $xcassets_path $target_folder_path

そして、スクリプトとswiftgenコマンドの呼び出しを簡略化するためにMakefileを作成します。

$ touch Makefile

Makefileの内容 (2,3行目のインデントはスペース4つではなくタブ1つにしてください)

swiftgen:
    SwiftGenImageDocC/SwiftGen/copy-images-for-docc.sh
    swiftgen

全ての準備が完了したので以下のコマンドを実行します。xcassetsに新しい画像を追加したときも、こちらのコマンドで更新可能です。

$ make swiftgen

SwiftGenImageDocC/Documentation.docc/Resources/CopyOfAssetsXcassets/ 下に画像ファイルが入ればOKです。

今回の流れでファイル構成は以下のようになっているかと思います。 左: 別エディタ, 右: Xcode

ここまで来ると Asset.imageName.imageAsset.imageName.swiftUIImageimageName 部分を ⌥オプション+クリック でポップアップが表示されるようになります。 表示されない場合は Xcode の再起動などを試すと出てくることがあります。今回使う機能ではメニューから Product > Build Documentation する必要はありません。

おわりに

最近は xcassets 以外にUIコンポーネントのスクショなどもQuick Helpで見られるようにしています。プロジェクトが大きくなるほど単純な表示の確認だけならXcode Previewsよりも早くサクッと確認できるので嬉しいです。

We are hiring!

ミラティブではMirrativをより良いプロダクトにし、「好きでつながり自分の物語が生まれる居場所」をつくるエンジニアを募集中です!

www.mirrativ.co.jp

speakerdeck.com