Como encontrar imagens não utilizadas em um projeto Xcode?

97

Alguém tem uma linha para localizar imagens não utilizadas em um projeto Xcode? (Supondo que todos os arquivos sejam referenciados por nome no código ou nos arquivos de projeto - nenhum nome de arquivo gerado por código.)

Esses arquivos tendem a se acumular ao longo da vida de um projeto e pode ser difícil dizer se é seguro excluir qualquer PNG.

Paul Robinson
fonte
4
Isso funciona para o XCode4 também? O Cmd-Opt-A no XCode4 parece abrir a caixa de diálogo "Adicionar arquivos".
Rajavanya Subramaniyan

Respostas:

61

Para arquivos que não estão incluídos no projeto, mas apenas perdidos na pasta, você pode pressionar

cmd ⌘+ alt ⌥+A

e eles não ficarão esmaecidos.

Para arquivos que não são referenciados nem no xib nem no código, algo assim pode 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
romano
fonte
6
Se você encontrar o erro: Não existe esse arquivo ou diretório, provavelmente é devido aos espaços no caminho do arquivo. As aspas precisam ser adicionadas na linha grep, então segue: if! grep -qhs "$ name" "$ PROJ";
Lukasz
8
Um cenário em que isso não funcionaria é quando poderíamos carregar imagens programaticamente após construir seus nomes. Como arm1.png, arm2.png .... arm22.png. Eu poderia construir seus nomes no loop for e carregar. Por exemplo, jogos
Rajavanya Subramaniyan
Se você tiver imagens para exibição de retina nomeadas com @ 2x, elas serão listadas como não utilizadas. Você pode se livrar disso adicionando uma instrução if extra: if [["$ name"! = @ 2x ]]; então
Sten
3
Cmd + Opt + a parece não funcionar mais no XCode 5. O que ele deve acionar?
powtac
cmd + opt + a não parece esmaecer os arquivos em Images.xcassets, embora eles façam parte do projeto :(
tettoffensive
80

Esta é uma solução mais robusta - ela verifica qualquer referência ao nome de base em qualquer arquivo de texto. Observe as soluções acima que não incluem arquivos de storyboard (completamente compreensíveis, eles não existiam na época).

Ack torna isso muito rápido, mas há algumas otimizações óbvias a serem feitas se esse script for executado com frequência. Este código verifica cada nome de base duas vezes se você tiver ativos retina / não retina, por exemplo.

#!/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
fonte
12
Instale o Homebrew e faça a brew install ack.
março
1
Obrigado. Esta resposta também lida com arquivos e pastas com espaços corretamente.
djskinner
2
@Johnny você precisa tornar o arquivo executável ( chmod a+x FindUnusedImages.sh), então execute-o como qualquer outro programa do bash./FindUnusedImages.sh
Mike Sprague
2
Fiz uma modificação para ignorar os arquivos pbxproj (ignorando os arquivos que estão no projeto xcode, mas não são usados ​​no código ou nibs / storyboards): result=`ack --ignore-file=match:/.\.pbxproj/ -i "$file"` Isso requer ack 2.0 e superior
Mike Sprague
2
milanpanchal, você pode colocar o script em qualquer lugar e apenas executá-lo a partir de qualquer diretório que deseja usar como raiz para a busca de imagens (por exemplo, a pasta raiz do projeto). Você pode colocá-lo em ~ / script / por exemplo e, em seguida, ir para a pasta raiz do projeto e executá-lo apontando para o script diretamente: ~ / script / unused_images.sh
Erik van der Neut
25

Por favor, experimente LSUnusedResources .

É fortemente influenciado por jeffhodnett's Unused , mas honestamente Unused é muito lento e os resultados não são totalmente corretos. Então fiz algumas otimizações de desempenho, a velocidade de pesquisa é mais rápida do que não usada.

LessFun
fonte
2
Uau, essa é uma ótima ferramenta! Muito melhor do que tentar executar esses scripts. Você pode ver visualmente todas as imagens não usadas e excluir as que desejar. Uma pegadinha que descobri é que ele não pega imagens referenciadas no plist
RyanG
1
Definitivamente incrível e salve o meu dia! Melhor solução em discussão. Você é demais.
Jakehao
2
Melhor na discussão. Eu gostaria que isso fosse mais alto e eu pudesse votar a favor mais de uma vez!
Yoav Schwartz
Você sabe se há algo semelhante a isso, mas para a detecção de código morto? Por exemplo, para métodos não mais chamados (pelo menos não mais chamados estaticamente ).
superpuccio
24

Tentei a solução de Roman e adicionei alguns ajustes para lidar com imagens de retina. Funciona bem, mas lembre-se de que os nomes das imagens podem ser gerados programaticamente no código, e esse script listaria incorretamente essas imagens como não referenciadas. Por exemplo, você pode ter

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

Este script pensará incorretamente que image_1.pngnão é referenciado.

Aqui está o 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
roubar
fonte
o que @ 2x faz na opção de sufixo para o nome de base?
ThaDon
3
FYI, pastas com espaços no nome causam problemas com o script.
Steve,
3
Se você encontrar o erro: Não existe esse arquivo ou diretório, provavelmente é devido aos espaços no caminho do arquivo. As aspas precisam ser adicionadas na linha grep, então segue: if! grep -qhs "$ name" "$ PROJ";
Lukasz
3
Este script lista todos os meus arquivos
jjxtra
2
Não sei porque não está funcionando para mim, está me dando todas as imagens PNG
Omer Obaid
12

Pode ser que você tente esbelto , faz um trabalho decente.

update: Com a ideia emcmanus, fui em frente e criei um pequeno utilitário sem ack apenas para evitar configuração adicional em uma máquina.

https://github.com/arun80/xcodeutils

Uma corrida
fonte
1
O Slender é um aplicativo pago. vários falsos positivos e não é bom para produtos comerciais. o script fornecido pelo emcmanus é realmente ótimo.
Arun
6

Apenas este script está funcionando para mim, que está até mesmo controlando o espaço nos nomes dos arquivos:

Editar

Atualizado para suportar swiftarquivos e cocoapod. Por padrão, ele exclui o diretório de pods e verifica apenas os arquivos do projeto. Para executar a verificação da pasta Pods também, execute com o --podatributo:

/.finunusedimages.sh --pod

Aqui está o script 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
Ingaham
fonte
Este script marcou muitos recursos usados como não usados . Melhorias necessárias.
Artem Shmatkov
Também não gosta de hierarquias de projeto grandes e profundas: ./findunused.sh: linha 28: / usr / bin / grep: Lista de argumentos muito longa
Martin-Gilles Lavoie
3

Fiz uma pequena modificação na excelente resposta fornecida por @EdMcManus para lidar com projetos que utilizam catálogos de ativos.

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

Eu realmente não escrevo scripts bash, então se houver melhorias a serem feitas aqui (provavelmente), deixe-me saber nos comentários e eu irei atualizá-los.

Stakenborg
fonte
Eu tenho um problema com espaços no nome dos arquivos. Eu descobri que é útil definir `IFS = $ '\ n'`, logo antes do código (este define o separador de campo interno para uma nova linha) - não funcionará se novamente os arquivos tiverem novas linhas no nome.
Laura Calinoiu
2

Você pode fazer um script de shell que grepcontém o seu código-fonte e comparar as imagens encontradas com a pasta do seu projeto.

Aqui o (s) homem (s) para GREPeLS

Facilmente, você pode fazer um loop em todo o seu arquivo de origem, salvar imagens em array ou algo igual e usar

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

Com este truque, você pode pesquisar todas as imagens no código-fonte do seu projeto !!

espero que isto ajude!

elp
fonte
2

Eu escrevi um roteiro lua, não tenho certeza se posso compartilhá-lo porque fiz isso no trabalho, mas funciona bem. Basicamente, ele faz isso:

Etapa um - referências de imagens estáticas (a parte fácil, coberta pelas outras respostas)

  • olha recursivamente através de diretórios de imagens e extrai nomes de imagens
  • remove os nomes de imagem de .png e @ 2x (não necessário / usado em imageNamed :)
  • pesquisa textualmente por cada nome de imagem nos arquivos de origem (deve estar dentro de uma string literal)

Etapa dois - referências de imagens dinâmicas (a parte divertida)

  • extrai uma lista de todos os literais de string na fonte contendo especificadores de formato (por exemplo,% @)
  • substitui os especificadores de formato nestas strings por expressões regulares (por exemplo, "foo% dbar" torna-se "foo [0-9] * bar"
  • pesquisa textualmente os nomes das imagens usando essas strings de regex

Em seguida, exclui tudo o que não encontrou em nenhuma das pesquisas.

O caso extremo é que os nomes das imagens que vêm de um servidor não são tratados. Para lidar com isso, incluímos o código do servidor nesta pesquisa.

Sam
fonte
Arrumado. Por curiosidade, há algum utilitário para transformar especificadores de formato em regexes curinga? Apenas pensando que você teria que lidar com muita complexidade para acomodar com precisão todos os especificadores e plataformas. (Documentos do especificador de formato)
Ed McManus
2

Você pode experimentar o FauxPas App for Xcode . É muito bom descobrir as imagens que faltam e muitos outros problemas / violações relacionados ao projeto Xcode.

Kunal Shah
fonte
Parece que isso não foi atualizado desde o Xcode 9. Posso confirmar que não funciona com o Xcode 11.
Robin Daugherty
2

Usando as outras respostas, este é um bom exemplo de como ignorar imagens em dois diretórios e não pesquisar ocorrências das imagens nos arquivos pbxproj ou xcassets (cuidado com o ícone do aplicativo e as telas iniciais). Altere o * no --ignore-dir = *. Xcassets para corresponder ao seu diretório:

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

Eu usei esta estrutura: -

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

Funciona muito bem! Apenas 2 lugares onde vi problemas são quando os nomes das imagens são do servidor e quando o nome do recurso da imagem é diferente do nome da imagem dentro da pasta do recurso ...

Swasidhant
fonte
Isso não procura por ativos, apenas por arquivos de imagem que não são diretamente referenciados. Se você estiver usando Ativos como deveria, esta ferramenta infelizmente não funcionará para você.
Robin Daugherty
0

Use http://jeffhodnett.github.io/Unused/ para encontrar as imagens não utilizadas.

Praveen Matanam
fonte
Parece-me que nem este aplicativo lida bem com o espaço nos nomes das pastas. E é bem lento para um dos meus projetos maiores.
ingaham
0

Eu criei um script python para identificar as imagens não utilizadas: 'unused_assets.py' @ gist . Ele pode ser usado assim:

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

Aqui estão algumas regras para usar o script:

  • É importante passar o caminho da pasta do projeto como primeiro argumento, o caminho da pasta de ativos como segundo argumento
  • Presume-se que todas as imagens são mantidas na pasta Assets.xcassets e são usadas em arquivos swift ou em storyboards

Limitações na primeira versão:

  • Não funciona para arquivos c objetivos

Vou tentar melhorá-lo com o tempo, com base no feedback, mas a primeira versão deve ser boa para a maioria.

Encontre abaixo o código. O código deve ser autoexplicativo, pois adicionei comentários apropriados a cada etapa 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
fonte