-
-
Save KingOfBrian/d0e91c3f63c94398f349 to your computer and use it in GitHub Desktop.
| import Foundation | |
| /** | |
| * Build script to generate a type safe wrapper around your projects .xcasset file. | |
| * This will fail your build if you reference an image in your .xcasset file that has | |
| * changed or been removed, as well as provide code completion help for all your images. | |
| * | |
| * Copy this file into a new `Run Phase` in your project, with `/usr/bin/env xcrun swift` | |
| * specified for `Shell`. | |
| * | |
| * Configure the variables below: | |
| * | |
| * - outputPath: The location to write the generated file. This file must be manually | |
| * added to your .xcodeproj. | |
| * - path: The location to look for .imageset files (Which reside inside the .xcasset directory) | |
| * - objcCompatible: A flag to use static methods or an enum. | |
| */ | |
| private struct Parameters { | |
| static let outputFile = "./Assets.swift" | |
| static let inputPath = "." | |
| static let objcCompatible = false | |
| static let separationCharacters = NSCharacterSet(charactersInString: "-_ ") | |
| static let indentWidth = 4 | |
| } | |
| // Transform image names in the xcasset to Tokens for use in code | |
| func stringToCamelCase(input:String) -> String { | |
| let parts = input.componentsSeparatedByCharactersInSet(Parameters.separationCharacters) | |
| return parts.reduce("") { methodName, part in | |
| methodName + (methodName == "" ? part : part.capitalizedString) | |
| } | |
| } | |
| func stringToUpperCase(input:String) -> String { | |
| let parts = input.componentsSeparatedByCharactersInSet(Parameters.separationCharacters) | |
| return parts.reduce("") { methodName, part in | |
| methodName + part.capitalizedString | |
| } | |
| } | |
| // Formatting helper | |
| func indent(lines:[String], level:Int = 1) -> [String] { | |
| let space = "".join((0..<Parameters.indentWidth * level).map { (x:Int) in return " "}) | |
| return lines.map { "\(space)\($0)" } | |
| } | |
| // Functions to transform image names to lines of code | |
| func imageNameToSwiftStaticFunc(imageName:String) -> String { | |
| let methodName = stringToCamelCase(imageName) | |
| return "static func \(methodName)() -> UIImage { return UIImage(named: \"\(imageName)\")! }" | |
| } | |
| func imageNameToSwiftEnum(imageName:String) -> String { | |
| let methodName = stringToUpperCase(imageName) | |
| return "case \(methodName) = \"\(imageName)\"" | |
| } | |
| // Higher level transforms from image names to compilable code | |
| let generated = ["/*", | |
| " * WARNING: This file is automatically generated based on the contents", | |
| " * of your .xcasset files. Do not modify.", | |
| " */"] | |
| func swiftImageExtension(imageNames:[String]) -> String { | |
| let functions = indent(imageNames.map(imageNameToSwiftStaticFunc)) | |
| var lines = ["import UIKit", "extension UIImage {", "}"] | |
| lines.splice(functions, atIndex: 2) | |
| lines.splice(generated, atIndex: 0) | |
| return "\n".join(lines) | |
| } | |
| func swiftImageEnum(imageNames:[String]) -> String { | |
| let functions = indent(imageNames.map(imageNameToSwiftEnum), level:2) | |
| var lines = ["import UIKit", | |
| "extension UIImage {", | |
| " enum AssetName : String {", | |
| " }", | |
| " convenience init!(asset:AssetName) {", | |
| " self.init(named:asset.rawValue)", | |
| " }", | |
| "}"] | |
| lines.splice(functions, atIndex: 3) | |
| lines.splice(generated, atIndex: 0) | |
| return "\n".join(lines) | |
| } | |
| // Obtain image names and pass them to a higher level transform function | |
| if let paths = NSFileManager.defaultManager().subpathsAtPath(Parameters.inputPath) { | |
| let imageNames = paths.filter { | |
| $0.pathExtension == "imageset" | |
| }.map { (x:String) -> String in | |
| x.lastPathComponent.stringByDeletingPathExtension | |
| } | |
| let body = Parameters.objcCompatible ? swiftImageExtension(imageNames) : swiftImageEnum(imageNames) | |
| try! body.writeToFile(Parameters.outputFile, atomically:true, encoding: NSUTF8StringEncoding) | |
| } | |
| else { | |
| print(" warning: Unable to find any imageset files in \(Parameters.inputPath)") | |
| } |
Could you use reduce in lines 28 and 36? Also, hate that I can't comment on lines here.
Stylistically I'd prefer
if let paths = opaths {
let imageNames = paths.filter({
return $0.pathExtension == "imageset"
}).map({ (x:String) -> String in
return x.lastPathComponent.stringByDeletingPathExtension
})
let body = objcCompatible ? swiftImageExtension(imageNames) : swiftImageEnum(imageNames)
try! body.writeToFile(outputPath, atomically:true, encoding: NSUTF8StringEncoding)
}
else {
// Complain about error.
}Also, I believe you can omit the ()'s and use trailing closure syntax here, e.g.:
paths.filter {
}.map { (x: String) -> String in
}Thanks @jwatson! I updated indent to use map + join instead too. Pretty sure it's slightly worse, but it doesn't have any var's! Also, I dropped return from the map functions. Not sure if it's correct or better, but thought i'd give it a shot.
Hi @KingOfBrian,
You might be interested to know that I developed a similar tool to generate enums for assets — which actually can also generate enums for various other stuff like UIStoryboards and Localizable.strings, etc…
Anyway, nice to see that we had a similar idea, great minds think alike 😉
Don't hesitate to make suggestions or PR to my code if you see anything that could be improved)
Hey @AliSoftware -- That looks awesome! Will definitely point people in that direction instead.
I like to stash my constants into a struct so that they're namespaced, e.g.:
And then refer to
Parameters.outputPath. Might be overkill for this, but it's great for layout constants.