XUtils

SwiftGen

A suite of tools to auto-generate code for various assets of your project.


Install bundle if it isn’t installed

gem install bundle

Install the Ruby gems from Gemfile

bundle install


You can now install to the default locations (no parameter) or to custom locations:

```sh
# Binary is installed in `./.build/swiftgen/bin`
$ rake cli:install
# - OR -
# Binary will be installed in `~/swiftgen/bin``
$ rake cli:install[~/swiftgen/bin]

You can then invoke SwiftGen using the path to the binary where you installed it:

~/swiftgen/bin/swiftgen …

Or add the path to the bin folder to your $PATH and invoke swiftgen directly.


Choosing your template

SwiftGen is based on templates (it uses Stencil as its template engine). This means that you can choose the template that fits the Swift version you’re using β€” and also the one that best fits your preferences β€” to adapt the generated code to your own conventions and Swift version.

Bundled templates vs. Custom ones

SwiftGen comes bundled with some templates for each of the parsers (colors, coredata, files, fonts, ib, json, plist, strings, xcassets, yaml), which will fit most needs; simply use the templateName output option to specify the name of the template to use. But you can also create your own templates if the bundled ones don’t suit your coding conventions or needs: just store them anywhere (like in your project repository) and use the templatePath output option instead of templateName, to specify their path.

πŸ’‘ You can use the swiftgen template list command to list all the available bundled templates for each parser, and use swiftgen template cat to show a template’s content and duplicate it to create your own variation.

For more information about how to create your own templates, see the dedicated documentation.

Templates bundled with SwiftGen:

As explained above, you can use swiftgen template list to list all templates bundled with SwiftGen. For most SwiftGen parsers, we provide, among others:

  • A swift4 template, compatible with Swift 4
  • A swift5 template, compatible with Swift 5
  • Other variants, like flat-swift4/5 and structured-swift4/5 templates for Strings, etc.

You can find the documentation for each bundled template here in the repo, with documentation organized as one folder per SwiftGen parser, then one MarkDown file per template. You can also use swiftgen template doc to open that documentation page in your browser directly from your terminal.

Each MarkDown file documents the Swift Version it’s aimed for, the use case for that template (in which cases you might favor that template over others), the available parameters to customize it on invocation (using the params: key in your config file), and some code examples.

Don’t hesitate to make PRs to share your improvements suggestions on the bundled templates πŸ˜‰

Additional documentation

Playground

The SwiftGen.playground available in this repository will allow you to play with the code that the tool typically generates, and see some examples of how you can take advantage of it.

This allows you to have a quick look at how typical code generated by SwiftGen looks like, and how you will then use the generated constants in your code.

Dedicated Documentation in Markdown

There is a lot of documentation in the form of Markdown files in this repository, and in the related StencilSwiftKit repository as well.

Be sure to check the “Documentation” folder of each repository.

Especially, in addition to the previously mentioned Migration Guide and Configuration File documentation, the Documentation/ folder in the SwiftGen repository also includes:

Tutorials

You can also find other help & tutorial material on the internet, like this classroom about Code Generation I gave at FrenchKit in Sept’17 β€” and its wiki detailing a step-by-step tutorial about installing and using SwiftGen (and Sourcery too)


Available Parsers

Asset Catalog

xcassets:
  inputs: /dir/to/search/for/imageset/assets
  outputs:
    templateName: swift5
    output: Assets.swift

This will generate an enum Asset with one static let per asset (image set, color set, data set, …) in your assets catalog, so that you can use them as constants.

Example of code generated by the bundled template ```swift internal enum Asset { internal enum Files { internal static let data = DataAsset(value: "Data") internal static let readme = DataAsset(value: "README") } internal enum Food { internal enum Exotic { internal static let banana = ImageAsset(value: "Exotic/Banana") internal static let mango = ImageAsset(value: "Exotic/Mango") } internal static let `private` = ImageAsset(value: "private") } internal enum Styles { internal enum Vengo { internal static let primary = ColorAsset(value: "Vengo/Primary") internal static let tint = ColorAsset(value: "Vengo/Tint") } } internal enum Symbols { internal static let exclamationMark = SymbolAsset(name: "Exclamation Mark") internal static let plus = SymbolAsset(name: "Plus") } internal enum Targets { internal static let bottles = ARResourceGroupAsset(name: "Bottles") internal static let paintings = ARResourceGroupAsset(name: "Paintings") } } ```

Core Data

coredata:
  inputs: /path/to/model.xcdatamodeld
  outputs:
    templateName: swift5
    output: CoreData.swift

This will parse the specified core data model(s), generate a class for each entity in your model containing all the attributes, and a few extensions if needed for relationships and predefined fetch requests.

Example of code generated by the bundled template ```swift internal class MainEntity: NSManagedObject { internal class var entityName: String { return "MainEntity" } internal class func entity(in managedObjectContext: NSManagedObjectContext) -> NSEntityDescription? { return NSEntityDescription.entity(forEntityName: entityName, in: managedObjectContext) } @nonobjc internal class func makeFetchRequest() -> NSFetchRequest { return NSFetchRequest(entityName: entityName) } @NSManaged internal var attributedString: NSAttributedString? @NSManaged internal var binaryData: Data? @NSManaged internal var boolean: Bool @NSManaged internal var date: Date? @NSManaged internal var float: Float @NSManaged internal var int64: Int64 internal var integerEnum: IntegerEnum { get { let key = "integerEnum" willAccessValue(forKey: key) defer { didAccessValue(forKey: key) } guard let value = primitiveValue(forKey: key) as? IntegerEnum.RawValue, let result = IntegerEnum(rawValue: value) else { fatalError("Could not convert value for key '\(key)' to type 'IntegerEnum'") } return result } set { let key = "integerEnum" willChangeValue(forKey: key) defer { didChangeValue(forKey: key) } setPrimitiveValue(newValue.rawValue, forKey: key) } } @NSManaged internal var manyToMany: Set } // MARK: Relationship ManyToMany extension MainEntity { @objc(addManyToManyObject:) @NSManaged public func addToManyToMany(_ value: SecondaryEntity) @objc(removeManyToManyObject:) @NSManaged public func removeFromManyToMany(_ value: SecondaryEntity) @objc(addManyToMany:) @NSManaged public func addToManyToMany(_ values: Set) @objc(removeManyToMany:) @NSManaged public func removeFromManyToMany(_ values: Set) } ```

Files

files:
  inputs: path/to/search
  filter: .+\.mp4$
  outputs:
    templateName: structured-swift5
    output: Files.swift

The files parser is intended to just list the name and mimetype of the files and subdirectories in a given directory. This will recursively search the specified directory using the given filter (default .*), defining a struct File for each matching file, and an hierarchical enum representing the directory structure of files.

Example of code generated by the bundled template ```swift internal enum Files { /// test.txt internal static let testTxt = File(name: "test", ext: "txt", path: "", mimeType: "text/plain") /// subdir/ internal enum Subdir { /// subdir/A Video With Spaces.mp4 internal static let aVideoWithSpacesMp4 = File(name: "A Video With Spaces", ext: "mp4", path: "subdir", mimeType: "video/mp4") } } ```

Fonts

fonts:
  inputs: /path/to/font/dir
  outputs:
    templateName: swift5
    output: Fonts.swift

This will recursively go through the specified directory, finding any typeface files (TTF, OTF, …), defining a struct FontFamily for each family, and an enum nested under that family that will represent the font styles.

Example of code generated by the bundled template ```swift internal enum FontFamily { internal enum SFNSDisplay: String, FontConvertible { internal static let regular = FontConvertible(name: ".SFNSDisplay-Regular", family: ".SF NS Display", path: "SFNSDisplay-Regular.otf") } internal enum ZapfDingbats: String, FontConvertible { internal static let regular = FontConvertible(name: "ZapfDingbatsITC", family: "Zapf Dingbats", path: "ZapfDingbats.ttf") } } ```

Interface Builder

ib:
  inputs: /dir/to/search/for/storyboards
  outputs:
    - templateName: scenes-swift5
      output: Storyboard Scenes.swift
    - templateName: segues-swift5
      output: Storyboard Segues.swift

This will generate an enum for each of your NSStoryboard/UIStoryboard, with respectively one static let per storyboard scene or segue.

Example of code generated by the bundled template The generated code will look like this: ```swift // output from the scenes template internal enum StoryboardScene { internal enum Dependency: StoryboardType { internal static let storyboardName = "Dependency" internal static let dependent = SceneType(storyboard: Dependency.self, identifier: "Dependent") } internal enum Message: StoryboardType { internal static let storyboardName = "Message" internal static let messagesList = SceneType(storyboard: Message.self, identifier: "MessagesList") } } // output from the segues template internal enum StoryboardSegue { internal enum Message: String, SegueType { case customBack = "CustomBack" case embed = "Embed" case nonCustom = "NonCustom" case showNavCtrl = "Show-NavCtrl" } } ```

JSON and YAML

json:
  inputs: /path/to/json/dir-or-file
  outputs:
    templateName: runtime-swift5
    output: JSON.swift
yaml:
  inputs: /path/to/yaml/dir-or-file
  outputs:
    templateName: inline-swift5
    output: YAML.swift

This will parse the given file, or when given a directory, recursively search for JSON and YAML files. It will define an enum for each file (and documents in a file where needed), and type-safe constants for the content of the file.

Unlike other parsers, this one is intended to allow you to use more custom inputs (as the formats are quite open to your needs) to generate your code. This means that for these parsers (and the plist one), you’ll probably be more likely to use custom templates to generate code properly adapted/tuned to your inputs, rather than using the bundled templates. To read more about writing your own custom templates, see see the dedicated documentation.

Example of code generated by the bundled template ```swift internal enum JSONFiles { internal enum Info { private static let _document = JSONDocument(path: "info.json") internal static let key1: String = _document["key1"] internal static let key2: String = _document["key2"] internal static let key3: [String: Any] = _document["key3"] } internal enum Sequence { internal static let items: [Int] = objectFromJSON(at: "sequence.json") } } ```

Plists

plist:
  inputs: /path/to/plist/dir-or-file
  outputs:
    templateName: runtime-swift5
    output: Plist.swift

This will parse the given file, or when given a directory, recursively search for Plist files. It will define an enum for each file (and documents in a file where needed), and type-safe constants for the content of the file.

Unlike other parsers, this one is intended to allow you to use more custom inputs (as the format is quite open to your needs) to generate your code. This means that for this parser (and the json and yaml ones), you’ll probably be more likely to use custom templates to generate code properly adapted/tuned to your inputs, rather than using the bundled templates. To read more about writing your own custom templates, see see the dedicated documentation.

Example of code generated by the bundled template ```swift internal enum PlistFiles { internal enum Test { internal static let items: [String] = arrayFromPlist(at: "array.plist") } internal enum Stuff { private static let _document = PlistDocument(path: "dictionary.plist") internal static let key1: Int = _document["key1"] internal static let key2: [String: Any] = _document["key2"] } } ```

Strings

strings:
  inputs: /path/to/language.lproj
  outputs:
    templateName: structured-swift5
    output: Strings.swift

This will generate a Swift enum L10n that will map all your Localizable.strings and Localizable.stringsdict (or other tables) keys to a static let constant. And if it detects placeholders like %@,%d,%f, it will generate a static func with the proper argument types instead, to provide type-safe formatting. By default it will add comments to the generated constants and functions using the comments from the strings file if present, or the default translation of the string.

Note that all dots within the key names are converted to dots in code (by using nested enums). You can provide a different separator than . to split key names into substructures by using a parser option – see the parser documentation.

Example of code generated by the structured bundled template Given the following `Localizable.strings` file: ```swift /* Title for an alert */ "alert_title" = "Title of the alert"; "alert_message" = "Some alert body there"; /* A comment with no space above it */ "bananas.owner" = "Those %d bananas belong to %@."; ``` And the following `Localizable.stringsdict` file: ```xml apples.count NSStringLocalizedFormatKey %#@apples@ apples NSStringFormatSpecTypeKey NSStringPluralRuleType NSStringFormatValueTypeKey d zero You have no apples one You have one apple other You have %d apples. Wow that is a lot! ``` > _Reminder: Don't forget to end each line in your `*.strings` files with a semicolon `;`! Now that in Swift code we don't need semi-colons, it's easy to forget it's still required by the `Localizable.strings` file format πŸ˜‰_ The generated code will contain this: ```swift internal enum L10n { /// Some alert body there internal static let alertMessage = L10n.tr("Localizable", "alert__message", fallback: #"Some alert body there"#) /// Title for an alert internal static let alertTitle = L10n.tr("Localizable", "alert__title", fallback: #"Title of the alert"#) internal enum Apples { /// You have %d apples internal static func count(_ p1: Int) -> String { return L10n.tr("Localizable", "apples.count", p1, fallback: #"You have %d apples"#) } } internal enum Bananas { /// A comment with no space above it internal static func owner(_ p1: Int, _ p2: Any) -> String { return L10n.tr("Localizable", "bananas.owner", p1, String(describing: p2), fallback: #"Those %d bananas belong to %@."#) } } } ``` Note that if the same key is present in both the `.strings` and the `.stringsdict` files, SwiftGen will only consider the one in the `.stringsdict` file, as that's also how Foundation behaves at runtime.

Licence

This code and tool is under the MIT Licence. See the LICENCE file in this repository.

Attributions

This tool is powered by

It is currently mainly maintained by @AliSoftware and @djbe. But I couldn’t thank enough all the other contributors to this tool along the different versions which helped make SwiftGen awesome! πŸŽ‰

If you want to contribute, don’t hesitate to open a Pull Request, or even join the team!

Other Libraries / Tools

If you want to also get rid of String-based APIs not only for your resources, but also for UITableViewCell, UICollectionViewCell and XIB-based views, you should take a look at my Mixin Reusable.

If you want to generate Swift code from your own Swift code (so meta!), like generate Equatable conformance to your types and a lot of other similar things, use Sourcery.

SwiftGen and Sourcery are complementary tools. In fact, Sourcery uses Stencil too, as well as SwiftGen’s StencilSwiftKit so you can use the exact same syntax for your templates for both!

You can also follow me on twitter for news/updates about other projects I am creating, or read my blog.


Articles

  • coming soon...