-
-
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.