จะค้นหาภาพที่ไม่ได้ใช้ในโครงการ Xcode ได้อย่างไร


97

มีใครสามารถค้นหาภาพที่ไม่ได้ใช้ในโครงการ Xcode ได้หรือไม่? (สมมติว่าไฟล์ทั้งหมดอ้างอิงด้วยชื่อในโค้ดหรือไฟล์โปรเจ็กต์ - ไม่มีโค้ดสร้างชื่อไฟล์)

ไฟล์เหล่านี้มักจะสร้างขึ้นตลอดอายุของโปรเจ็กต์และอาจเป็นเรื่องยากที่จะบอกได้ว่าการลบ png ใด ๆ นั้นปลอดภัยหรือไม่


4
สิ่งนี้ใช้ได้กับ XCode4 ด้วยหรือไม่? ดูเหมือนว่า Cmd-Opt-A ใน XCode4 จะเปิดกล่องโต้ตอบ "เพิ่มไฟล์"
Rajavanya Subramaniyan

คำตอบ:


61

สำหรับไฟล์ที่ไม่รวมอยู่ในโปรเจ็กต์ แต่อยู่เฉยๆในโฟลเดอร์คุณสามารถกด

cmd ⌘+ alt ⌥+A

และจะไม่เป็นสีเทา

สำหรับไฟล์ที่ไม่ได้อ้างอิงทั้งใน xib หรือในโค้ดสิ่งนี้อาจใช้ได้:

#!/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
หากคุณพบข้อผิดพลาด: ไม่มีไฟล์หรือไดเร็กทอรีดังกล่าวอาจเป็นเพราะช่องว่างในเส้นทางไฟล์ จำเป็นต้องเพิ่มเครื่องหมายคำพูดในบรรทัด grep ดังนั้นจึงเป็นไปได้: if! grep -qhs "$ name" "$ PROJ";
Lukasz

8
สถานการณ์หนึ่งที่ไม่ได้ผลคือเมื่อเราอาจโหลดภาพโดยใช้โปรแกรมหลังจากสร้างชื่อ เช่น arm1.png, arm2.png .... arm22.png. ฉันอาจสร้างชื่อของพวกเขาใน for loop และ load เช่นเกมส์
ราชวรรณาสุบรามานิ

หากคุณมีภาพสำหรับจอแสดงผลเรตินาที่ตั้งชื่อด้วย @ 2x ภาพเหล่านั้นจะแสดงรายการว่าไม่ได้ใช้ คุณสามารถกำจัดสิ่งนั้นได้โดยเพิ่ม if-statement พิเศษ: if [["$ name"! = @ 2x ]]; แล้ว
สเตน

3
Cmd + Opt + a ดูเหมือนจะไม่ทำงานบน XCode 5 อีกต่อไปควรทริกเกอร์อะไร
powtac

cmd + opt + a ดูเหมือนจะไม่เป็นสีเทาของไฟล์ใน Images.xcassets แม้ว่าจะเป็นส่วนหนึ่งของโครงการ :(
tettoffensive

80

นี่เป็นโซลูชันที่มีประสิทธิภาพมากขึ้นซึ่งจะตรวจสอบสิ่งใด ๆอ้างอิงถึง basename ในไฟล์ข้อความใด ๆ สังเกตวิธีแก้ปัญหาข้างต้นที่ไม่มีไฟล์สตอรี่บอร์ด (เข้าใจได้อย่างสมบูรณ์ไม่มีอยู่ในขณะนั้น)

Ack ทำให้สิ่งนี้ค่อนข้างเร็ว แต่มีการปรับให้เหมาะสมที่ชัดเจนหากสคริปต์นี้ทำงานบ่อยๆ รหัสนี้จะตรวจสอบทุกชื่อฐานสองครั้งหากคุณมีทั้งเนื้อหาเรตินา / ไม่ใช่เรตินา

#!/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
ติดตั้งHomebrewแล้วทำbrew install ack.
Marko

1
ขอบคุณ. คำตอบนี้ยังจัดการไฟล์และโฟลเดอร์ที่มีช่องว่างอย่างถูกต้อง
djskinner

2
@ จอห์นนี่คุณต้องทำให้ไฟล์เรียกใช้งานได้ ( chmod a+x FindUnusedImages.sh) จากนั้นเรียกใช้เหมือนโปรแกรมอื่น ๆ จาก bash./FindUnusedImages.sh
Mike Sprague

2
ฉันได้ทำการแก้ไขเพื่อละเว้นไฟล์ pbxproj (จึงไม่สนใจไฟล์ที่อยู่ในโปรเจ็กต์ xcode แต่ไม่ได้ใช้ในโค้ดหรือ nibs / สตอรี่บอร์ด): result=`ack --ignore-file=match:/.\.pbxproj/ -i "$file"` สิ่งนี้ต้องใช้ ack 2.0 ขึ้นไป
Mike

2
milanpanchal คุณสามารถวางสคริปต์ได้ทุกที่และคุณเพียงเรียกใช้งานจากไดเร็กทอรีใดก็ได้ที่คุณต้องการใช้เป็นรูทสำหรับค้นหารูปภาพ (เช่นโฟลเดอร์รูทโปรเจ็กต์ของคุณ) คุณสามารถใส่ไว้ใน ~ / script / ตัวอย่างเช่นจากนั้นไปที่โฟลเดอร์รูทโปรเจ็กต์ของคุณและเรียกใช้โดยชี้ไปที่สคริปต์โดยตรง: ~ / script / unused_images.sh
Erik van der Neut

25

โปรดได้ลองLSUnusedResources

มันได้รับอิทธิพลอย่างมากจากUnusedของ jeffhodnett แต่จริงๆแล้ว Unused นั้นช้ามากและผลลัพธ์ก็ไม่ถูกต้องทั้งหมด ดังนั้นฉันจึงทำการเพิ่มประสิทธิภาพบางอย่างความเร็วในการค้นหาเร็วกว่าที่ไม่ได้ใช้


2
ว้าวเป็นเครื่องมือที่ยอดเยี่ยม! ดีกว่าการพยายามเรียกใช้สคริปต์เหล่านั้นมาก คุณสามารถมองเห็นภาพทั้งหมดที่ไม่ได้ใช้และลบภาพที่คุณต้องการ gotcha หนึ่งที่ฉันพบแม้ว่ามันจะไม่ได้รับภาพที่อ้างถึงใน plist
RyanG

1
สุดยอดแน่นอนและช่วยวันของฉัน! ทางออกที่ดีที่สุดในเธรด คุณร็อค
Jakehao

2
หนึ่งในด้ายที่ดีที่สุด ฉันหวังว่าสิ่งนี้จะสูงขึ้นและฉันสามารถโหวตได้มากกว่าหนึ่งครั้ง!
Yoav Schwartz

คุณรู้หรือไม่ว่ามีบางอย่างที่คล้ายกันนี้ แต่สำหรับการตรวจจับรหัสตาย? ตัวอย่างเช่นสำหรับวิธีการที่ไม่ถูกเรียกอีกต่อไป (อย่างน้อยก็ไม่มีการเรียกแบบคงที่อีกต่อไป)
superpuccio

24

ฉันลองใช้วิธีแก้ปัญหาของ Roman และเพิ่มการปรับแต่งเล็กน้อยเพื่อจัดการกับภาพเรตินา ใช้งานได้ดี แต่โปรดจำไว้ว่าชื่อรูปภาพสามารถสร้างขึ้นโดยใช้โปรแกรมในโค้ดและสคริปต์นี้จะแสดงรายการรูปภาพเหล่านี้อย่างไม่ถูกต้องว่าไม่ได้อ้างอิง ตัวอย่างเช่นคุณอาจมี

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

สคริปต์นี้จะคิดไม่ถูกต้อง image_1.pngไม่มีการอ้างอิง

นี่คือสคริปต์ที่แก้ไข:

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

สิ่งที่ไม่@ 2xทำในสวิทช์ต่อท้ายสำหรับ basename?
ท่าดอน

3
FYI โฟลเดอร์ที่มีช่องว่างในชื่อทำให้เกิดปัญหากับสคริปต์
Steve

3
หากคุณพบข้อผิดพลาด: ไม่มีไฟล์หรือไดเร็กทอรีดังกล่าวอาจเป็นเพราะช่องว่างในเส้นทางไฟล์ จำเป็นต้องเพิ่มเครื่องหมายคำพูดในบรรทัด grep ดังนั้นจึงเป็นไปได้: if! grep -qhs "$ name" "$ PROJ";
Lukasz

3
สคริปต์นี้แสดงรายการไฟล์ทั้งหมดของฉัน
jjxtra

2
ฉันไม่รู้ว่าทำไมมันถึงไม่ได้ผลสำหรับฉันมันให้ภาพ png ทั้งหมดแก่ฉัน
Omer Obaid

12

อาจจะลองเรียวก็ได้งานที่ดี

อัปเดต:ด้วยแนวคิด emcmanus ฉันจึงดำเนินการสร้างยูทิลิตี้ขนาดเล็กโดยไม่ต้องใช้ ack เพียงเพื่อหลีกเลี่ยงการตั้งค่าเพิ่มเติมในเครื่อง

https://github.com/arun80/xcodeutils


1
Slender เป็นแอพที่ต้องชำระเงิน ผลบวกที่ผิดพลาดหลายประการและไม่ดีสำหรับผลิตภัณฑ์เชิงพาณิชย์ สคริปต์ที่จัดทำโดย emcmanus นั้นยอดเยี่ยมมาก
อรุณ

6

มีเพียงสคริปต์นี้เท่านั้นที่ใช้งานได้สำหรับฉันซึ่งยังจัดการพื้นที่ในชื่อไฟล์:

แก้ไข

อัปเดตเพื่อรองรับswiftไฟล์และcocoapod. โดยค่าเริ่มต้นจะไม่รวม Pods dir และตรวจสอบเฉพาะไฟล์โครงการ หากต้องการเรียกใช้เพื่อตรวจสอบโฟลเดอร์ Pods ด้วยให้รันด้วย--podattrbiute:

/.finunusedimages.sh --pod

นี่คือสคริปต์จริง:

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

สคริปต์นี้จะมีการทำเครื่องหมายมากเกินไปที่ใช้ทรัพยากรที่ไม่ได้ใช้ จำเป็นต้องปรับปรุง
Artem Shmatkov

นอกจากนี้ยังไม่ชอบลำดับชั้นของโครงการที่ใหญ่และลึก: ./findunused.sh: บรรทัดที่ 28: / usr / bin / grep: รายการอาร์กิวเมนต์ยาวเกินไป
Martin-Gilles Lavoie

3

ฉันได้ปรับเปลี่ยนคำตอบที่ยอดเยี่ยมของ @EdMcManus เล็กน้อยเพื่อจัดการโครงการที่ใช้แคตตาล็อกสินทรัพย์

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

ฉันไม่ได้เขียนสคริปต์ทุบตีจริงๆดังนั้นหากมีการปรับปรุงที่นี่ (น่าจะ) แจ้งให้เราทราบในความคิดเห็นและฉันจะอัปเดต


ฉันมีปัญหาเกี่ยวกับช่องว่างในชื่อไฟล์ ฉันพบว่ามีประโยชน์ในการตั้งค่า `` IFS = $ '\ n' 'ก่อนโค้ด (อันนี้ตั้งค่าตัวคั่นฟิลด์ภายในเป็นบรรทัดใหม่) - จะไม่ทำงานหากไฟล์มีชื่อใหม่อีกครั้ง
Laura Calinoiu

2

คุณสามารถสร้างเชลล์สคริปต์ที่grepซอร์สโค้ดของคุณและเปรียบเทียบอิมเมจที่สร้างขึ้นกับโฟลเดอร์โปรเจ็กต์ของคุณ

นี่คือผู้ชายสำหรับGREPและLS

คุณสามารถวนซ้ำไฟล์ต้นฉบับทั้งหมดของคุณได้อย่างง่ายดายบันทึกภาพในอาร์เรย์หรือสิ่งที่เท่าเทียมและใช้งานได้

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

ด้วยเคล็ดลับนี้คุณสามารถค้นหาภาพทั้งหมดในซอร์สโค้ดโครงการของคุณได้ !!

หวังว่านี่จะช่วยได้!


2

ฉันเขียนสคริปต์ lua ฉันไม่แน่ใจว่าฉันสามารถแบ่งปันได้เพราะฉันทำในที่ทำงาน แต่มันก็ใช้ได้ดี โดยทั่วไปจะทำสิ่งนี้:

ขั้นตอนที่หนึ่ง - การอ้างอิงรูปภาพคงที่ (บิตง่าย ๆ ซึ่งครอบคลุมโดยคำตอบอื่น ๆ )

  • ดูซ้ำผ่าน dirs รูปภาพและดึงชื่อรูปภาพออกมา
  • แถบชื่อภาพเป็น. png และ @ 2x (ไม่จำเป็น / ใช้ใน imageNamed :)
  • ค้นหาด้วยข้อความสำหรับชื่อภาพแต่ละภาพในไฟล์ต้นฉบับ (ต้องอยู่ในตัวอักษรสตริง)

ขั้นตอนที่สอง - การอ้างอิงรูปภาพแบบไดนามิก (บิตสนุก)

  • ดึงรายการลิเทอรัลสตริงทั้งหมดในแหล่งที่มาที่มีตัวระบุรูปแบบ (เช่น% @)
  • แทนที่ตัวระบุรูปแบบในสตริงเหล่านี้ด้วยนิพจน์ทั่วไป (เช่น "foo% dbar" กลายเป็น "foo [0-9] * bar"
  • ค้นหาข้อความผ่านชื่อรูปภาพโดยใช้สตริง regex เหล่านี้

จากนั้นลบสิ่งที่ไม่พบในการค้นหา

ขอบกรณีคือชื่อภาพที่มาจากเซิร์ฟเวอร์ไม่ได้รับการจัดการ ในการจัดการสิ่งนี้เรารวมรหัสเซิร์ฟเวอร์ในการค้นหานี้


เรียบร้อย. ด้วยความอยากรู้อยากเห็นมียูทิลิตี้บางอย่างสำหรับการเปลี่ยนตัวระบุรูปแบบเป็นตัวแทน regexes หรือไม่? แค่คิดว่ามีความซับซ้อนมากมายที่คุณต้องจัดการเพื่อรองรับตัวระบุและแพลตฟอร์มทั้งหมดอย่างถูกต้อง (เอกสารระบุรูปแบบ)
Ed McManus

2

คุณสามารถลองFauxPas App สำหรับ Xcode เป็นการดีมากในการค้นหาภาพที่หายไปและปัญหา / การละเมิดอื่น ๆ ที่เกี่ยวข้องกับโครงการ Xcode


ดูเหมือนว่าสิ่งนี้ยังไม่ได้รับการอัปเดตตั้งแต่ Xcode 9 สามารถยืนยันได้ว่าใช้ไม่ได้กับ Xcode 11
Robin Daugherty

2

คำตอบนี้เป็นตัวอย่างที่ดีในการเพิกเฉยต่อภาพในสองไดเรกทอรีและไม่ค้นหาการเกิดขึ้นของภาพในไฟล์ pbxproj หรือ xcassets (โปรดระวังไอคอนแอปและหน้าจอเริ่มต้น) เปลี่ยน * ใน --ignore-dir = *. xcassets ให้ตรงกับไดเร็กทอรีของคุณ:

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

ฉันใช้กรอบนี้: -

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

ใช้งานได้ดี! มีเพียง 2 สถานที่ที่ฉันพบปัญหาคือเมื่อชื่อรูปภาพมาจากเซิร์ฟเวอร์และเมื่อชื่อเนื้อหารูปภาพแตกต่างจากชื่อของรูปภาพในโฟลเดอร์เนื้อหา ...


สิ่งนี้ไม่ได้มองหาเนื้อหาเฉพาะสำหรับไฟล์รูปภาพที่ไม่ได้อ้างอิงโดยตรง หากคุณกำลังใช้ Assets อย่างที่ควรจะเป็นเครื่องมือนี้จะไม่เหมาะกับคุณ
Robin Daugherty

0

ใช้http://jeffhodnett.github.io/Unused/เพื่อค้นหาภาพที่ไม่ได้ใช้


สำหรับฉันแล้วดูเหมือนว่าแอปนี้ไม่สามารถจัดการพื้นที่ในชื่อโฟลเดอร์ได้ดี และค่อนข้างช้าสำหรับโครงการใหญ่โครงการหนึ่งของฉัน
ingaham

0

ผมได้สร้างสคริปต์หลามเพื่อระบุภาพที่ไม่ได้ใช้: 'unused_assets.py' @ สรุปสาระสำคัญ สามารถใช้งานได้ดังนี้:

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

กฎบางประการในการใช้สคริปต์มีดังนี้

  • สิ่งสำคัญคือต้องส่งเส้นทางโฟลเดอร์โครงการเป็นอาร์กิวเมนต์แรกเส้นทางโฟลเดอร์สินทรัพย์เป็นอาร์กิวเมนต์ที่สอง
  • สันนิษฐานว่ารูปภาพทั้งหมดจะถูกเก็บรักษาไว้ในโฟลเดอร์ Assets.xcassets และใช้ภายในไฟล์ที่รวดเร็วหรือภายในสตอรี่บอร์ด

ข้อ จำกัด ในเวอร์ชันแรก:

  • ไม่ทำงานกับไฟล์ c วัตถุประสงค์

ฉันจะพยายามปรับปรุงอยู่ตลอดเวลาตามข้อเสนอแนะอย่างไรก็ตามเวอร์ชันแรกน่าจะดีที่สุด

โปรดดูรหัสด้านล่าง รหัสควรจะอธิบายตนเองที่ผมได้เพิ่มความคิดเห็นที่เหมาะสมกับแต่ละขั้นตอนที่สำคัญ

# 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!")
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.