swift脚本编程:一键生成AppIcon

自从Xcode8之后就不支持插件了,没法用Xcode一键生成AppIcon,一直没找到好的解决方案,一怒之下决定自己写一个脚本用来生成AppIcon,下面是正文,小弟抛砖引玉,有写的不好的地方有请大佬们见谅:

源码地址

事前准备

查看swift版本

首先你要确定你的Mac上的swift版本:

swift --version

我电脑上的执行结果是这样的:

Apple Swift version 4.0 (swiftlang-900.0.65 clang-900.0.37)
Target: x86_64-apple-macosx10.9

然后就可以用Xcode建一个swift文件来编写swift脚本了,不过单独建一个swift文件,Xcode编辑起来非常不友好,我的方案是建一个在Mac上运行的Command Line Tool工程,这样的话有代码提示,要不然写起来太痛苦,如果大佬们有更好的办法,可以指导一下小弟。

swift脚本编程小知识

终端输入和输出

刚入手脚本我们第一件事前就应该了解在终端如何进行输入和输出,下面是输入和输出的办法:

输出

输入很简单,大家也很熟悉,就是print,下面是代码示例:

print("Hello world!")

然后大家可以执行以下试试(test.swift是你的文件名):

swift test.swift

执行后就能在终端上看到一行字:Hello world!

这样子我们的第一个swift脚本就完成了。

输入

知道了怎么输出我们还得知道怎么输入,输入也非常简单,下面是代码示例:

print("请输入文字:")
if let input = readLine() {
    print("你输入的文字:\(input)")
}

执行之后显示的结果:

请输入文字:
Hello world!
你输入的文字:Hello world!

这样输入也完成了,我们也算swift脚本编程入门了。

在swift脚本中调用其他命令

我们经常用的命令有很多,比如echo、mkdir、cd等等,我们能不能在swift中直接调用呢,答案是可以的,下面我们用简单的例子来了解一下,大家想深入的话可以去研究一下传送门

import Foundation

func execute(path: String, arguments: [String]? = nil) -> Int {
    let process = Process()
    process.launchPath = path
    if arguments != nil {
        process.arguments = arguments!
    }
    process.launch()
    process.waitUntilExit()
    return Int(process.terminationStatus)
    
}

let status = execute(path: "/bin/ls")
print("Status = \(status)")

以上的脚本相当于在终端中执行了ls命令,如果大家不知道命令的路径的话,可以用where查找一下,例如:

where ls

这是执行后的结果:

ls: aliased to ls -G
/bin/ls

这里的/bin/ls就是ls命令的路径。

开始编写脚本

读取input.png

首先我们要从将需要转化的图片读取出来,下面是主要代码:

import Foundation

let inputPath = "input.png"
let inoutData = try Data(contentsOf: url)
print("图片大小:\(inoutData.count / 1024) kb")
let dataProvider = CGDataProvider(data: inoutData as CFData)
if let inputImage = CGImage(pngDataProviderSource: dataProvider!, decode: nil, shouldInterpolate: true, intent: .defaultIntent) {
    /// inputImage就是需要转化的图片
}else {
    print("转换失败,图片必须是png格式")
}

生成AppIcon.appiconset和Contents.json

这里就设计到文件操作了,用FileManager就行了,相信大家已经轻车熟路了,我就贴一些主要代码,大家看完整版去我的github源码看就行了:

import Foundation

/// AppIcon的model
struct AppIconImageItem: Codable {
    let size: String
    let idiom: String
    let filename: String
    let scale: String
    let role: String?
    let subtype: String?
}

struct AppIconInfo: Codable {
    let version: Int
    let author: String
}

struct AppIcon: Codable {
    var images: [AppIconImageItem]
    let info: AppIconInfo
}


/// 创建contentsJson
///
/// - Parameter appIcon: 传入的appIcon
func createAppIconContentsJson(appIcon: AppIcon) {
    print("\n开始生成contentsJson\n")
    let encoder = JSONEncoder()
    do {
        encoder.outputFormatting = .prettyPrinted
        let appIconData = try encoder.encode(appIcon)
        if let appIconStr  = String.init(data: appIconData, encoding: .utf8) {
            let contentJsonPath = "AppIcon.appiconset/Contents.json"
            let contentJsonUrl = URL(fileURLWithPath: contentJsonPath)
            try appIconStr.write(to: contentJsonUrl, atomically: true, encoding: .utf8)
            print("contentsJson生成成功\n")
        }else {
            print("contentsJson生成失败")
        }
    }catch {
        print(error.localizedDescription)
    }
}

/// 创建appicon文件
///
/// - Parameter appIcon: appicon
func createFile(appIcon: AppIcon, image: CGImage) {
    let fileManager = FileManager.default
    let filePath = "AppIcon.appiconset"
    do {
        if fileManager.fileExists(atPath: filePath) {
            try fileManager.removeItem(atPath: filePath)
        }
        try fileManager.createDirectory(atPath: filePath, withIntermediateDirectories: true, attributes: nil)
        createAppIconContentsJson(appIcon: appIcon)
        print("~~~~~~~~~~~~~~完成~~~~~~~~~~~~~~")
    }catch {
        print("文件目录\(filePath)创建失败")
        print(error.localizedDescription)
    }
}

生成不同尺寸的image

生成图片我们用的是Foundation框架里面的Core Graphics框架,下面是主要代码:

import Foundation


/// 生成单个image
///
/// - Parameters:
///   - size: 图片的size
///   - scale: 倍数,例如@2x就是2倍
///   - filename: 文件名
func createImage(size: CGSize, scale: CGFloat, image: CGImage, filename: String) {
    print("开始生成图片: \(filename)")
    let width  = Int(size.width * scale)
    let height = Int(size.height * scale)
    let bitsPerComponent = image.bitsPerComponent
    let bytesPerRow = image.bytesPerRow
    let colorSpace  = image.colorSpace
    
    if let context = CGContext.init(data: nil,
                                    width: width,
                                    height: height,
                                    bitsPerComponent: bitsPerComponent,
                                    bytesPerRow: bytesPerRow,
                                    space: colorSpace!,
                                    bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue) {
        context.interpolationQuality = .high
        context.draw(image, in: .init(origin: .zero, size: .init(width: width, height: height)))
        if let inputImage = context.makeImage() {
            let outputImagePath = "AppIcon.appiconset/\(filename)"
            let outputUrl = URL(fileURLWithPath: outputImagePath) as CFURL
            let destination = CGImageDestinationCreateWithURL(outputUrl, kUTTypePNG, 1, nil)
            if let destination = destination {
                CGImageDestinationAddImage(destination, inputImage, nil)
                if CGImageDestinationFinalize(destination) {
                    print("图片: \(filename) 生成成功\n")
                }else {
                    print("图片: \(filename) 生成失败\n")
                }
            }
        }else {
            print("图片: \(filename) 生成失败\n")
        }
    }
}

最后给大家贴以下完成的截图:


上面只是一部分主要代码,完整的代码太多了,大家可以去我的github地址上去下载执行以下试试,如果有什么做的不好的地方,欢迎大家指教~~

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

推荐阅读更多精彩内容