Come trovare immagini inutilizzate in un progetto Xcode?


97

Qualcuno ha una riga per trovare immagini inutilizzate in un progetto Xcode? (Supponendo che tutti i file siano referenziati in base al nome nel codice o nei file di progetto, nessun nome di file generato dal codice.)

Questi file tendono a accumularsi durante la vita di un progetto e può essere difficile dire se è sicuro eliminare un dato png.


4
Funziona anche con XCode4? Cmd-Opt-A in XCode4 sembra aprire la finestra di dialogo "Aggiungi file".
Rajavanya Subramaniyan

Risposte:


61

Per i file che non sono inclusi nel progetto, ma semplicemente aggirati nella cartella, è possibile premere

cmd ⌘+ alt ⌥+A

e non saranno disattivati.

Per i file che non sono referenziati né in xib né nel codice, qualcosa del genere potrebbe funzionare:

#!/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
Se si verifica un errore: nessun file o directory di questo tipo, è probabilmente dovuto agli spazi nel percorso del file. Le virgolette devono essere aggiunte nella riga grep, quindi va: if! grep -qhs "$ name" "$ PROJ";
Lukasz

8
Uno scenario in cui questo non funzionerebbe è quando potremmo caricare le immagini a livello di codice dopo aver costruito i loro nomi. Come arm1.png, arm2.png .... arm22.png. Potrei costruire i loro nomi nel ciclo for e caricare. Es. Giochi
Rajavanya Subramaniyan

Se si dispone di immagini per il display retina denominate con @ 2x, verranno elencate come inutilizzate. Puoi sbarazzartene aggiungendo un'istruzione if extra: if [["$ name"! = @ 2x ]]; poi
Sten

3
Cmd + Opt + a sembra non funzionare più su XCode 5. Cosa dovrebbe attivare?
powtac

cmd + opt + a non sembra disattivare i file in Images.xcassets anche se fanno parte del progetto :(
tettoffensive

80

Questa è una soluzione più robusta: controlla qualsiasi riferimento al nome di base in qualsiasi file di testo. Nota le soluzioni sopra che non includevano i file dello storyboard (completamente comprensibile, non esistevano al momento).

Ack lo rende abbastanza veloce, ma ci sono alcune ovvie ottimizzazioni da fare se questo script viene eseguito frequentemente. Questo codice controlla ogni nome di base due volte se si dispone di entrambe le risorse retina / non retina, ad esempio.

#!/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
Installa Homebrew e poi esegui un file brew install ack.
Marko

1
Grazie. Questa risposta gestisce correttamente anche file e cartelle con spazi.
djskinner

2
@ Johnny devi rendere il file eseguibile ( chmod a+x FindUnusedImages.sh), quindi eseguirlo come qualsiasi altro programma da bash./FindUnusedImages.sh
Mike Sprague

2
Ho apportato una modifica per ignorare i file pbxproj (ignorando così i file che si trovano nel progetto xcode, ma non utilizzati nel codice o nei pennini / storyboard): result=`ack --ignore-file=match:/.\.pbxproj/ -i "$file"` Ciò richiede ack 2.0 e versioni successive
Mike Sprague

2
milanpanchal, puoi mettere lo script ovunque e basta eseguirlo da qualsiasi directory tu voglia usare come root per la ricerca di immagini (es. la cartella root del tuo progetto). Puoi metterlo in ~ / script / per esempio e poi andare alla cartella principale del tuo progetto ed eseguirlo puntando direttamente allo script: ~ / script / unused_images.sh
Erik van der Neut

25

Si prega di provare LSUnusedResources .

Si è fortemente influenzato da jeffhodnett di non utilizzato , ma onestamente non utilizzato è molto lento, ed i risultati non sono del tutto corrette. Quindi ho ottimizzato le prestazioni, la velocità di ricerca è più veloce di Non utilizzato.


2
Wow, è un ottimo strumento! Molto più carino che provare a eseguire quegli script. Puoi vedere visivamente tutte le immagini non utilizzate ed eliminare quelle che desideri. Un problema che ho trovato è che non raccoglie immagini a cui si fa riferimento nel plist
RyanG

1
Decisamente fantastico e salva la mia giornata! Migliore soluzione in thread. Sei forte.
Jakehao

2
Il migliore in thread. Vorrei che fosse più in alto e potessi votare più di una volta!
Yoav Schwartz

Sai se c'è qualcosa di simile a questo ma per il rilevamento del codice morto? Ad esempio, per i metodi non più chiamati (almeno non più chiamati staticamente ).
superpuccio

24

Ho provato la soluzione di Roman e ho aggiunto alcune modifiche per gestire le immagini della retina. Funziona bene, ma ricorda che i nomi delle immagini possono essere generati a livello di codice nel codice e questo script elenca erroneamente queste immagini come non referenziate. Ad esempio, potresti avere

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

Questo script penserà erroneamente che image_1.pngnon sia referenziato.

Ecco lo script modificato:

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

cosa fa @ 2x nell'interruttore del suffisso per basename?
ThaDon

3
Cordiali saluti, le cartelle con spazi nel nome causano problemi con lo script.
Steve

3
Se si verifica un errore: nessun file o directory di questo tipo, è probabilmente dovuto agli spazi nel percorso del file. Le virgolette devono essere aggiunte nella riga grep, quindi va: if! grep -qhs "$ name" "$ PROJ";
Lukasz

3
Questo script elenca tutti i miei file
jjxtra

2
Non so perché non funziona per me, mi dà tutte le immagini png
Omer Obaid

12

Forse puoi provare snello , fa un lavoro decente.

aggiornamento: con l'idea di emcmanus, sono andato avanti e ho creato una piccola utility senza ack solo per evitare configurazioni aggiuntive in una macchina.

https://github.com/arun80/xcodeutils


1
Slender è un'app a pagamento. diversi falsi positivi e non va bene per i prodotti commerciali. lo script fornito da emcmanus è davvero fantastico.
Arun

6

Solo questo script funziona per me che gestisce anche lo spazio nei nomi dei file:

modificare

Aggiornato per supportare swiftfile e cocoapod. Per impostazione predefinita, esclude la directory Pod e controlla solo i file di progetto. Per correre a controllare anche la cartella Pods, esegui con --podattrbiute:

/.finunusedimages.sh --pod

Ecco lo script vero e proprio:

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

Questo script ha contrassegnato troppe risorse utilizzate come inutilizzate . Miglioramenti necessari.
Artem Shmatkov

Inoltre non ama le gerarchie di progetto grandi e profonde: ./findunused.sh: riga 28: / usr / bin / grep: Elenco argomenti troppo lungo
Martin-Gilles Lavoie

3

Ho apportato una leggera modifica all'eccellente risposta fornita da @EdMcManus per gestire i progetti che utilizzano cataloghi di risorse.

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

Non scrivo davvero script bash, quindi se ci sono miglioramenti da apportare qui (probabilmente) fammelo sapere nei commenti e lo aggiornerò.


Ho un problema con gli spazi nel nome dei file. Ho scoperto che è utile impostare `IFS = $ '\ n'`, subito prima del codice (questo imposta il separatore di campo interno su una nuova riga) - non funzionerà se ancora i file hanno nuove righe nel nome.
Laura Calinoiu

2

Puoi creare uno script di shell come grepcodice sorgente e confrontare le immagini trovate con la cartella del tuo progetto.

Qui l'uomo (i) per GREPeLS

Puoi facilmente riprodurre in loop tutto il tuo file sorgente, salvare le immagini in array o qualcosa di uguale e utilizzare

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

Con questo trucco, puoi cercare tutte le immagini nel codice sorgente del tuo progetto !!

spero che questo ti aiuti!


2

Ho scritto uno script lua, non sono sicuro di poterlo condividere perché l'ho fatto al lavoro, ma funziona bene. Fondamentalmente lo fa:

Passaggio uno: riferimenti a immagini statiche (la parte facile, coperta dalle altre risposte)

  • cerca in modo ricorsivo le directory delle immagini e ne estrae i nomi
  • rimuove i nomi delle immagini di .png e @ 2x (non richiesto / utilizzato in imageNamed :)
  • cerca testualmente il nome di ogni immagine nei file di origine (deve essere all'interno di una stringa letterale)

Passaggio due: riferimenti dinamici alle immagini (la parte divertente)

  • estrae un elenco di tutti i valori letterali di stringa nella sorgente contenente gli identificatori di formato (ad esempio,% @)
  • sostituisce gli specificatori di formato in queste stringhe con espressioni regolari (ad esempio, "foo% dbar" diventa "foo [0-9] * bar"
  • cerca testualmente attraverso i nomi delle immagini usando queste stringhe regex

Quindi elimina tutto ciò che non ha trovato in nessuna delle due ricerche.

Il caso limite è che i nomi delle immagini che provengono da un server non vengono gestiti. Per gestire questo, includiamo il codice del server in questa ricerca.


Neat. Per curiosità c'è qualche utilità per trasformare gli specificatori di formato in espressioni regolari con caratteri jolly? Sto solo pensando che c'è molta complessità che dovresti gestire per soddisfare accuratamente tutti gli specificatori e le piattaforme. (Documenti specificatori di formato)
Ed McManus

2

Puoi provare l'app FauxPas per Xcode . È davvero utile trovare le immagini mancanti e molti altri problemi / violazioni relativi al progetto Xcode.


Sembra che questo non sia stato aggiornato da Xcode 9. Posso confermare che non funziona con Xcode 11.
Robin Daugherty

2

Usando le altre risposte, questo è un buon esempio di come ignorare le immagini su due directory e non cercare le occorrenze delle immagini sui file pbxproj o xcassets (fai attenzione con l'icona dell'app e le schermate iniziali). Cambia * in --ignore-dir = *. Xcassets in modo che corrisponda alla tua directory:

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

Ho usato questo framework: -

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

Funziona dannatamente bene! Solo 2 punti in cui ho riscontrato problemi sono quando i nomi delle immagini provengono dal server e quando il nome dell'asset dell'immagine è diverso dal nome dell'immagine all'interno della cartella degli asset ...


Non cerca risorse, solo file di immagine a cui non si fa riferimento diretto. Se stai usando le risorse come dovresti, questo strumento purtroppo non funzionerà per te.
Robin Daugherty


0

Ho creato uno script Python per identificare le immagini inutilizzate: 'unused_assets.py' @ gist . Può essere usato in questo modo:

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

Ecco alcune regole per utilizzare lo script:

  • È importante passare il percorso della cartella del progetto come primo argomento, il percorso della cartella delle risorse come secondo argomento
  • Si presume che tutte le immagini siano conservate nella cartella Assets.xcassets e siano utilizzate all'interno di file swift o all'interno di storyboard

Limitazioni nella prima versione:

  • Non funziona per i file c oggettivi

Cercherò di migliorarlo nel tempo, in base al feedback, tuttavia la prima versione dovrebbe essere buona per la maggior parte.

Di seguito trovi il codice. Il codice dovrebbe essere autoesplicativo poiché ho aggiunto commenti appropriati a ogni passaggio 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!")

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.