¿Cómo encontrar imágenes no utilizadas en un proyecto de Xcode?


97

¿Alguien tiene una línea para encontrar imágenes no utilizadas en un proyecto de Xcode? (Suponiendo que se hace referencia a todos los archivos por nombre en el código o en los archivos del proyecto, no hay nombres de archivo generados por código).

Estos archivos tienden a acumularse durante la vida de un proyecto y puede ser difícil saber si es seguro eliminar cualquier png.


4
¿Funciona esto también para XCode4? Cmd-Opt-A en XCode4 parece abrir el cuadro de diálogo "Agregar archivos".
Rajavanya Subramaniyan

Respuestas:


61

Para los archivos que no están incluidos en el proyecto, pero que se encuentran en la carpeta, puede presionar

cmd ⌘+ alt ⌥+A

y no aparecerán atenuados.

Para los archivos a los que no se hace referencia ni en xib ni en el código, algo como esto podría funcionar:

#!/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
Si encuentra un error: No existe tal archivo o directorio, probablemente se deba a los espacios en la ruta del archivo. Las comillas deben agregarse en la línea grep, por lo que dice: ¡si! grep -qhs "$ nombre" "$ PROJ";
Lukasz

8
Un escenario en el que esto no funcionaría es cuando podríamos cargar imágenes mediante programación después de construir sus nombres. Como arm1.png, arm2.png .... arm22.png. Podría construir sus nombres en el bucle for y cargar. Eg Games
Rajavanya Subramaniyan

Si tiene imágenes para la pantalla retina nombradas con @ 2x, aparecerán como no utilizadas. Puede deshacerse de eso agregando una declaración if adicional: if [["$ name"! = @ 2x ]]; luego
Sten

3
Cmd + Opt + a parece que ya no funciona en XCode 5. ¿Qué debería disparar?
powtac

cmd + opt + a no parece atenuar los archivos en Images.xcassets a pesar de que son parte del proyecto :(
tettoffensive

80

Esta es una solución más sólida: busca cualquier referencia al nombre de base en cualquier archivo de texto. Tenga en cuenta las soluciones anteriores que no incluyen archivos de guiones gráficos (completamente comprensible, no existían en ese momento).

Ack hace esto bastante rápido, pero hay algunas optimizaciones obvias que hacer si este script se ejecuta con frecuencia. Este código verifica cada nombre base dos veces si tiene activos tanto retina como no retina, por ejemplo.

#!/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
Instale Homebrew y luego haga un brew install ack.
Marko

1
Gracias. Esta respuesta también maneja archivos y carpetas con espacios correctamente.
djskinner

2
@Johnny, debe hacer que el archivo sea ejecutable ( chmod a+x FindUnusedImages.sh), luego ejecutarlo como cualquier otro programa de bash./FindUnusedImages.sh
Mike Sprague

2
Hice una modificación para ignorar los archivos pbxproj (ignorando así los archivos que están en el proyecto xcode, pero que no se usan en el código o nibs / storyboards): result=`ack --ignore-file=match:/.\.pbxproj/ -i "$file"` esto requiere ack 2.0 y superior
Mike Sprague

2
milanpanchal, puede poner el script en cualquier lugar y ejecutarlo desde cualquier directorio que desee utilizar como raíz para buscar imágenes (por ejemplo, la carpeta raíz de su proyecto). Puede ponerlo en ~ / script / por ejemplo y luego ir a la carpeta raíz de su proyecto y ejecutarlo apuntando directamente al script: ~ / script / unused_images.sh
Erik van der Neut

25

Pruebe LSUnusedResources .

Está fuertemente influenciado por el Unused de jeffhodnett , pero honestamente Unused es muy lento y los resultados no son del todo correctos. Así que realicé una optimización del rendimiento, la velocidad de búsqueda es más rápida que Sin usar.


2
¡Vaya, es una gran herramienta! Mucho mejor que intentar ejecutar esos scripts. Puede ver visualmente todas las imágenes no utilizadas y eliminar las que desee. Sin embargo, un problema que encontré es que no recoge imágenes a las que se hace referencia en el plist
RyanG

1
¡Definitivamente increíble y salva mi día! Mejor solución en hilo. Tú Molas.
Jakehao

2
El mejor en hilo. ¡Ojalá estuviera más arriba y pudiera votar a favor más de una vez!
Yoav Schwartz

¿Sabes si hay algo similar a esto pero para la detección de código muerto? Por ejemplo, para métodos que ya no se llaman (al menos ya no se llaman estáticamente ).
superpuccio

24

Probé la solución de Roman y agregué algunos ajustes para manejar imágenes de retina. Funciona bien, pero recuerde que los nombres de las imágenes se pueden generar mediante programación en el código, y este script enumeraría incorrectamente estas imágenes como no referenciadas. Por ejemplo, podrías tener

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

Este script pensará incorrectamente image_1.png no está referenciado.

Aquí está el script modificado:

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

¿Qué hace @ 2x en el cambio de sufijo para el nombre de base?
ThaDon

3
Para su información, las carpetas con espacios en el nombre causan problemas con el script.
Steve

3
Si encuentra un error: No existe tal archivo o directorio, probablemente se deba a los espacios en la ruta del archivo. Las comillas deben agregarse en la línea grep, por lo que dice: ¡si! grep -qhs "$ nombre" "$ PROJ";
Lukasz

3
Este script enumera todos mis archivos
jjxtra

2
No sé por qué no funciona para mí, me está dando todas las imágenes png
Omer Obaid

12

Puede ser que puedas probar delgado , hace un trabajo decente.

actualización: Con la idea de emcmanus, seguí adelante y creé una pequeña utilidad sin ack solo para evitar configuraciones adicionales en una máquina.

https://github.com/arun80/xcodeutils


1
Slender es una aplicación de pago. varios falsos positivos y no es bueno para productos comerciales. El script proporcionado por emcmanus es realmente genial.
Arun

6

Solo este script funciona para mí, que incluso maneja el espacio en los nombres de archivo:

Editar

Actualizado para admitir swiftarchivos y cocoapod. De forma predeterminada, excluye el directorio Pods y solo verifica los archivos del proyecto. Para ejecutar y verificar también la carpeta Pods, ejecute con --podattrbiute:

/.finunusedimages.sh --pod

Aquí está el guión real:

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

Este script ha marcado demasiados recursos utilizados como no utilizados . Se necesitan mejoras.
Artem Shmatkov

Tampoco le gustan las jerarquías de proyectos grandes y profundas: ./findunused.sh: línea 28: / usr / bin / grep: Lista de argumentos demasiado larga
Martin-Gilles Lavoie

3

Hice una modificación muy leve a la excelente respuesta proporcionada por @EdMcManus para manejar proyectos que utilizan catálogos de activos.

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

Realmente no escribo scripts de bash, así que si hay mejoras que hacer aquí (probablemente) hágamelo saber en los comentarios y lo actualizaré.


Tengo un problema con los espacios en el nombre de los archivos. Descubrí que es útil configurar `IFS = $ '\ n'`, justo antes del código (este establece el separador de campo interno en una nueva línea); no funcionará si nuevamente los archivos tienen nuevas líneas en el nombre.
Laura Calinoiu

2

Puede hacer un script de shell con grepsu código fuente y comparar las imágenes fundadas con la carpeta de su proyecto.

Aquí el (los) hombre (s) para GREPyLS

Fácilmente puede recorrer todo su archivo fuente, guardar imágenes en una matriz o algo igual y usar

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

¡Con este truco, puedes buscar todas las imágenes en el código fuente de tu proyecto!

¡espero que esto ayude!


2

Escribí un guión de lua, no estoy seguro de poder compartirlo porque lo hice en el trabajo, pero funciona bien. Básicamente hace esto:

Paso uno: referencias de imágenes estáticas (lo fácil, cubierto por las otras respuestas)

  • busca de forma recursiva los directorios de imágenes y extrae los nombres de las imágenes
  • elimina los nombres de las imágenes de .png y @ 2x (no es necesario / se usa en imageNamed :)
  • busca textualmente el nombre de cada imagen en los archivos de origen (debe estar dentro de una cadena literal)

Paso dos: referencias de imágenes dinámicas (la parte divertida)

  • saca una lista de todos los literales de cadena en la fuente que contiene especificadores de formato (por ejemplo,% @)
  • reemplaza los especificadores de formato en estas cadenas con expresiones regulares (por ejemplo, "foo% dbar" se convierte en "foo [0-9] * bar"
  • busca textualmente los nombres de las imágenes usando estas cadenas de expresiones regulares

Luego borra todo lo que no encontró en ninguna de las búsquedas.

El caso extremo es que los nombres de imágenes que provienen de un servidor no se manejan. Para manejar esto incluimos el código del servidor en esta búsqueda.


Ordenado. Por curiosidad, ¿existe alguna utilidad para transformar especificadores de formato en expresiones regulares comodín? Solo pensar que hay mucha complejidad que tendría que manejar para acomodar con precisión todos los especificadores y plataformas. (Documentos de especificador de formato)
Ed McManus

2

Puede probar la aplicación FauxPas para Xcode . Es realmente bueno para encontrar las imágenes faltantes y muchos otros problemas / violaciones relacionados con el proyecto Xcode.


Parece que esto no se ha actualizado desde Xcode 9. Puedo confirmar que no funciona con Xcode 11.
Robin Daugherty

2

Usando las otras respuestas, esta es un buen ejemplo de cómo ignorar imágenes en dos directorios y no buscar ocurrencias de las imágenes en los archivos pbxproj o xcassets (tenga cuidado con el ícono de la aplicación y las pantallas de presentación). Cambie el * en --ignore-dir = *. Xcassets para que coincida con su directorio:

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

Usé este marco: -

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

¡Funciona muy bien! Solo 2 lugares en los que vi problemas son cuando los nombres de las imágenes son del servidor y cuando el nombre del activo de la imagen es diferente del nombre de la imagen dentro de la carpeta del activo ...


Esto no busca activos, solo archivos de imagen a los que no se hace referencia directamente. Si está utilizando Activos como debería, esta herramienta desafortunadamente no funcionará para usted.
Robin Daugherty


0

He creado un script de Python para identificar las imágenes no utilizadas: 'unused_assets.py' @ gist . Se puede usar así:

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

Aquí hay algunas reglas para usar el script:

  • Es importante pasar la ruta de la carpeta del proyecto como primer argumento, la ruta de la carpeta de activos como segundo argumento
  • Se supone que todas las imágenes se mantienen dentro de la carpeta Assets.xcassets y se utilizan en archivos rápidos o en guiones gráficos.

Limitaciones en la primera versión:

  • No funciona para archivos objetivos c

Intentaré mejorarlo con el tiempo, basándome en los comentarios; sin embargo, la primera versión debería ser buena para la mayoría.

A continuación encontrará el código. El código debe explicarse por sí mismo, ya que he agregado comentarios apropiados a cada paso 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!")

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.