Wie finde ich nicht verwendete Bilder in einem Xcode-Projekt?


97

Hat jemand eine einzeilige, um nicht verwendete Bilder in einem Xcode-Projekt zu finden? (Angenommen, alle Dateien werden im Code oder in den Projektdateien nach Namen referenziert - keine durch Code generierten Dateinamen.)

Diese Dateien neigen dazu, sich über die Laufzeit eines Projekts aufzubauen, und es kann schwierig sein zu sagen, ob es sicher ist, ein bestimmtes PNG zu löschen.


4
Funktioniert das auch für XCode4? Cmd-Opt-A in XCode4 scheint das Dialogfeld "Dateien hinzufügen" zu öffnen.
Rajavanya Subramaniyan

Antworten:


61

Für Dateien, die nicht im Projekt enthalten sind, sondern nur im Ordner herumhängen, können Sie drücken

cmd ⌘+ alt ⌥+A

und sie werden nicht ausgegraut sein.

Für Dateien, auf die weder in xib noch im Code verwiesen wird, könnte Folgendes funktionieren:

#!/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
Wenn Sie auf einen Fehler stoßen: Keine solche Datei oder kein solches Verzeichnis, liegt wahrscheinlich an den Leerzeichen im Dateipfad. Die Anführungszeichen müssen in grep-Zeile hinzugefügt werden, also geht es: wenn! grep -qhs "$ name" "$ PROJ";
Lukasz

8
Ein Szenario, in dem dies nicht funktionieren würde, ist das Laden von Bildern programmgesteuert nach dem Erstellen ihrer Namen. Wie arm1.png, arm2.png .... arm22.png. Ich könnte ihre Namen in der for-Schleife konstruieren und laden. ZB Spiele
Rajavanya Subramaniyan

Wenn Sie Bilder für die Retina-Anzeige mit dem Namen @ 2x haben, werden diese als nicht verwendet aufgeführt. Sie können dies beseitigen, indem Sie eine zusätzliche if-Anweisung hinzufügen: if [["$ name"! = @ 2x ]]; dann
Sten

3
Cmd + Opt + a scheint auf XCode 5 nicht mehr zu funktionieren. Was soll es auslösen?
Powtac

cmd + opt + a scheint Dateien in Images.xcassets nicht auszublenden, obwohl sie Teil des Projekts sind :(
tettoffensive

80

Dies ist eine robustere Lösung - es prüft jede Bezugnahme auf die Basisnamen in einer beliebigen Textdatei. Beachten Sie die obigen Lösungen, die keine Storyboard-Dateien enthielten (völlig verständlich, sie waren zu diesem Zeitpunkt noch nicht vorhanden).

Ack macht das ziemlich schnell, aber es gibt einige offensichtliche Optimierungen, die vorgenommen werden müssen, wenn dieses Skript häufig ausgeführt wird. Dieser Code überprüft jeden Basisnamen zweimal, wenn Sie beispielsweise sowohl Retina- als auch Nicht-Retina-Assets haben.

#!/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
Installieren Sie Homebrew und führen Sie dann a brew install ack.
Marko

1
Vielen Dank. Diese Antwort behandelt auch Dateien und Ordner mit Leerzeichen korrekt.
Djskinner

2
@ Johnny Sie müssen die Datei ausführbar machen ( chmod a+x FindUnusedImages.sh), und sie dann wie jedes andere Programm von bash ausführen./FindUnusedImages.sh
Mike Sprague

2
Ich habe eine Änderung vorgenommen, um pbxproj-Dateien zu ignorieren (also Dateien zu ignorieren, die sich im xcode-Projekt befinden, aber nicht in Code oder Nibs / Storyboards verwendet werden): result=`ack --ignore-file=match:/.\.pbxproj/ -i "$file"` Dies erfordert ack 2.0 und höher
Mike Sprague

2
milanpanchal können Sie das Skript an einer beliebigen Stelle ablegen und es einfach aus einem beliebigen Verzeichnis ausführen, das Sie als Stamm für die Suche nach Bildern verwenden möchten (z. B. Ihren Projektstammordner). Sie können es beispielsweise in ~ / script / ablegen und dann zu Ihrem Projektstammordner gehen und es ausführen, indem Sie direkt auf das Skript zeigen: ~ / script / unused_images.sh
Erik van der Neut

25

Bitte probieren Sie LSUnusedResources aus .

Es wird stark von Jeffhodnetts Unused beeinflusst , aber ehrlich gesagt ist Unused sehr langsam und die Ergebnisse sind nicht ganz korrekt. Deshalb habe ich einige Leistungsoptimierungen vorgenommen. Die Suchgeschwindigkeit ist schneller als bei Nicht verwendeten.


2
Wow das ist ein tolles Werkzeug! Viel schöner als zu versuchen, diese Skripte auszuführen. Sie können alle nicht verwendeten Bilder visuell anzeigen und die gewünschten löschen. Ein Problem, das ich jedoch festgestellt habe, ist, dass es keine Bilder
aufnimmt,

1
Auf jeden Fall super und rette meinen Tag! Beste Lösung im Thread. Du rockst.
Jakehao

2
Beste im Thread. Ich wünschte, das wäre höher und ich könnte mehr als einmal abstimmen!
Yoav Schwartz

Wissen Sie, ob es etwas Ähnliches gibt, außer für die Erkennung von totem Code? Zum Beispiel für Methoden, die nicht mehr aufgerufen werden (zumindest nicht mehr statisch aufgerufen ).
Superpuccio

24

Ich habe Romans Lösung ausprobiert und ein paar Verbesserungen hinzugefügt, um Retina-Bilder zu verarbeiten. Es funktioniert gut, aber denken Sie daran, dass Bildnamen programmgesteuert im Code generiert werden können und dieses Skript diese Bilder fälschlicherweise als nicht referenziert auflistet. Zum Beispiel könnten Sie haben

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

Dieses Skript glaubt fälschlicherweise, dass image_1.pnges nicht referenziert ist.

Hier ist das modifizierte Skript:

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

Was macht das @ 2x im Suffix-Schalter für den Basisnamen?
ThaDon

3
Zu Ihrer Information, Ordner mit Leerzeichen im Namen verursachen Probleme mit dem Skript.
Steve

3
Wenn Sie auf einen Fehler stoßen: Keine solche Datei oder kein solches Verzeichnis, liegt wahrscheinlich an den Leerzeichen im Dateipfad. Die Anführungszeichen müssen in grep-Zeile hinzugefügt werden, also geht es: wenn! grep -qhs "$ name" "$ PROJ";
Lukasz

3
Dieses Skript listet alle meine Dateien auf
jjxtra

2
Ich weiß nicht, warum es nicht für mich funktioniert, es gibt mir alle PNG-Bilder
Omer Obaid

12

Vielleicht können Sie versuchen, schlank , macht einen anständigen Job.

Update: Mit der Idee von emcmanus habe ich ein kleines Dienstprogramm ohne Bestätigung erstellt, nur um zusätzliche Einstellungen in einer Maschine zu vermeiden.

https://github.com/arun80/xcodeutils


1
Schlank ist App bezahlt. mehrere falsch positive und nicht gut für kommerzielle Produkte. Das von emcmanus bereitgestellte Skript ist wirklich großartig.
Arun

6

Für mich funktioniert nur dieses Skript, das sogar den Speicherplatz in den Dateinamen verwaltet:

Bearbeiten

Aktualisiert, um swiftDateien und zu unterstützen cocoapod. Standardmäßig wird das Pods-Verzeichnis ausgeschlossen und nur die Projektdateien überprüft. Führen Sie --podattrbiute aus , um auch den Pods-Ordner zu überprüfen :

/.finunusedimages.sh --pod

Hier ist das eigentliche Skript:

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

Dieses Skript hat zu viele verwendete Ressourcen als nicht verwendet markiert . Verbesserungen erforderlich.
Artem Shmatkov

Mag auch keine großen, tiefen Projekthierarchien: ./findunused.sh: Zeile 28: / usr / bin / grep: Argumentliste zu lang
Martin-Gilles Lavoie

3

Ich habe die hervorragende Antwort von @EdMcManus geringfügig geändert, um Projekte mit Asset-Katalogen abzuwickeln.

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

Ich schreibe nicht wirklich Bash-Skripte. Wenn hier (wahrscheinlich) Verbesserungen vorgenommen werden müssen, lassen Sie es mich in den Kommentaren wissen und ich werde es aktualisieren.


Ich habe ein Problem mit Leerzeichen im Dateinamen. Ich habe herausgefunden, dass es nützlich ist, `IFS = $ '\ n'` kurz vor dem Code zu setzen (dieser setzt das interne Feldtrennzeichen auf neue Zeile) - funktioniert nicht, wenn Dateien wieder neue Zeilen im Namen haben.
Laura Calinoiu

2

Sie können ein Shell-Skript greperstellen , das Ihren Quellcode enthält, und die gefundenen Bilder mit Ihrem Projektordner vergleichen.

Hier die Männer für GREPundLS

Sie können ganz einfach Ihre gesamte Quelldatei schleifen, Bilder in einem Array oder etwas Ähnlichem speichern und verwenden

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

Mit diesem Trick können Sie alle Bilder in Ihrem Projektquellcode durchsuchen !!

hoffe das hilft!


2

Ich habe ein Lua-Skript geschrieben. Ich bin mir nicht sicher, ob ich es teilen kann, weil ich es bei der Arbeit gemacht habe, aber es funktioniert gut. Grundsätzlich macht es das:

Schritt eins - statische Bildreferenzen (das einfache Bit, das von den anderen Antworten abgedeckt wird)

  • Durchsucht rekursiv Bildverzeichnisse und zieht Bildnamen heraus
  • entfernt die Bildnamen von .png und @ 2x (nicht erforderlich / in imageNamed verwendet :)
  • sucht textuell nach jedem Bildnamen in den Quelldateien (muss sich im Zeichenfolgenliteral befinden)

Schritt zwei - dynamische Bildreferenzen (das lustige Bit)

  • Ruft eine Liste aller Zeichenfolgenliterale in der Quelle ab, die Formatspezifizierer enthält (z. B.% @).
  • Ersetzt Formatbezeichner in diesen Zeichenfolgen durch reguläre Ausdrücke (z. B. wird "foo% dbar" zu "foo [0-9] * bar".
  • Durchsucht die Bildnamen mithilfe dieser Regex-Zeichenfolgen in Textform

Löscht dann alles, was in beiden Suchvorgängen nicht gefunden wurde.

Der Randfall ist, dass Bildnamen, die von einem Server stammen, nicht behandelt werden. Um dies zu handhaben, nehmen wir den Servercode in diese Suche auf.


Ordentlich. Gibt es aus Neugier ein Dienstprogramm zum Umwandeln von Formatspezifizierern in Wildcard-Regexes? Ich denke nur, es gibt eine Menge Komplexität, mit der Sie umgehen müssen, um alle Spezifizierer und Plattformen genau unterzubringen. (Formatbezeichner docs)
Ed McManus

2

Sie können FauxPas App für Xcode ausprobieren . Es ist wirklich gut, die fehlenden Bilder und viele andere Probleme / Verstöße im Zusammenhang mit dem Xcode-Projekt zu finden.


Sieht so aus, als ob dies seit Xcode 9 nicht mehr aktualisiert wurde. Kann bestätigen, dass es mit Xcode 11 nicht funktioniert.
Robin Daugherty

2

Mit den anderen Antworten ist dies ein gutes Beispiel dafür, wie Sie Bilder in zwei Verzeichnissen ignorieren und nicht nach Vorkommen der Bilder in den Dateien pbxproj oder xcassets suchen (Vorsicht mit dem App-Symbol und den Begrüßungsbildschirmen). Ändern Sie das * in --ignore-dir = *. Xcassets entsprechend Ihrem Verzeichnis:

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

Ich habe dieses Framework verwendet: -

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

Funktioniert verdammt gut! Nur zwei Stellen, an denen ich Probleme gesehen habe, sind, wenn Bildnamen vom Server stammen und wenn sich der Name des Bild-Assets vom Namen des Bildes im Asset-Ordner unterscheidet ...


Dies sucht nicht nach Assets, sondern nur nach Bilddateien, auf die nicht direkt verwiesen wird. Wenn Sie Assets wie gewünscht verwenden, funktioniert dieses Tool leider nicht für Sie.
Robin Daugherty


0

Ich habe ein Python-Skript erstellt, um die nicht verwendeten Bilder zu identifizieren: 'unused_assets.py' @ gist . Es kann folgendermaßen verwendet werden:

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

Hier sind einige Regeln für die Verwendung des Skripts:

  • Es ist wichtig, den Pfad des Projektordners als erstes Argument und den Pfad des Assets-Ordners als zweites Argument zu übergeben
  • Es wird davon ausgegangen, dass alle Bilder im Ordner Assets.xcassets verwaltet werden und entweder in schnellen Dateien oder in Storyboards verwendet werden

Einschränkungen in der ersten Version:

  • Funktioniert nicht für objektive c-Dateien

Ich werde versuchen, es im Laufe der Zeit zu verbessern, basierend auf dem Feedback, aber die erste Version sollte für die meisten gut sein.

Nachfolgend finden Sie den Code. Der Code sollte selbsterklärend sein, da ich jedem wichtigen Schritt entsprechende Kommentare hinzugefügt habe .

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

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.