はじめに
先日、SARAHというグルメアプリの、ダークモード対応を担当しました🎉
その際に、大量のアイコンをメンテナンスする必要があったので、Xcodeへのiconアセット取り込みを自動化するスクリプトを作りました。
ZeplinからエクスポートしたPDFに癖があり、一致を調べるのが案外難しかったので、参考になれば幸いです。(実は、imagemagick便利、という話です。)
課題
- ZeplinのどのアイコンがLightとDarkのペアなのか分からない👀
- 手で一つ一つXcodeに登録していくのは、めちゃ時間がかかる😭
- アイコンがアップデートされた時に、また大変😱
- 人手で行うと、ミスが起こるリスクがある
自動化の方針
- 同じアイコンのLight/Dark違いは、必ず同じファイル名にしてもらう
- Zeplinから一括でダウンロードする
- スクリプトで
*.imageset
とContents.json
を自動生成する
自動化の課題
ZeplinからエクスポートしたPDFは、レンダリング結果が同じでも、バイナリレベルでは毎回違うことが判明しました。(メタデータ?)
そうすると以下のような事になり、あまり美しくありません。
- 変更されたアイコンが少なくても、gitのログだと全て変更されたように見える
- light/darkが同じアイコンなのに、余分なPDFが登録される
工夫した点
2つのPDFの内容がピクセルレベルで同じかどうかを、imagemagick
で判別するようにしました。
いつの間にか、PDFにも標準で対応してたんですね〜
最初は単純にググってdiff-pdf
とかを使ってましたが、依存ライブラリが大量な上に、結果はイマイチでした。
- lightとdarkのPDFの内容が同じだった場合、anyのみで登録
- 前回インポートしたPDFと内容が同じだった場合、copyしない
- gitのログで、何のアイコンが更新されたかが分かる!
スクリプト
shellで関数とか分岐をこんなに書いたのは初めてかも…😅
参考: 割りと便利だけど微妙に忘れがちなbashのコマンド・チートシート
-
~/Downloads/light/
にダウンロード -
~/Downloads/dark/
にダウンロード -
./scripts/import-icons.sh
で一括インポート
compare
の出力画像は捨てて、終了コードだけを利用しています。
#!/usr/bin/env sh
brew install imagemagick
light_dir=~/Downloads/light
dark_dir=~/Downloads/dark
assets_dir=SARAH/Resources/Icons.xcassets
tmpdir=$(mktemp -d)
trap "rm -rf $tmpdir" 0 2
# compare by pixel
function is_identical() {
if [ ! $# = 2 ] ; then
echo "is_identical(): Invalid arguments: $*"
exit 1
fi
if [ ! -f $1 -o ! -f $2 ] ; then
return 1
fi
compare -metric AE $1 $2 /dev/null >& /dev/null
return $?
}
# copy only if not identical
function overwrite() {
file=`basename $2`
if is_identical $1 $2 ; then
echo "Skip: $file"
return
fi
echo "Copy: $file"
cp $1 $2
}
# Create any-dark imageset
function create_dual_imageset() {
file=$1
file_base=${file%.*}
light_icon=$light_dir/$file
dark_icon=$dark_dir/$file
imageset=$assets_dir/$file_base".imageset"
mkdir -p $imageset
light_pdf=$file_base"_light.pdf"
dark_pdf=$file_base"_dark.pdf"
overwrite $light_icon $imageset/$light_pdf
overwrite $dark_icon $imageset/$dark_pdf
cat <<- EOF > $imageset/Contents.json
{
"images" : [
{
"filename" : "$light_pdf",
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"filename" : "$dark_pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"preserves-vector-representation" : true
}
}
EOF
}
# Create any imageset
function create_any_imageset() {
file=$1
file_base=${file%.*}
icon=$light_dir/$file
imageset=$assets_dir/$file_base".imageset"
#rm -rf $imageset
mkdir -p $imageset
overwrite $icon $imageset/$file
cat <<- EOF > $imageset/Contents.json
{
"images" : [
{
"filename" : "$file",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"preserves-vector-representation" : true
}
}
EOF
}
# Usage
if [ ! -d $light_dir -o ! -d $dark_dir ] ; then
cat <<- EOF
1. Download all icons by "Download all assets" with "Original" naming convention:
- https://app.zeplin.io/project/[light]
- https://app.zeplin.io/project/[dark]
2. Unzip those as:
- $light_dir -- light icons
- $dark_dir -- dark icons
EOF
exit 1
fi
# Diff
ls $light_dir > $tmpdir/light.txt
ls $dark_dir > $tmpdir/dark.txt
if ! diff $tmpdir/light.txt $tmpdir/dark.txt ; then
echo "There are differences."
exit 1
fi
# Clean assets
# TODO: create diff and remove
#for file in $assets_dir/*.imageset ; do
# rm -rf $file
#done
# Import
icons=`cd $light_dir; ls -1`
for file in $icons ; do
file_base=${file%.*}
light_icon=$light_dir/$file
dark_icon=$dark_dir/$file
if is_identical $light_icon $dark_icon ; then
create_any_imageset $file
else
create_dual_imageset $file
fi
done
今後の課題
- 削除されたIconに対応する
- エクスポートまでデザイナーにお願いして、GitHub Actionで取り込むPRを作る
最後に
最初はポチポチ登録してたので、一括インポートは感動でした😭
あとimagemagickめちゃ便利、という話でした。
他にもこんな方法もあるよとか、フィードバックもぜひお願いします!
(SketchやFigmaからエクスポートした場合はどうなるんだろう…?🤔)