使用脚本结合Xcode的Asset Catalogs功能对图片和颜色的管理

在Xcode中使用Asset Catalogs大致效果如下图


asset.png

代码调用

UIColor(named: "greenx")

UIImage(named: "Image")

与我们平时调用的代码没有什么区别。

这样调用代码里面会出现很对的硬编码,会导致代码不易维护,资源管理也极其不方便。

为了减少硬编码的对代码管理带来的不便可以统一管理资源的硬编码,创建管理类来维护这些资源。

颜色管理类

import UIKit
extension UIColor {
//start sync tag
    // R: 0.717, G: 0.200 , B: 0.300, A: 1.000                    
    // R: 182  , G: 51    , B: 76   , A: 255                      
    // R: B6   , G: 33    , B: 4C   , A: FF                       
    public static var asdw: UIColor? {                    
        _ = #colorLiteral(red: 0.717, green: 0.200, blue: 0.300, alpha: 1.000)                    
        return UIColor(named: "asdw")                    
    }
        ........
//end sync tag
}

代码效果


color_code.png

图片管理类

import UIKit
extension UIImage {
//start sync tag
    public static var folder: UIImage? {        
        _ = #imageLiteral(resourceName: "folder")        
        return UIImage(named: "folder")        
    }
//end sync tag
}

图片效果


image_code.png

资源的使用就不会出现硬编码了

let color = UIColor.asdw
let image = UIImage.folder

但是还是需要我们手动去维护这些个管理类,也是一件比较麻烦的事。

我们可以使用Xcode的Run Script来帮助我门来完成这个管理类的维护工作,这样就能帮助我们省去很多的工作量


runscript.png

我们可以使用脚本来帮助我们完成管理类与Assets Catalogs的同步工作,每次编译时都会进行一次同步(Run Script的位置决定了脚本的执行顺序,建议放在Compile Sources的上面),同步后在进行代码的编译。

这样就可以直接知道资源的使用错误。

下面直接上脚本,这里我用的是Python脚本

颜色同步脚本

import sys
import os
import json

def sync_color_file():
    temp_file = None
    if os.path.exists(color_file_path):
        os.rename(color_file_name, temp_file_name)
        temp_file = open(temp_file_path, 'r')
        color_file = open(color_file_path, "w+")
        while True:
            line = temp_file.readline()
            color_file.write(line)
            if start_tag in line:
                break
    else:
        color_file = open(color_file_path, "w+")
        color_file.write('//\
            \n//  {}\
            \n//  {}\
            \n//\
            \n//  Created by XXXX on 9999/99/99.\
            \n//\
            \n\
            \nimport UIKit\
            \n\
            \nextension UIColor {{\
            \n{}\n'.format(color_file_name, project_name, start_tag))
    color_file.write(analysis_assets())
    start_write_tail = False
    if temp_file:
        while True:
            line = temp_file.readline()
            if end_tag in line:
                start_write_tail = True
            if start_write_tail:
                color_file.write(line)
            if line == '':
                break
        if not start_write_tail:
            color_file.write('\n{}\n}}'.format(end_tag))
        temp_file.close()
        os.remove(temp_file_path)
    else:
        color_file.write('\n{}\n}}'.format(end_tag))
    color_file.close()

def analysis_assets():
    sync_result_str = ''
    for folder, subFolders, files in os.walk("Assets.xcassets"):
        color_name = os.path.basename(folder)
        color_suffix = '.colorset'
        if not color_name.endswith(color_suffix):
            continue
        param_name = color_name.replace(color_suffix, '')
        if param_name in system_color_names:
            print('!!!!!color name is same as system color name <<<{}>>>!!!!! '.format(param_name))
            continue
        if 'Contents.json' not in files:
            continue
        with open(os.path.join(folder, 'Contents.json'), 'r') as load_f:
            load_dict = json.load(load_f)
            for color_item in load_dict['colors']:
                # appearances = color_item.get('appearances', None)
                # idiom = color_item.get('idiom', None)
                color = color_item.get('color', None)
                if not color:
                    continue
                color_components = color['components']
                sync_result_str += '\
                    \n\t// R: {R}, G: {G} , B: {B}, A: {A}\
                    \n\t// R: {R8}, G: {G8} , B: {B8}, A: {A8}\
                    \n\t// R: {RH}, G: {GH} , B: {BH}, A: {AH}\
                    \n\tpublic static var {name}: UIColor? {{\
                    \n\t\t_ = #colorLiteral(red: {R}, green: {G}, blue: {B}, alpha: {A})\
                    \n\t\treturn UIColor(named: "{name}")\
                    \n\t}}\n'.format(name=param_name,
                                     R=color_to_float(color_components['red']),
                                     G=color_to_float(color_components['green']),
                                     B=color_to_float(color_components['blue']),
                                     A=color_to_float(color_components['alpha']),
                                     R8=color_to_8_bit(color_components['red']),
                                     G8=color_to_8_bit(color_components['green']),
                                     B8=color_to_8_bit(color_components['blue']),
                                     A8=color_to_8_bit(color_components['alpha']),
                                     RH=color_to_bit_h(color_components['red']),
                                     GH=color_to_bit_h(color_components['green']),
                                     BH=color_to_bit_h(color_components['blue']),
                                     AH=color_to_bit_h(color_components['alpha']))
                break
    return sync_result_str

def color_to_float(value):
    if '.' in value:
        return value
    elif 'X' in value or 'x' in value:
        return '{:0.3f}'.format(float(int(value[-2:], 16)) / 255)
    else:
        return '{:0.3f}'.format(float(value) / 255)

def color_to_8_bit(value):
    if '.' in value:
        return '{: <5}'.format(int(float(value) * 255))
    elif 'X' in value or 'x' in value:
        return '{: <5}'.format(int(value, 16))
    else:
        return '{: <5}'.format(value)

def color_to_bit_h(value):
    if '.' in value:
        return '{: <5X}'.format(int(float(value) * 255))
    elif 'X' in value or 'x' in value:
        return '{: <5}'.format(value[-2:])
    else:
        return '{: <5X}'.format(int(value))

if __name__ == '__main__':
    project_path = sys.argv[1]
    project_name = sys.argv[2]
    os.chdir(project_path)
    system_color_names = ['black', 'darkGray', 'lightGray', 'white', 'gray', 'red', 'green', 'blue', 'cyan', 'yellow', 'magenta', 'orange', 'purple', 'brown', 'clear']
    color_file_name = 'Colors.swift'
    color_file_path = os.path.join(project_path, color_file_name)
    temp_file_name = 'Colors_temp.swift'
    temp_file_path = os.path.join(project_path, temp_file_name)
    start_tag = '//start sync tag'
    end_tag = '//end sync tag'
    sync_color_file()
    exit(0)

图片同步脚本

import sys
import os
import json

project_path = sys.argv[1]
project_name = sys.argv[2]
os.chdir(project_path)
image_file_name = 'Images.swift'
image_file_path = os.path.join(project_path, image_file_name)
temp_file_name = 'Images_temp.swift'
temp_file_path = os.path.join(project_path, temp_file_name)
start_tag = '//start sync tag'
end_tag = '//end sync tag'
temp_file = None
if os.path.exists(image_file_path):
    os.rename(image_file_name, temp_file_name)
    temp_file = open(temp_file_path, 'r')
    image_file = open(image_file_path, "w+")
    while True:
        line = temp_file.readline()
        image_file.write(line)
        if start_tag in line:
            break
else:
    image_file = open(image_file_path, "w+")
    image_file.write('//\
                \n//  {}\
                \n//  {}\
                \n//\
                \n//  Created by XXXX on 9999/99/99.\
                \n//\
                \n\
                \nimport UIKit\
                \n\
                \nextension UIImage {{\
                \n{}\n'.format(image_file_name, project_name, start_tag))
print('current project path: {}'.format(os.getcwd()))
for folder, subFolders, files in os.walk("Assets.xcassets"):
    image_name = os.path.basename(folder)
    image_suffix = '.imageset'
    if not image_name.endswith(image_suffix):
        continue
    param_name = image_name.replace(image_suffix, '')
    image_file.write('\n\tpublic static var {0}: UIImage? {{\
        \n\t\t_ = #imageLiteral(resourceName: "{0}")\
        \n\t\treturn UIImage(named: "{0}")\
        \n\t}}\n'.format(param_name))
start_write_tail = False
if temp_file:
    while True:
        line = temp_file.readline()
        if end_tag in line:
            start_write_tail = True
        if start_write_tail:
            image_file.write(line)
        if line == '':
            break
    if not start_write_tail:
        image_file.write('\n{}\n}}'.format(end_tag))
    temp_file.close()
    os.remove(temp_file_path)
else:
    image_file.write('\n{}\n}}'.format(end_tag))
image_file.close()
exit(0)

将脚本放到py为后缀的文件中就可以了(示例中用的是color.py和image.py),
然后在Xcode的Run Script里面写入

python3 path/for/script/image.py ${SRCROOT}/${TARGET_NAME} ${PROJECT_NAME}
python3 path/for/script/image.py ${SRCROOT}/${TARGET_NAME} ${PROJECT_NAME} 

直接在shell脚本中调用python脚本,将脚本路径换成自己的存放路径,建议在放在项目根目录下 color.py,这样所有人都可以使用。

这样,每次在编译之前都会进行一次同步,不需要手动去维护这些硬编码了,在编码时也会有相应的提示。

资源的管理也会变得简单,删除后管理类也会同时删除,还能有编译错误来提示删除的资源对代码的影响

可以使用 literal 对资源的代码可视化,在代码中就可以看到颜色效果,和图片的缩略图,如上面的截图,color_code.png和image_code.png

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,445评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,889评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,047评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,760评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,745评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,638评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,011评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,669评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,923评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,655评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,740评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,406评论 4 320
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,995评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,961评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,023评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,483评论 2 342

推荐阅读更多精彩内容