Comment trouver des images inutilisées dans un projet Xcode?

97

Quelqu'un a-t-il une ligne pour trouver des images inutilisées dans un projet Xcode? (En supposant que tous les fichiers sont référencés par nom dans le code ou les fichiers de projet - aucun nom de fichier généré par le code.)

Ces fichiers ont tendance à s'accumuler au cours de la vie d'un projet et il peut être difficile de dire s'il est sûr de supprimer un png donné.

Paul Robinson
la source
4
Cela fonctionne-t-il également pour XCode4? Cmd-Opt-A dans XCode4 semble ouvrir la boîte de dialogue "Ajouter des fichiers".
Rajavanya Subramaniyan

Réponses:

61

Pour les fichiers qui ne sont pas inclus dans le projet, mais qui restent simplement dans le dossier, vous pouvez appuyer sur

cmd ⌘+ alt ⌥+A

et ils ne seront pas grisés.

Pour les fichiers qui ne sont référencés ni dans xib ni dans le code, quelque chose comme ça pourrait fonctionner:

#!/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
romain
la source
6
Si vous rencontrez une erreur: aucun fichier ou répertoire de ce type, cela est probablement dû aux espaces dans le chemin du fichier. Les guillemets doivent être ajoutés dans la ligne grep, donc ça va: si! grep -qhs "$ nom" "$ PROJ";
Lukasz
8
Un scénario où cela ne fonctionnerait pas est celui où nous pourrions charger des images par programme après avoir construit leurs noms. Comme arm1.png, arm2.png .... arm22.png. Je pourrais construire leurs noms dans la boucle for et charger. Eg Games
Rajavanya Subramaniyan
Si vous avez des images pour l'affichage Retina nommées avec @ 2x, elles seront répertoriées comme inutilisées. Vous pouvez vous en débarrasser en ajoutant une instruction if supplémentaire: if [["$ name"! = @ 2x ]]; puis
Sten
3
Cmd + Opt + a ne semble plus fonctionner sur XCode 5. Que doit-il déclencher?
powtac
cmd + opt + a ne semble pas griser les fichiers dans Images.xcassets même s'ils font partie du projet :(
tettoffensive
80

C'est une solution plus robuste - elle vérifie toute référence au nom de base dans n'importe quel fichier texte. Notez les solutions ci-dessus qui n'incluaient pas les fichiers de storyboard (tout à fait compréhensible, ils n'existaient pas à l'époque).

Ack rend cela assez rapide, mais il y a des optimisations évidentes à faire si ce script s'exécute fréquemment. Ce code vérifie chaque nom de base deux fois si vous avez à la fois des actifs rétine / non-rétine, par exemple.

#!/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
Ed McManus
la source
12
Installez Homebrew , puis effectuez un brew install ack.
Marko
1
Merci. Cette réponse gère également correctement les fichiers et les dossiers avec des espaces.
djskinner
2
@Johnny vous devez rendre le fichier exécutable ( chmod a+x FindUnusedImages.sh), puis l'exécuter comme n'importe quel autre programme de bash./FindUnusedImages.sh
Mike Sprague
2
J'ai fait une modification pour ignorer les fichiers pbxproj (ignorant ainsi les fichiers qui sont dans le projet xcode, mais non utilisés dans le code ou les nibs / storyboards): result=`ack --ignore-file=match:/.\.pbxproj/ -i "$file"` Cela nécessite ack 2.0 et plus
Mike Sprague
2
milanpanchal, vous pouvez placer le script n'importe où, et vous l'exécutez simplement à partir du répertoire que vous souhaitez utiliser comme racine pour rechercher des images (par exemple, le dossier racine de votre projet). Vous pouvez le mettre dans ~ / script / par exemple, puis aller dans le dossier racine de votre projet et l'exécuter en pointant directement sur le script: ~ / script / unknown_images.sh
Erik van der Neut
25

Veuillez essayer LSUnusedResources .

Il est fortement influencée par de jeffhodnett non utilisée , mais honnêtement inutilisé est très lent, et les résultats ne sont pas tout à fait correct. J'ai donc optimisé les performances, la vitesse de recherche est plus rapide que Inutilisé.

LessFun
la source
2
Wow, c'est un excellent outil! Bien plus agréable que d'essayer d'exécuter ces scripts. Vous pouvez voir visuellement toutes les images non utilisées et supprimer celles que vous souhaitez. Un problème que j'ai trouvé, c'est qu'il ne récupère pas les images référencées dans le plist
RyanG
1
Vraiment génial et sauve ma journée! Meilleure solution en filetage. Tu gères.
Jakehao
2
Meilleur dans le fil. J'aurais aimé que ce soit plus haut et que je puisse voter plus d'une fois!
Yoav Schwartz
Savez-vous s'il existe quelque chose de similaire, mais pour la détection de code mort? Par exemple, pour les méthodes qui ne sont plus appelées (du moins plus appelées statiquement ).
superpuccio du
24

J'ai essayé la solution de Roman et j'ai ajouté quelques modifications pour gérer les images de la rétine. Cela fonctionne bien, mais rappelez-vous que les noms d'images peuvent être générés par programme dans le code, et ce script listerait à tort ces images comme non référencées. Par exemple, vous pourriez avoir

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

Ce script pensera à tort qu'il image_1.pngn'est pas référencé.

Voici le script modifié:

#!/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
Rob
la source
que fait le @ 2x dans le commutateur de suffixe pour basename?
ThaDon
3
Pour info, les dossiers avec des espaces dans le nom provoquent des problèmes avec le script.
Steve
3
Si vous rencontrez une erreur: aucun fichier ou répertoire de ce type, cela est probablement dû aux espaces dans le chemin du fichier. Les guillemets doivent être ajoutés dans la ligne grep, donc ça va: si! grep -qhs "$ nom" "$ PROJ";
Lukasz
3
Ce script liste tous mes fichiers
jjxtra
2
Je ne sais pas pourquoi cela ne fonctionne pas pour moi, il me donne toutes les images png
Omer Obaid
12

Peut-être que vous pouvez essayer mince , fait un travail décent.

mise à jour: Avec l'idée d'emcmanus, je suis allé de l'avant et j'ai créé un petit utilitaire sans ack juste pour éviter une configuration supplémentaire dans une machine.

https://github.com/arun80/xcodeutils

Arun
la source
1
Slender est une application payante. plusieurs faux positifs et pas bons pour les produits commerciaux. le script fourni par emcmanus est vraiment génial.
Arun
6

Seul ce script fonctionne pour moi et gère même l'espace dans les noms de fichiers:

Éditer

Mis à jour pour prendre en charge les swiftfichiers et cocoapod. Par défaut, il exclut le répertoire Pods et ne vérifie que les fichiers du projet. Pour vérifier également le dossier Pods, exécutez avec --podattrbiute:

/.finunusedimages.sh --pod

Voici le script actuel:

#!/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
Ingaham
la source
Ce script a marqué trop de ressources utilisées comme inutilisées . Améliorations nécessaires.
Artem Shmatkov
N'aime pas non plus les grandes et profondes hiérarchies de projets: ./findunused.sh: ligne 28: / usr / bin / grep: Liste d'arguments trop longue
Martin-Gilles Lavoie
3

J'ai apporté une très légère modification à l'excellente réponse fournie par @EdMcManus pour gérer des projets utilisant des catalogues d'actifs.

#!/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

Je n'écris pas vraiment de scripts bash, donc s'il y a des améliorations à apporter ici (probablement) faites-le moi savoir dans les commentaires et je le mettrai à jour.

Stakenborg
la source
J'ai un problème avec les espaces dans le nom des fichiers. J'ai découvert qu'il est utile de définir `IFS = $ '\ n' ', juste avant le code (celui-ci définit le séparateur de champ interne sur une nouvelle ligne) - ne fonctionnera pas si à nouveau les fichiers ont de nouvelles lignes dans le nom.
Laura Calinoiu
2

Vous pouvez créer un script shell que grepvotre code source et comparer les images fondées avec votre dossier de projet.

Ici le (s) homme (s) pour GREPetLS

Vous pouvez facilement mettre en boucle tout votre fichier source, enregistrer des images dans un tableau ou quelque chose d'équivalent et utiliser

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

Avec cette astuce, vous pouvez rechercher toutes les images dans le code source de votre projet !!

J'espère que cela t'aides!

elp
la source
2

J'ai écrit un script lua, je ne suis pas sûr de pouvoir le partager car je l'ai fait au travail, mais cela fonctionne bien. En gros, il fait ceci:

Première étape - références d'images statiques (le plus facile, couvert par les autres réponses)

  • regarde récursivement dans les répertoires d'images et extrait les noms d'images
  • supprime les noms d'image .png et @ 2x (non requis / utilisé dans imageNamed :)
  • recherche textuellement chaque nom d'image dans les fichiers source (doit être dans une chaîne littérale)

Deuxième étape - Références d'images dynamiques (le plus amusant)

  • extrait une liste de tous les littéraux de chaîne dans la source contenant des spécificateurs de format (par exemple,% @)
  • remplace les spécificateurs de format dans ces chaînes par des expressions régulières (par exemple, "foo% dbar" devient "foo [0-9] * bar"
  • recherche textuellement dans les noms d'image à l'aide de ces chaînes d'expression régulière

Supprime ensuite tout ce qu'il n'a trouvé dans aucune des deux recherches.

Le cas limite est que les noms d'image provenant d'un serveur ne sont pas gérés. Pour gérer cela, nous incluons le code du serveur dans cette recherche.

Sam
la source
Soigné. Par curiosité, existe-t-il un utilitaire pour transformer les spécificateurs de format en expressions rationnelles génériques? Je pense simplement qu'il y a beaucoup de complexité à gérer pour s'adapter avec précision à tous les prescripteurs et plates-formes. (Documentation sur le spécificateur de format)
Ed McManus
2

Vous pouvez essayer l' application FauxPas pour Xcode . Il est vraiment bon pour trouver les images manquantes et beaucoup d'autres problèmes / violations liés au projet Xcode.

Kunal Shah
la source
On dirait que cela n'a pas été mis à jour depuis Xcode 9. Peut confirmer que cela ne fonctionne pas avec Xcode 11.
Robin Daugherty
2

En utilisant les autres réponses, celle-ci est un bon exemple de la façon d'ignorer les images sur deux répertoires et de ne pas rechercher les occurrences des images sur les fichiers pbxproj ou xcassets (soyez prudent avec l'icône de l'application et les écrans de démarrage). Modifiez le * dans le --ignore-dir = *. Xcassets pour correspondre à votre répertoire:

#!/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
Gabriel Madruga
la source
2

J'ai utilisé ce cadre: -

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

Fonctionne très bien! Seuls 2 endroits où j'ai vu des problèmes sont lorsque les noms d'image proviennent du serveur et lorsque le nom de l'actif d'image est différent du nom de l'image dans le dossier d'actif ...

Swasidhant
la source
Cela ne recherche pas les actifs, uniquement les fichiers image qui ne sont pas directement référencés. Si vous utilisez Assets comme vous le devriez, cet outil ne fonctionnera malheureusement pas pour vous.
Robin Daugherty
0

Utilisez http://jeffhodnett.github.io/Unused/ pour trouver les images inutilisées.

Praveen Matanam
la source
Il me semble que ni cette application ne gère bien l'espace dans les noms de dossier. Et c'est assez lent pour l'un de mes plus gros projets.
ingaham
0

J'ai créé un script python pour identifier les images inutilisées: 'unused_assets.py' @ gist . Il peut être utilisé comme ceci:

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

Voici quelques règles pour utiliser le script:

  • Il est important de transmettre le chemin du dossier du projet comme premier argument, le chemin du dossier des ressources comme deuxième argument
  • Il est supposé que toutes les images sont conservées dans le dossier Assets.xcassets et sont utilisées soit dans des fichiers Swift, soit dans des storyboards

Limitations de la première version:

  • Ne fonctionne pas pour les fichiers objectif c

Je vais essayer de l'améliorer au fil du temps, en fonction des retours, mais la première version devrait être bonne pour la plupart.

Veuillez trouver ci-dessous le code. Le code doit être explicite car j'ai ajouté des commentaires appropriés à chaque étape importante .

# 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!")
Devarshi
la source