public
Last active

An Xcode precompilation script to turn your images into auto-completeable, type-checkable symbols.

  • Download Gist
image.py
Python
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133
import os.path as path
import string
import argparse
import glob
import re
 
def basename(filename):
base = filename
if filename.find('@2x') > 0:
base = filename[:filename.find('@2x')]
elif filename.find('~') > 0:
base = filename[:filename.find('~')]
elif filename.find('.') > 0:
base = filename[:filename.find('.')]
return base
 
def file_type(filename):
isRetina = False
isIpad = False
if filename.find('@2x') > 0:
isRetina = True
if filename.find('~ipad') > 0:
isIpad = True
if isRetina:
if isIpad:
return 'ipad2x'
else:
return 'iphone2x'
else:
if isIpad:
return 'ipad'
return 'iphone'
def mungedName(basename):
parts = re.split('[-_]', basename)
capitalized = [word.capitalize() for word in parts]
return string.join(capitalized, '')
class ImageGroup:
iphone = None
iphone2x = None
ipad = None
ipad2x = None
extras = []
def __init__(self, file_name):
setattr(self, file_type(file_name), file_name)
def add_file(self, file_name):
type = file_type(file_name)
if getattr(self, type) is not None:
self.extras.append(file_name)
else:
setattr(self, type, file_name)
def warnings(self, iPhone=True, iPad=True, retina=True, duplicates=True):
definition = ''
if iPhone and self.iphone is None:
definition += '#warning image formatted for iPhone %s not found\n' % filename
if iPhone and retina and self.iphone2x is None:
definition += '#warning image formatted for retina iPhone %s not found\n' % filename
if iPad and self.ipad is None:
definition += '#warning image formatted for iPad %s not found\n' % filename
if iPad and retina and self.ipad2x is None:
definition += '#warning image formatted for retina iPad %s not found\n' % filename
if duplicates:
for file in self.extras:
definition += '#warning duplicate image %s found in project. Verify proper capitalization.\n' % file
return definition
def output(self, filename, prefix):
return args.format % {'prefix':prefix, 'identifier':mungedName(filename), 'filename':filename}
DEFAULT_FORMAT = '#define %(prefix)s%(identifier)s (UIImage*)^{\
UIImage *image = [UIImage imageNamed:@"%(filename)s"];\
ZAssert(image, @"Image %(filename)s not found");\
return image;\
}()\n\n'
 
### cmd folder outputFile
parser = argparse.ArgumentParser(description='Create a header file with contants for each image file in the given folder.')
parser.add_argument('-s', '--source', type=str, default='./', help='A folder which contains images.')
parser.add_argument('-d', '--destination', type=str, default='./images.h', help='The filename to write to.')
parser.add_argument('--prefix', type=str, default='img', help='The prefix added at the begining of each image\'s filename.')
parser.add_argument('--format', type=str, default=DEFAULT_FORMAT, help='The format string specifying how the file should be written')
parser.add_argument('--warn-retina', dest='retina', type=bool, default=True, help='Warn for missing retina images.')
parser.add_argument('--warn-ipad', dest='ipad', type=bool, default=False, help='Warn for missing iPad (~ipad) images.')
parser.add_argument('--warn-iphone', dest='iphone', type=bool, default=False, help='Warn for missing iPhone (~iphone) images')
parser.add_argument('--warn-duplicates', dest='duplicates', type=bool, default=True, help='Warn for duplicate images.')
 
args = parser.parse_args()
source = path.join(path.expanduser(args.source), '*.png')
iterator = glob.iglob(source)
all_files = {}
 
for fullpath in iterator:
(leading, filename) = path.split(fullpath)
key = basename(filename)
 
if key in all_files:
current_file = all_files[key]
current_file.add_file(filename)
else:
current_file = ImageGroup(filename)
all_files[key] = current_file
 
### Florian Bruger ensures the file isn't updated needlessly.
original_output = ''
file_path = path.join(path.expanduser('.'), args.destination)
if path.exists(file_path):
with open(file_path, 'r') as file:
original_output += file.read()
file.close()
 
output = '// Created using the image.py script written by Patrick Hughes. He\'s a pretty cool guy.\n'
output +='//\n// DO NOT EDIT THIS FILE. \n//\n'
output +='// This file is automatically generated. Any changes may be overwritten the next time images.py is invoked.\n\n'
 
for key in all_files:
image_group = all_files[key]
warnings = image_group.warnings(iPhone=args.iphone, iPad=args.ipad, retina=args.retina, duplicates=args.duplicates)
output += warnings
output += image_group.output(key, args.prefix)
 
if original_output == output:
pass
else:
with open(file_path, 'w') as file:
file.write(output)
file.close()

Super useful :)
Only problem: When including the file inside the prefix.pch, Xcode thinks the file did change even when there are no new images.
I modified the script to only write to the file if the output is different than the file content.. I am not a python expert, maybe there is a better way but for now it works well
https://gist.github.com/4672086

If you set the script up as a dependent project, as outlined in the blog post, it creates the images.h file before the pre-compilation step, which avoids the warnings. Nonetheless I've incorporated your test to the script for efficiency's sake.

Glad I found this, great idea!

My images aren't all in a single folder, so I hacked on it a bit and made it recursive. It assumes you use groups, not folder references, as it uses the basename of the image, not the full path from the source directory passed to the script. Although, now that I think of it, an automated solution like this might make folder references feasible! Should be simple to use the full path as the key instead of the basename, if that's what you desire.

Python isn't my weapon of choice, so let me know if there are any egregious trespasses.

https://gist.github.com/rhgills/5847456

Please sign in to comment on this gist.

Something went wrong with that request. Please try again.