Xcodeプロジェクトで未使用の画像を見つける方法は?


97

Xcodeプロジェクトで未使用の画像を見つけるための1行はありますか?(すべてのファイルがコードまたはプロジェクトファイルで名前によって参照されていると仮定します-コード生成されたファイル名はありません。)

これらのファイルはプロジェクトの存続期間にわたって蓄積する傾向があり、特定のpngを削除しても安全かどうかを判断するのは難しい場合があります。


4
これはXCode4でも機能しますか?XCode4のCmd-Opt-Aが「ファイルの追加」ダイアログを開くようです。
Rajavanya Subramaniyan 2013

回答:


61

プロジェクトに含まれていないが、フォルダにたどり着くだけのファイルの場合は、

cmd ⌘+ alt ⌥+A

そして、それらは淡色表示されません。

xibでもコードでも参照されていないファイルの場合、次のようなものが機能する可能性があります。

#!/bin/sh
PROJ=`find . -name '*.xib' -o -name '*.[mh]'`

find . -iname '*.png' | while read png
do
    name=`basename $png`
    if ! grep -qhs "$name" "$PROJ"; then
        echo "$png is not referenced"
    fi
done

6
エラーが発生した場合:そのようなファイルまたはディレクトリはありません。ファイルパスのスペースが原因である可能性があります。引用符はgrep行に追加する必要があるため、次のようになります。grep -qhs "$ name" "$ PROJ";
Lukasz

8
これがうまくいかないシナリオの1つは、名前を作成した後にプログラムで画像を読み込む場合です。arm1.png、arm2.png .... arm22.pngなど。forループとロードでそれらの名前を作成する場合があります。例:ゲーム
-Rajavanya Subramaniyan 2013

@ 2xという名前のRetinaディスプレイ用の画像がある場合、それらは未使用としてリストされます。if文を追加することで、それを取り除くことができます。if [["$ name"!= @ 2x ]]; そして
ステン

3
Cmd + Opt + aはXCode 5では動作しないようです。何がトリガーされますか?
powtac 14

cmd + opt + aは、プロジェクトの一部であるにもかかわらず、Images.xcassets内のファイルを灰色表示しないようです:(
tettoffensive

80

これは、より堅牢なソリューションである-それはのためにチェックする任意の任意のテキストファイル内のベース名を参照します。ストーリーボードファイルを含まない上記のソリューションに注意してください(完全に理解可能で、当時は存在しませんでした)。

Ackはこれをかなり高速にしますが、このスクリプトが頻繁に実行される場合は、いくつかの明らかな最適化を行う必要があります。たとえば、retinaとnon-retinaの両方のアセットがある場合、このコードはすべてのベース名を2回チェックします。

#!/bin/bash

for i in `find . -name "*.png" -o -name "*.jpg"`; do 
    file=`basename -s .jpg "$i" | xargs basename -s .png | xargs basename -s @2x`
    result=`ack -i "$file"`
    if [ -z "$result" ]; then
        echo "$i"
    fi
done

# Ex: to remove from git
# for i in `./script/unused_images.sh`; do git rm "$i"; done

12
Homebrewをインストールしてから、を実行しbrew install ackます。
Marko

1
ありがとう。この回答では、スペースが含まれているファイルとフォルダーも正しく処理されます。
djskinner 2012年

2
@Johnnyは、ファイルを実行可能(chmod a+x FindUnusedImages.sh)にして、bashの他のプログラムと同じように実行する必要があります./FindUnusedImages.sh
Mike Sprague

2
pbxprojファイルを無視するように変更しました(したがって、xcodeプロジェクトにあるが、コードまたはnibs / storyboardsでは使用されていません): result=`ack --ignore-file=match:/.\.pbxproj/ -i "$file"` これにはack 2.0以降が必要です
Mike Sprague

2
milanpanchalでは、スクリプトをどこにでも置くことができ、画像を検索するためのルートとして使用する任意のディレクトリ(プロジェクトのルートフォルダーなど)からスクリプトを実行するだけです。たとえば、〜/ script /に配置してから、プロジェクトのルートフォルダーに移動し、スクリプトを直接指定して実行することができます:〜/ script / unused_images.sh
Erik van der

25

LSUnusedResourcesを試してみてください。

jeffhodnettのUnusedの影響を強く受けていますが、正直に言って、Unusedは非常に遅く、結果は完全に正しくありません。そこで、パフォーマンスの最適化を行いました。検索速度は未使用よりも高速です。


2
すばらしいツールです。これらのスクリプトを実行するよりもはるかに優れています。使用されていないすべての画像を視覚的に確認し、必要な画像を削除できます。私が見つけた1つの
落とし穴

1
間違いなく素晴らしいと私の日を救います!スレッドでの最適なソリューション。あなたはロックします。
Jakehao

2
スレッドで最高の1つ。これがもっと高くて、何度も賛成票を投じることができればいいのに!
Yoav Schwartz 2017

これに似ているがデッドコード検出のためのものかどうか知っていますか?たとえば、もはや呼び出されなくなったメソッドの場合(少なくとも静的に呼び出されなくなった場合)。
superpuccio 2017年

24

Romanの解決策を試し、網膜の画像を処理するためにいくつかの調整を加えました。これはうまく機能しますが、イメージ名はプログラムでコードで生成でき、このスクリプトはこれらのイメージを参照されていないものとして誤ってリストすることに注意してください。たとえば、

NSString *imageName = [NSString stringWithFormat:@"image_%d.png", 1];

このスクリプトimage_1.pngは、参照されていないと誤って見なします。

変更されたスクリプトは次のとおりです。

#!/bin/sh
PROJ=`find . -name '*.xib' -o -name '*.[mh]' -o -name '*.storyboard' -o -name '*.mm'`

for png in `find . -name '*.png'`
do
   name=`basename -s .png $png`
   name=`basename -s @2x $name`
   if ! grep -qhs "$name" "$PROJ"; then
        echo "$png"
   fi
done

ベース名の接尾辞スイッチで@ 2xは何をしますか?
ThaDon、2011

3
参考までに、名前にスペースが含まれているフォルダは、スクリプトで問題を引き起こします。
Steve

3
エラーが発生した場合:そのようなファイルまたはディレクトリはありません。ファイルパスのスペースが原因である可能性があります。引用符はgrep行に追加する必要があるため、次のようになります。grep -qhs "$ name" "$ PROJ";
Lukasz

3
このスクリプトは、すべてのファイルをリストします
jjxtra '18

2
なぜ私には役に立たないのかわからないpng画像がすべて表示される
Omer Obaid

12

あなたは細身を試すことができるかもしれません、まともな仕事をします。

更新: emcmanusのアイデアで、私は先に進んで、マシンでの追加のセットアップを回避するためだけに確認なしで小さなユーティリティを作成しました。

https://github.com/arun80/xcodeutils


1
スレンダーは有料アプリです。いくつかの誤検知があり、商用製品には適していません。emcmanusが提供するスクリプトは本当に素晴らしいです。
2012年

6

ファイル名のスペースも処理するこのスクリプトだけが私のために機能しています:

編集する

swiftファイルとをサポートするように更新されましたcocoapod。デフォルトでは、ポッドディレクトリは除外され、プロジェクトファイルのみがチェックされます。実行して--podPods フォルダーもチェックするには、attrbiute を指定して実行します。

/.finunusedimages.sh --pod

実際のスクリプトは次のとおりです。

#!/bin/sh

#varables
baseCmd="find ." 
attrs="-name '*.xib' -o -name '*.[mh]' -o -name '*.storyboard' -o -name '*.mm' -o -name '*.swift'"
excudePodFiles="-not \( -path  */Pods/* -prune \)"
imgPathes="find . -iname '*.png' -print0"


#finalize commands
if [ "$1" != "--pod" ]; then
    echo "Pod files excluded"
    attrs="$excudePodFiles $attrs"
    imgPathes="find . $excudePodFiles -iname '*.png' -print0"
fi

#select project files to check
projFiles=`eval "$baseCmd $attrs"`
echo "Looking for in files: $projFiles"

#check images
eval "$imgPathes" | while read -d $'\0' png
do
   name=`basename -s .png "$png"`
   name=`basename -s @2x $name`
   name=`basename -s @3x $name`

   if grep -qhs "$name" $projFiles; then
        echo "(used - $png)"
   else
        echo "!!!UNUSED - $png"
   fi
done

このスクリプトは、あまりにも多くの使用済みリソースを未使用としてマークしています。改善が必要です。
Artem Shmatkov 16

また、大きな、深いプロジェクト階層好きではない:./findunused.sh:行28:は/ usr /ビン/ grepを:引数リストが長すぎる
マーティン・ジルLavoie

3

@EdMcManusから提供された優れた回答を少し変更して、アセットカタログを利用するプロジェクトを処理しました。

#!/bin/bash

for i in `find . -name "*.imageset"`; do
    file=`basename -s .imageset "$i"`
    result=`ack -i "$file" --ignore-dir="*.xcassets"`
    if [ -z "$result" ]; then
        echo "$i"
    fi
done

私は実際にはbashスクリプトを作成していないので、ここで改善すべき点がある場合は(おそらく)コメントでお知らせください。更新します。


ファイル名のスペースに問題があります。`IFS = $ '\ n'`をコードの直前に設定すると便利であることがわかりました(これにより、内部フィールドセパレーターが新しい行に設定されます)。ファイルの名前に新しい行がある場合は機能しません。
Laura Calinoiu 2017年

2

あなたはgrepあなたのソースコードにシェルスクリプトを作って、そしてあなたの創設されたイメージをあなたのプロジェクトフォルダーと比較することができます。

ここでの男性GREPLS

すべてのソースファイルをループし、画像を配列または同等のものに保存して簡単に使用できます

cat file.m | grep [-V] myImage.png

このトリックを使用すると、プロジェクトのソースコード内のすべての画像を検索できます。

お役に立てれば!


2

私はluaスクリプトを書きましたが、仕事でそれを共有したので共有できるかわかりませんが、うまくいきます。基本的にはこれを行います:

ステップ1-静的画像参照(簡単なビット、他の回答でカバー)

  • 画像ディレクトリを再帰的に調べ、画像名を引き出します
  • .pngと@ 2xの画像名を取り除きます(imageNamed:では不要/使用されます)。
  • ソースファイル内の各画像名をテキストで検索します(文字列リテラル内にある必要があります)

ステップ2-動的画像参照(楽しいビット)

  • 書式指定子(たとえば、%@)を含むソース内のすべての文字列リテラルのリストを引き出します
  • これらの文字列のフォーマット指定子を正規表現で置き換えます(たとえば、「foo%dbar」は「foo [0-9] * bar」になります)
  • これらの正規表現文字列を使用して画像名をテキストで検索します

次に、どちらの検索でも見つからなかったものをすべて削除します。

最悪のケースは、サーバーから取得されたイメージ名が処理されないことです。これを処理するために、この検索にサーバーコードを含めます。


きちんと。好奇心から、フォーマット指定子をワイルドカード正規表現に変換するためのユーティリティはありますか?すべての指定子とプラットフォームに正確に対応するために処理しなければならない複雑さがたくさんあると考えるだけです。(フォーマット指定子ドキュメント)
Ed McManus 2013年

2

Xcode用のFauxPas Appを試すことができます。不足している画像や、Xcodeプロジェクトに関連する他の多くの問題/違反を発見するのは本当に良いことです。


このように見えますが、それはXcodeの11で動作しないことをXcodeの9缶確認以降に更新されていません
ロビン・ドアティ

2

他の回答を使用すると、これは2つのディレクトリの画像を無視し、pbxprojまたはxcassetsファイルで画像の出現を検索しない方法の良い例です(アプリアイコンとスプラッシュスクリーンに注意してください)。--ignore-dir = *。xcassetsの*をディレクトリに一致するように変更します。

#!/bin/bash

for i in `find . -not \( -path ./Frameworks -prune \) -not \( -path ./Carthage -prune \) -not \( -path ./Pods -prune \) -name "*.png" -o -name "*.jpg"`; do 
    file=`basename -s .jpg "$i" | xargs basename -s .png | xargs basename -s @2x | xargs basename -s @3x`
    result=`ack -i --ignore-file=ext:pbxproj --ignore-dir=*.xcassets "$file"`
    if [ -z "$result" ]; then
        echo "$i"
    fi
done

2

私はこのフレームワークを使用しました:-

http://jeffhodnett.github.io/Unused/

うまくいきます!問題が発生したのは、画像名がサーバーからのものである場合と、画像アセット名がアセットフォルダー内の画像名と異なる場合の2箇所のみです...


これはアセットを検索せず、直接参照されていない画像ファイルのみを検索します。アセットを適切に使用している場合、このツールは残念ながら機能しません。
Robin Daugherty


0

未使用の画像を特定するPythonスクリプトを作成しました:'unused_assets.py' @ gist。次のように使用できます。

python3 unused_assets.py '/Users/DevK/MyProject' '/Users/DevK/MyProject/MyProject/Assets/Assets.xcassets'

スクリプトを使用するためのいくつかのルールを次に示します。

  • 最初の引数としてプロジェクトフォルダーパス、2番目の引数としてアセットフォルダーパスを渡すことが重要です
  • すべての画像がAssets.xcassetsフォルダー内に保持され、Swiftファイル内またはストーリーボード内で使用されることが想定されています

最初のバージョンの制限:

  • 目的のCファイルでは機能しません

私はフィードバックに基づいて、時間をかけてそれを改善しようとしますが、最初のバージョンはほとんどの人にとって良いはずです。

コードの下を見つけてください。重要な各ステップに適切なコメントを追加したので、コードは自明です。

# Usage e.g.: python3 unused_assets.py '/Users/DevK/MyProject' '/Users/DevK/MyProject/MyProject/Assets/Assets.xcassets'
# It is important to pass project folder path as first argument, assets folder path as second argument
# It is assumed that all the images are maintained within Assets.xcassets folder and are used either within swift files or within storyboards

"""
@author = "Devarshi Kulshreshtha"
@copyright = "Copyright 2020, Devarshi Kulshreshtha"
@license = "GPL"
@version = "1.0.1"
@contact = "kulshreshtha.devarshi@gmail.com"
"""

import sys
import glob
from pathlib import Path
import mmap
import os
import time

# obtain start time
start = time.time()

arguments = sys.argv

# pass project folder path as argument 1
projectFolderPath = arguments[1].replace("\\", "") # replacing backslash with space
# pass assets folder path as argument 2
assetsPath = arguments[2].replace("\\", "") # replacing backslash with space

print(f"assetsPath: {assetsPath}")
print(f"projectFolderPath: {projectFolderPath}")

# obtain all assets / images 
# obtain paths for all assets

assetsSearchablePath = assetsPath + '/**/*.imageset'  #alternate way to append: fr"{assetsPath}/**/*.imageset"
print(f"assetsSearchablePath: {assetsSearchablePath}")

imagesNameCountDict = {} # empty dict to store image name as key and occurrence count
for imagesetPath in glob.glob(assetsSearchablePath, recursive=True):
    # storing the image name as encoded so that we save some time later during string search in file 
    encodedImageName = str.encode(Path(imagesetPath).stem)
    # initializing occurrence count as 0
    imagesNameCountDict[encodedImageName] = 0

print("Names of all assets obtained")

# search images in swift files
# obtain paths for all swift files

swiftFilesSearchablePath = projectFolderPath + '/**/*.swift' #alternate way to append: fr"{projectFolderPath}/**/*.swift"
print(f"swiftFilesSearchablePath: {swiftFilesSearchablePath}")

for swiftFilePath in glob.glob(swiftFilesSearchablePath, recursive=True):
    with open(swiftFilePath, 'rb', 0) as file, \
        mmap.mmap(file.fileno(), 0, access=mmap.ACCESS_READ) as s:
        # search all the assests within the swift file
        for encodedImageName in imagesNameCountDict:
            # file search
            if s.find(encodedImageName) != -1:
                # updating occurrence count, if found 
                imagesNameCountDict[encodedImageName] += 1

print("Images searched in all swift files!")

# search images in storyboards
# obtain path for all storyboards

storyboardsSearchablePath = projectFolderPath + '/**/*.storyboard' #alternate way to append: fr"{projectFolderPath}/**/*.storyboard"
print(f"storyboardsSearchablePath: {storyboardsSearchablePath}")
for storyboardPath in glob.glob(storyboardsSearchablePath, recursive=True):
    with open(storyboardPath, 'rb', 0) as file, \
        mmap.mmap(file.fileno(), 0, access=mmap.ACCESS_READ) as s:
        # search all the assests within the storyboard file
        for encodedImageName in imagesNameCountDict:
            # file search
            if s.find(encodedImageName) != -1:
                # updating occurrence count, if found
                imagesNameCountDict[encodedImageName] += 1

print("Images searched in all storyboard files!")
print("Here is the list of unused assets:")

# printing all image names, for which occurrence count is 0
print('\n'.join({encodedImageName.decode("utf-8", "strict") for encodedImageName, occurrenceCount in imagesNameCountDict.items() if occurrenceCount == 0}))

print(f"Done in {time.time() - start} seconds!")

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.