美文网首页
手把手教你编写Swift脚本——一条命令完成AppIcon更换需

手把手教你编写Swift脚本——一条命令完成AppIcon更换需

作者: zackzheng | 来源:发表于2021-07-19 21:20 被阅读0次

    本文始发于我的博文手把手教你编写Swift脚本——一条命令更换AppIcon,现转发至此。

    Swift是世界上最好的语言!

    • 背景
    • 脚本使用
    • 脚本具体实现
      • 数据结构
      • 创建AppIcon.appiconset文件夹
      • 创建Contents.json文件的素材组织结构
      • 创建Contents.json文件并写入素材组织结构
      • 读取素材文件
      • 批量压缩成对应的素材
      • 使用Shell提交commit

    背景

    提倡培养自动化思维处理日常工作,提高效率,时间和精力聚焦到更关键的点上。

    对于具有重复性或周期性的工作可以思考使用各种自动化方式去解决,如替换App Icon的工作。

    脚本使用

    目前实现的功能是:

    1. 生成AppIcon.appiconset文件夹和Contents.json文件
    2. 生成所有需要的尺寸素材
    3. 提交commit
    • 使用

    执行reduceAppIconAndPush.swift文件,带上素材的相对路径

    swift reduceAppIconAndPush.swift ../../icon-1024.png
    

    因为会提交commit,所以本地最好不要有未提交的commit,不然会一并被push上远端

    commit只会包含Contents.json文件和素材文件,对本地其他未暂存的文件没有影响;已暂存的内容会一并被提交

    脚本具体实现

    抽出自动化流程为

    1. 创建AppIcon.appiconset文件夹
    2. 创建Contents.json文件
    3. Contents.json文件写入素材的组织结构
    4. 批量压缩成对应的素材
    5. 提交改动commit

    相比使用Shell或者Python,使用Swift命令行模式实现,更易于编写和方便团队其他人维护。

    数据结构

    struct AppIconImageItem: Codable {
        let size: String
        let idiom: String
        let filename: String
        let scale: String
    }
    
    struct AppIconInfo: Codable {
        let version: Int
        let author: String
    }
    
    struct AppIcon: Codable {
        var images: [AppIconImageItem]
        let info: AppIconInfo
    }
    

    创建AppIcon.appiconset文件夹

    func createAppIconSetFile(filePath: String, appIcon: AppIcon) -> Bool {
        let fileManager = FileManager.default
        do {
            if fileManager.fileExists(atPath: filePath) {
                try fileManager.removeItem(atPath: filePath)
            }
            try fileManager.createDirectory(atPath: filePath, withIntermediateDirectories: true, attributes: nil)
            print("\(filePath) 文件夹创建成功")
            return true
        }catch {
            print("【失败】\(filePath) 文件夹创建失败")
            print(error.localizedDescription)
            return false
        }
    }
    

    创建Contents.json文件的素材组织结构

    func createAppIconModel() -> AppIcon {
        let images: [AppIconImageItem] = [
            AppIconImageItem(size: "20x20", idiom: "iphone", filename: "icon-20@2x.png", scale: "2x"),
            AppIconImageItem(size: "20x20", idiom: "iphone", filename: "icon-20@3x.png", scale: "3x"),
    
            AppIconImageItem(size: "29x29", idiom: "iphone", filename: "icon-29.png", scale: "1x"),
            AppIconImageItem(size: "29x29", idiom: "iphone", filename: "icon-29@2x.png", scale: "2x"),
            AppIconImageItem(size: "29x29", idiom: "iphone", filename: "icon-29@3x.png", scale: "3x"),
            
            AppIconImageItem(size: "40x40", idiom: "iphone", filename: "icon-40@2x.png", scale: "2x"),
            AppIconImageItem(size: "40x40", idiom: "iphone", filename: "icon-40@3x.png", scale: "3x"),
            
            AppIconImageItem(size: "60x60", idiom: "iphone", filename: "icon-60@2x.png", scale: "2x"),
            AppIconImageItem(size: "60x60", idiom: "iphone", filename: "icon-60@3x.png", scale: "3x"),
            
            AppIconImageItem(size: "20x20", idiom: "ipad", filename: "icon-20-ipad.png", scale: "1x"),
            AppIconImageItem(size: "20x20", idiom: "ipad", filename: "icon-20@2x-ipad.png", scale: "2x"),
            
            AppIconImageItem(size: "29x29", idiom: "ipad", filename: "icon-29-ipad.png", scale: "1x"),
            AppIconImageItem(size: "29x29", idiom: "ipad", filename: "icon-29@2x-ipad.png", scale: "2x"),
            
            AppIconImageItem(size: "40x40", idiom: "ipad", filename: "icon-40.png", scale: "1x"),
            AppIconImageItem(size: "40x40", idiom: "ipad", filename: "icon-40@2x.png", scale: "2x"),
            
            AppIconImageItem(size: "76x76", idiom: "ipad", filename: "icon-76.png", scale: "1x"),
            AppIconImageItem(size: "76x76", idiom: "ipad", filename: "icon-76@2x.png", scale: "2x"),
            
            AppIconImageItem(size: "83.5x83.5", idiom: "ipad", filename: "icon-83.5@2x.png", scale: "2x"),
            
            AppIconImageItem(size: "1024x1024", idiom: "ios-marketing", filename: "icon-1024.png", scale: "1x"),
        ]
    
        let info = AppIconInfo(version: 1, author: "zhengzuanzhe")
        let appIcon = AppIcon(images: images, info: info)
        return appIcon
    }
    

    创建Contents.json文件并写入素材组织结构

    func createContentsJsonFile(filePath: String, appIcon: AppIcon) -> Bool {
    
        let encoder = JSONEncoder()
        do {
            encoder.outputFormatting = .prettyPrinted
            let appIconData = try encoder.encode(appIcon)
            if let appIconStr = String(data: appIconData, encoding: .utf8) {
                let contentJsonUrl = URL(fileURLWithPath: filePath)
                try appIconStr.write(to: contentJsonUrl, atomically: true, encoding: .utf8)
                print("\(filePath) 文件创建成功")
                return true
            } else {
                print("【失败】\(filePath) 文件创建失败")
                return false
            }
        } catch {
            print(error.localizedDescription)
            return false
        }
    }
    

    读取素材文件

    func cgImageWithPath(_ path: String) -> CGImage? {
    
        let url = URL(fileURLWithPath: path)
        guard let data = try? Data(contentsOf: url), let dataProvider = CGDataProvider(data: data as CFData), let image = CGImage(pngDataProviderSource: dataProvider, decode: nil, shouldInterpolate: true, intent: .defaultIntent) else {
            return nil
        }
        return image
    }
    

    批量压缩成对应的素材

    func createImage(size: CGSize, scale: CGFloat, image: CGImage, filename: String) {
        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(data: nil,
                                   width: width,
                                   height: height,
                                   bitsPerComponent: bitsPerComponent,
                                   bytesPerRow: bytesPerRow,
                                   space: colorSpace!,
                                   bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue) {
            context.interpolationQuality = .high
            context.draw(image, in: CGRect(origin: .zero, size: CGSize(width: width, height: height)))
            if let inputImage = context.makeImage() {
                let outputImagePath = "\(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) 生成成功")
                    }else {
                        print("【失败】图片 \(filename) 生成失败")
                    }
                }
            }else {
                print("【失败】图片 \(filename) 生成失败")
            }
        }
    }
    

    使用Shell提交commit

    @discardableResult
    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)
    }
    
    execute(path: "/usr/bin/git", arguments: ["add", "\(contentJsonPath)"])
    appIcon.images.forEach({ imageItem in
        let filename = "\(appIconSetFilePath)/\(imageItem.filename)"
        execute(path: "/usr/bin/git", arguments: ["add", "\(filename)"])
    })
    execute(path: "/usr/bin/git", arguments: ["commit", "-m", "chore: 替换AppIcon"])
    execute(path: "/usr/bin/git", arguments: ["push", "origin"])
    

    相关文章

      网友评论

          本文标题:手把手教你编写Swift脚本——一条命令完成AppIcon更换需

          本文链接:https://www.haomeiwen.com/subject/nhlsultx.html