Làm thế nào để tìm hình ảnh không sử dụng trong một dự án Xcode?


97

Có ai một dòng để tìm hình ảnh không sử dụng trong một dự án Xcode không? (Giả sử tất cả các tệp được tham chiếu theo tên trong mã hoặc các tệp dự án - không có tên tệp nào được tạo mã.)

Các tệp này có xu hướng tích tụ trong vòng đời của một dự án và khó có thể biết được liệu có an toàn khi xóa bất kỳ png đã cho nào hay không.


4
Điều này cũng hoạt động cho XCode4? Cmd-Opt-A trong XCode4 dường như mở hộp thoại "Thêm tệp".
Rajavanya Subramaniyan

Câu trả lời:


61

Đối với các tệp không có trong dự án, nhưng chỉ lơ lửng trong thư mục, bạn có thể nhấn

cmd ⌘+ alt ⌥+A

và chúng sẽ không bị xám.

Đối với các tệp không được tham chiếu không phải trong xib cũng như trong mã, một cái gì đó như thế này có thể hoạt động:

#!/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
Nếu bạn gặp lỗi: Không có tệp hoặc thư mục như vậy, có thể là do khoảng trống trong đường dẫn tệp. Các dấu ngoặc kép cần phải được thêm vào dòng grep, vì vậy nó sẽ là: nếu! grep -qhs "$ name" "$ PROJ";
Lukasz

8
Một tình huống mà điều này sẽ không diễn ra là khi chúng ta có thể tải hình ảnh theo chương trình sau khi xây dựng tên của chúng. Như arm1.png, arm2.png .... arm22.png. Tôi có thể xây dựng tên của họ trong vòng lặp for và tải. Ví dụ: Trò chơi
Rajavanya Subramaniyan

Nếu bạn có hình ảnh cho màn hình võng mạc được đặt tên bằng @ 2x, chúng sẽ liệt kê là không được sử dụng. Bạn có thể loại bỏ điều đó bằng cách thêm một câu lệnh if bổ sung: if [["$ name"! = @ 2x ]]; sau đó
Sten

3
Cmd + Opt + a dường như không còn hoạt động trên XCode 5. Nó sẽ kích hoạt điều gì?
powtac

cmd + opt + a dường như không màu xám ra tập tin trong Images.xcassets mặc dù họ là một phần của dự án :(
tettoffensive

80

Đây là một giải pháp mạnh mẽ hơn - nó kiểm tra mọi tham chiếu đến tên cơ sở trong bất kỳ tệp văn bản nào. Lưu ý rằng các giải pháp ở trên không bao gồm tệp bảng phân cảnh (hoàn toàn có thể hiểu được, chúng không tồn tại vào thời điểm đó).

Ack thực hiện điều này khá nhanh, nhưng có một số tối ưu hóa rõ ràng cần thực hiện nếu tập lệnh này chạy thường xuyên. Ví dụ: mã này kiểm tra mọi tên cơ sở hai lần nếu bạn có cả nội dung retina / non-retina.

#!/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
Cài đặt Homebrew và sau đó thực hiện a brew install ack.
Marko

1
Cảm ơn. Câu trả lời này cũng xử lý các tệp và thư mục có khoảng trắng một cách chính xác.
djskinner 27/10/12

2
@Johnny bạn cần phải thực hiện các tập tin thực thi ( chmod a+x FindUnusedImages.sh), sau đó chạy nó giống như bất kỳ chương trình nào khác từ bash./FindUnusedImages.sh
Mike Sprague

2
Tôi đã thực hiện một sự sửa đổi để bỏ qua các file pbxproj (do đó bỏ qua các file có trong dự án xcode, nhưng không được sử dụng trong mã hoặc ngòi / storyboards): result=`ack --ignore-file=match:/.\.pbxproj/ -i "$file"` Điều này đòi hỏi ack 2.0 và lên
Mike Sprague

2
milanpanchal, bạn có thể đặt tập lệnh ở bất cứ đâu và bạn chỉ cần thực thi nó từ bất kỳ thư mục nào bạn muốn sử dụng làm thư mục gốc để tìm kiếm hình ảnh (ví dụ: thư mục gốc dự án của bạn). Bạn có thể đặt nó trong ~ / script / ví dụ và sau đó đi đến thư mục gốc của dự án của bạn và chạy nó bằng cách trỏ đến kịch bản trực tiếp: ~ / script / unused_images.sh
Erik van der Neut

25

Vui lòng dùng thử LSUnusedResources .

Nó bị ảnh hưởng rất nhiều bởi tính năng Unused của jeffhodnett , nhưng thành thật mà nói thì Unused rất chậm và kết quả không hoàn toàn chính xác. Vì vậy, tôi đã thực hiện một số tối ưu hóa hiệu suất, tốc độ tìm kiếm nhanh hơn Không sử dụng.


2
Wow đó là một công cụ tuyệt vời! Đẹp hơn nhiều so với việc cố gắng chạy các tập lệnh đó. Bạn có thể xem trực quan tất cả các hình ảnh không được sử dụng và xóa những hình ảnh bạn muốn. Một Gotcha tôi thấy mặc dù là nó không lấy hình ảnh tham chiếu trong plist
Ryang

1
Chắc chắn tuyệt vời và tiết kiệm ngày của tôi! Giải pháp tốt nhất trong chủ đề. Bạn đá.
Jakehao

2
Tốt nhất trong chủ đề. Tôi ước điều này cao hơn và tôi có thể bỏ phiếu nhiều hơn một lần!
Yoav Schwartz

Bạn có biết nếu có một cái gì đó tương tự như thế này nhưng để phát hiện mã chết? Ví dụ, đối với các phương thức không còn được gọi nữa (ít nhất là không còn được gọi tĩnh ).
superpuccio

24

Tôi đã thử giải pháp của Roman và tôi đã thêm một số chỉnh sửa để xử lý hình ảnh võng mạc. Nó hoạt động tốt, nhưng hãy nhớ rằng tên hình ảnh có thể được tạo theo chương trình trong mã và tập lệnh này sẽ liệt kê sai những hình ảnh này là không được tham chiếu. Ví dụ, bạn có thể có

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

Tập lệnh này không chính xác sẽ nghĩ image_1.pnglà không được tham chiếu.

Đây là tập lệnh đã sửa đổi:

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

những gì hiện các @ 2x làm trong chuyển đổi hậu tố cho basename?
ThaDon

3
FYI, các thư mục có khoảng trắng trong tên gây ra sự cố với tập lệnh.
Steve

3
Nếu bạn gặp lỗi: Không có tệp hoặc thư mục như vậy, có thể là do khoảng trống trong đường dẫn tệp. Các dấu ngoặc kép cần phải được thêm vào dòng grep, vì vậy nó sẽ là: nếu! grep -qhs "$ name" "$ PROJ";
Lukasz

3
Kịch bản này liệt kê tất cả các file của tôi
jjxtra

2
i dunno lý do tại sao nó không làm việc cho tôi đem lại cho tôi tất cả những hình ảnh png của nó
Omer Obaid

12

Có thể bạn có thể cố gắng mảnh mai , làm một công việc tốt.

cập nhật: Với ý tưởng emcmanus, tôi đã tiếp tục và tạo ra một tiện ích nhỏ mà không tốn kém chỉ để tránh thiết lập bổ sung trong máy.

https://github.com/arun80/xcodeutils


1
Slender là ứng dụng trả phí. một số dương tính giả và không tốt cho các sản phẩm thương mại. script do emcmanus cung cấp thực sự tuyệt vời.
Arun

6

Chỉ có tập lệnh này đang hoạt động đối với tôi, thậm chí đang xử lý không gian trong tên tệp:

Biên tập

Cập nhật để hỗ trợ swifttệp và cocoapod. Theo mặc định, nó loại trừ dir Pods và chỉ kiểm tra các tệp dự án. Để chạy để kiểm tra cả thư mục Pods, hãy chạy với --podattrbiute:

/.finunusedimages.sh --pod

Đây là kịch bản thực tế:

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

Tập lệnh này đã đánh dấu quá nhiều tài nguyên được sử dụngkhông được sử dụng . Cải tiến cần thiết.
Artem Shmatkov

Cũng không giống như, phân cấp dự án lớn sâu: ./findunused.sh: dòng 28: / usr / bin / grep: Đối số danh sách quá dài
Martin-Gilles Lavoie

3

Tôi đã thực hiện một sửa đổi rất nhỏ đối với câu trả lời tuyệt vời do @EdMcManus cung cấp để xử lý các dự án sử dụng danh mục tài sản.

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

Tôi không thực sự viết kịch bản bash, vì vậy nếu có những cải tiến cần được thực hiện ở đây (có thể), hãy cho tôi biết trong phần nhận xét và tôi sẽ cập nhật nó.


Tôi gặp sự cố với khoảng trắng trong tên tệp. Tôi đã phát hiện ra rằng điều này rất hữu ích khi đặt `` IFS = $ '\ n' ', ngay trước mã (cái này đặt dấu phân tách trường nội bộ thành dòng mới) - sẽ không hoạt động nếu một lần nữa tệp có các dòng mới trong tên.
Laura Calinoiu

2

Bạn có thể tạo một tập lệnh shell làm grepmã nguồn của bạn và so sánh các hình ảnh đã tạo với thư mục dự án của bạn.

Đây (những) người đàn ông cho GREPLS

Dễ dàng bạn có thể lặp lại tất cả tệp nguồn của mình, lưu hình ảnh trong mảng hoặc một cái gì đó bằng và sử dụng

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

Với thủ thuật này, bạn có thể tìm kiếm tất cả các hình ảnh trong mã nguồn dự án của mình !!

hi vọng điêu nay co ich!


2

Tôi đã viết một kịch bản lua, tôi không chắc tôi có thể chia sẻ nó vì tôi đã làm nó tại nơi làm việc, nhưng nó hoạt động tốt. Về cơ bản nó thực hiện điều này:

Bước một - tham chiếu hình ảnh tĩnh (phần đơn giản, được bao gồm trong các câu trả lời khác)

  • đệ quy xem qua các dirs hình ảnh và lấy ra tên hình ảnh
  • xóa tên hình ảnh của .png và @ 2x (không bắt buộc / được sử dụng trong imageNamed :)
  • tìm kiếm bằng văn bản cho từng tên hình ảnh trong các tệp nguồn (phải nằm trong chuỗi ký tự)

Bước hai - tham chiếu hình ảnh động (một chút thú vị)

  • kéo ra danh sách tất cả các ký tự chuỗi trong nguồn có chứa mã định dạng (ví dụ:% @)
  • thay thế các từ chỉ định định dạng trong các chuỗi này bằng các biểu thức chính quy (ví dụ: "foo% dbar" trở thành "foo [0-9] * bar"
  • tìm kiếm văn bản thông qua tên hình ảnh bằng cách sử dụng các chuỗi regex này

Sau đó, xóa bất cứ thứ gì không tìm thấy trong tìm kiếm.

Trường hợp nguy hiểm là các tên hình ảnh đến từ một máy chủ không được xử lý. Để xử lý điều này, chúng tôi đưa mã máy chủ vào tìm kiếm này.


Khéo léo. Vì tò mò là có một số tiện ích để chuyển đổi các chỉ định định dạng thành các ký tự đại diện regexes không? Chỉ nghĩ rằng có rất nhiều sự phức tạp mà bạn phải xử lý để chứa chính xác tất cả các chỉ số và nền tảng. (Định dạng tài liệu chỉ định)
Ed McManus,

2

Bạn có thể thử Ứng dụng FauxPas cho Xcode . Nó thực sự tốt trong việc tìm ra những hình ảnh bị thiếu và rất nhiều vấn đề / vi phạm khác liên quan đến dự án Xcode.


Có vẻ như điều này chưa được cập nhật kể từ Xcode 9. Có thể xác nhận rằng nó không hoạt động với Xcode 11.
Robin Daugherty

2

Sử dụng các câu trả lời khác, câu trả lời này là một ví dụ điển hình về cách bỏ qua hình ảnh trên hai thư mục và không tìm kiếm sự xuất hiện của hình ảnh trên tệp pbxproj hoặc xcassets (Hãy cẩn thận với biểu tượng ứng dụng và màn hình giật gân). Thay đổi dấu * trong --ignore-dir = *. Xcassets để khớp với thư mục của bạn:

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

Tôi đã sử dụng khung này: -

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

Hoạt động tốt! Chỉ có 2 nơi tôi gặp sự cố là khi tên hình ảnh từ máy chủ và khi tên nội dung hình ảnh khác với tên của hình ảnh bên trong thư mục nội dung ...


Điều này không tìm kiếm nội dung, chỉ dành cho các tệp hình ảnh không được tham chiếu trực tiếp. Nếu bạn đang sử dụng Nội dung như bình thường, rất tiếc công cụ này sẽ không hoạt động với bạn.
Robin Daugherty

0

Sử dụng http://jeffhodnett.github.io/Unused/ để tìm những hình ảnh không sử dụng.


Đối với tôi, dường như cả ứng dụng này đều không xử lý tốt không gian trong tên thư mục. Và nó khá chậm đối với một trong những dự án lớn hơn của tôi.
ingaham

0

Tôi đã tạo một tập lệnh python để xác định các hình ảnh không được sử dụng: 'used_assets.py' @ gist . Nó có thể được sử dụng như thế này:

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

Dưới đây là một số quy tắc để sử dụng tập lệnh:

  • Điều quan trọng là phải chuyển đường dẫn thư mục dự án làm đối số đầu tiên, đường dẫn thư mục tài sản làm đối số thứ hai
  • Giả định rằng tất cả các hình ảnh được duy trì trong thư mục Assets.xcassets và được sử dụng trong các tệp nhanh hoặc trong bảng phân cảnh

Hạn chế trong phiên bản đầu tiên:

  • Không hoạt động đối với các tệp c khách quan

Tôi sẽ cố gắng cải thiện nó theo thời gian, dựa trên phản hồi, tuy nhiên phiên bản đầu tiên sẽ tốt cho hầu hết mọi người.

Vui lòng tìm mã bên dưới. Mã phải tự giải thích vì tôi đã thêm các nhận xét thích hợp cho mỗi bước quan trọng .

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

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.