Bagaimana cara menemukan gambar yang tidak digunakan dalam proyek Xcode?


97

Adakah yang punya satu baris untuk menemukan gambar yang tidak digunakan dalam proyek Xcode? (Dengan asumsi semua file direferensikan dengan nama dalam kode atau file proyek - tidak ada nama file yang dihasilkan kode.)

File-file ini cenderung menumpuk selama umur proyek dan mungkin sulit untuk mengetahui apakah aman untuk menghapus png tertentu.


4
Apakah ini berfungsi untuk XCode4 juga? Cmd-Opt-A di XCode4 sepertinya membuka dialog "Tambahkan file".
Rajavanya Subramaniyan

Jawaban:


61

Untuk file yang tidak termasuk dalam proyek, tetapi hanya berkeliaran di folder, Anda dapat menekan

cmd ⌘+ alt ⌥+A

dan mereka tidak akan berwarna abu-abu.

Untuk file yang tidak direferensikan baik dalam xib maupun dalam kode, sesuatu seperti ini mungkin berfungsi:

#!/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
Jika Anda menjumpai kesalahan: Tidak ada file atau direktori seperti itu, mungkin karena spasi di jalur file. Kutipan perlu ditambahkan di baris grep, begitulah: if! grep -qhs "$ name" "$ PROJ";
Lukasz

8
Satu skenario di mana ini tidak akan berhasil adalah ketika kita mungkin memuat gambar secara terprogram setelah membuat namanya. Seperti arm1.png, arm2.png .... arm22.png. Saya mungkin membangun nama mereka di for loop dan load. Misalnya Game
Rajavanya Subramaniyan

Jika Anda memiliki gambar untuk tampilan retina yang dinamai dengan @ 2x mereka akan terdaftar sebagai tidak digunakan. Anda dapat menghilangkannya dengan menambahkan pernyataan if tambahan: if [["$ name"! = @ 2x ]]; kemudian
Sten

3
Cmd + Opt + a tampaknya tidak lagi berfungsi pada XCode 5. Apa yang harus dipicu?
powtac

cmd + opt + a tampaknya tidak mengaburkan file di Images.xcassets meskipun mereka adalah bagian dari proyek :(
tettoffensive

80

Ini adalah solusi yang lebih kuat - ini memeriksa referensi apa pun ke nama dasar dalam file teks apa pun. Perhatikan solusi di atas yang tidak menyertakan file storyboard (dapat dimengerti sepenuhnya, saat itu tidak ada).

Ack membuatnya cukup cepat, tetapi ada beberapa pengoptimalan yang jelas harus dilakukan jika skrip ini sering berjalan. Kode ini memeriksa setiap nama dasar dua kali jika Anda memiliki aset retina / non-retina, misalnya.

#!/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
Instal Homebrew lalu lakukan a brew install ack.
Marko

1
Terima kasih. Jawaban ini juga menangani file dan folder dengan spasi dengan benar.
djskinner

2
@Johnny Anda perlu membuat file tersebut dapat dieksekusi ( chmod a+x FindUnusedImages.sh), lalu menjalankannya seperti program lain dari bash./FindUnusedImages.sh
Mike Sprague

2
Saya telah membuat modifikasi untuk mengabaikan file pbxproj (sehingga mengabaikan file yang ada di proyek xcode, tetapi tidak digunakan dalam kode atau nibs / storyboard): result=`ack --ignore-file=match:/.\.pbxproj/ -i "$file"` Ini membutuhkan ack 2.0 dan yang lebih baru
Mike Sprague

2
milanpanchal, Anda dapat meletakkan skrip di mana saja, dan Anda cukup menjalankannya dari direktori mana pun yang ingin Anda gunakan sebagai root untuk mencari gambar (misalnya folder root proyek Anda). Anda dapat memasukkannya ke dalam ~ / script / misalnya dan kemudian pergi ke folder root proyek Anda dan menjalankannya dengan mengarahkan ke skrip secara langsung: ~ / script / unused_images.sh
Erik van der Neut

25

Silakan mencoba LSUnusedResources .

Ini sangat dipengaruhi oleh Jeffhodnett's Unused , tapi jujur ​​Unused sangat lambat, dan hasilnya tidak sepenuhnya benar. Jadi saya membuat beberapa optimasi kinerja, kecepatan pencarian lebih cepat daripada yang tidak digunakan.


2
Wow, alat yang hebat! Jauh lebih bagus daripada mencoba menjalankan skrip tersebut. Anda dapat melihat secara visual semua gambar yang tidak digunakan, dan menghapus gambar yang Anda inginkan. Satu gotcha yang saya temukan adalah tidak mengambil gambar yang dirujuk di plist
RyanG

1
Benar-benar luar biasa dan menyelamatkan hari saya! Solusi terbaik di utas. Kamu keren.
Jakehao

2
Terbaik di utas. Saya berharap ini lebih tinggi dan saya dapat memilih lebih dari sekali!
Yoav Schwartz

Apakah Anda tahu jika ada yang mirip dengan ini tetapi untuk deteksi kode mati? Misalnya, untuk metode yang tidak lagi dipanggil (setidaknya tidak lagi dipanggil secara statis ).
superpuccio

24

Saya mencoba solusi Roman, dan saya menambahkan beberapa penyesuaian untuk menangani gambar retina. Ini berfungsi dengan baik, tetapi ingat bahwa nama gambar dapat dibuat secara terprogram dalam kode, dan skrip ini akan salah mencantumkan gambar ini sebagai tidak direferensikan. Misalnya, Anda mungkin pernah

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

Skrip ini akan salah mengira image_1.pngtidak direferensikan.

Berikut skrip yang dimodifikasi:

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

apa yang dilakukan @ 2x pada sakelar sufiks untuk nama dasar?
ThaD

3
FYI, folder dengan spasi di nama menyebabkan masalah dengan skrip.
Steve

3
Jika Anda menjumpai kesalahan: Tidak ada file atau direktori seperti itu, mungkin karena spasi di jalur file. Kutipan perlu ditambahkan di baris grep, begitulah: if! grep -qhs "$ name" "$ PROJ";
Lukasz

3
Skrip ini mencantumkan semua file saya
jjxtra

2
saya tidak tahu mengapa ini tidak bekerja untuk saya yang memberi saya semua gambar png
Omer Obaid

12

Mungkin Anda bisa mencoba ramping , melakukan pekerjaan yang layak.

update: Dengan ide emcmanus, saya melanjutkan dan membuat util kecil tanpa ack hanya untuk menghindari penyiapan tambahan di mesin.

https://github.com/arun80/xcodeutils


1
Slender adalah aplikasi berbayar. beberapa positif palsu dan tidak baik untuk produk komersial. script yang disediakan oleh emcmanus sangat bagus.
Arun

6

Hanya skrip ini yang berfungsi untuk saya yang bahkan menangani ruang di nama file:

Edit

Diperbarui untuk mendukung swiftfile dan cocoapod. Secara default tidak termasuk direktori Pods dan hanya memeriksa file proyek. Untuk menjalankan untuk memeriksa folder Pods juga, jalankan dengan --podattrbiute:

/.finunusedimages.sh --pod

Berikut ini skrip aslinya:

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

Skrip ini telah menandai terlalu banyak sumber daya yang digunakan sebagai tidak terpakai . Perlu perbaikan.
Artem Shmatkov

Juga tidak menyukai hierarki proyek yang besar dan dalam: ./findunused.sh: baris 28: / usr / bin / grep: Daftar argumen terlalu panjang
Martin-Gilles Lavoie

3

Saya membuat sedikit modifikasi pada jawaban luar biasa yang diberikan oleh @EdMcManus untuk menangani proyek yang menggunakan katalog aset.

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

Saya tidak benar-benar menulis skrip bash, jadi jika ada perbaikan yang harus dilakukan di sini (kemungkinan) beri tahu saya di komentar dan saya akan memperbaruinya.


Saya memiliki masalah dengan spasi pada nama file. Saya telah menemukan bahwa berguna untuk mengatur `` IFS = $ '\ n' ', tepat sebelum kode (yang ini mengatur pemisah bidang internal ke baris baru) - tidak akan berfungsi jika lagi file memiliki baris baru dalam nama.
Laura Calinoiu

2

Anda dapat membuat skrip shell sebagai grepkode sumber Anda dan membandingkan gambar yang didirikan dengan folder proyek Anda.

Di sini pria untuk GREPdanLS

Dengan mudah Anda dapat mengulang semua file sumber Anda, menyimpan gambar dalam array atau sesuatu yang setara dan digunakan

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

Dengan trik ini, Anda dapat mencari semua gambar dalam kode sumber proyek Anda !!

semoga ini membantu!


2

Saya menulis skrip lua, saya tidak yakin dapat membagikannya karena saya melakukannya di tempat kerja, tetapi berfungsi dengan baik. Pada dasarnya ini melakukan ini:

Langkah satu- referensi gambar statis (bagian yang mudah, ditutupi oleh jawaban lain)

  • secara rekursif melihat melalui direktori gambar dan mengeluarkan nama gambar
  • strip nama gambar .png dan @ 2x (tidak diperlukan / digunakan dalam imageNamed :)
  • mencari secara tekstual untuk setiap nama gambar di file sumber (harus di dalam string literal)

Langkah kedua- referensi gambar dinamis (bagian yang menyenangkan)

  • mengeluarkan daftar semua string literal dalam sumber yang berisi penentu format (misalnya,% @)
  • mengganti penentu format dalam string ini dengan ekspresi reguler (misalnya, "foo% dbar" menjadi "foo [0-9] * bar"
  • mencari secara tekstual melalui nama gambar menggunakan string regex ini

Kemudian hapus apa pun yang tidak ditemukan di kedua penelusuran.

Kasus tepi adalah bahwa nama gambar yang berasal dari server tidak ditangani. Untuk menangani ini kami menyertakan kode server dalam pencarian ini.


Rapi. Karena penasaran, adakah utilitas untuk mengubah penentu format menjadi regex wildcard? Hanya berpikir ada banyak kerumitan yang harus Anda tangani untuk mengakomodasi semua penentu dan platform secara akurat. (Format dokumen penentu)
Ed McManus

2

Anda dapat mencoba Aplikasi FauxPas untuk Xcode . Sangat bagus dalam menemukan gambar yang hilang dan banyak masalah / pelanggaran lain yang terkait dengan proyek Xcode.


Sepertinya ini belum diperbarui sejak Xcode 9. Dapat mengonfirmasi bahwa ini tidak berfungsi dengan Xcode 11.
Robin Daugherty

2

Menggunakan jawaban lain, yang satu ini adalah contoh yang baik tentang bagaimana mengabaikan gambar pada dua direktori dan tidak mencari kemunculan gambar pada file pbxproj atau xcassets (Hati-hati dengan ikon aplikasi dan layar splash). Ubah * di --ignore-dir = *. Xcassets agar sesuai dengan direktori Anda:

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

Saya menggunakan kerangka ini: -

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

Bekerja dengan sangat baik! Hanya 2 tempat saya melihat masalah adalah ketika nama gambar berasal dari server dan ketika nama aset gambar berbeda dari nama gambar di dalam folder aset ...


Ini tidak mencari aset, hanya untuk file gambar yang tidak direferensikan secara langsung. Jika Anda menggunakan Aset seperti yang seharusnya, sayangnya alat ini tidak akan berfungsi untuk Anda.
Robin Daugherty


0

Saya telah membuat skrip python untuk mengidentifikasi gambar yang tidak digunakan: 'unused_assets.py' @ gist . Ini bisa digunakan seperti ini:

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

Berikut beberapa aturan untuk menggunakan skrip:

  • Penting untuk meneruskan jalur folder proyek sebagai argumen pertama, jalur folder aset sebagai argumen kedua
  • Diasumsikan bahwa semua gambar disimpan dalam folder Assets.xcassets dan digunakan baik dalam file swift atau dalam storyboard

Batasan di versi pertama:

  • Tidak berfungsi untuk file c obyektif

Saya akan mencoba memperbaikinya dari waktu ke waktu, berdasarkan umpan balik, namun versi pertama seharusnya bagus untuk sebagian besar.

Silakan temukan di bawah kode. Kode harus cukup jelas karena saya telah menambahkan komentar yang sesuai untuk setiap langkah penting .

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

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.