Skip to content

The Swift code generator for your assets, storyboards, Localizable.strings,… — Get rid of all String-based APIs!

License

Notifications You must be signed in to change notification settings

SwiftGen/SwiftGen

Repository files navigation

SwiftGen

CocoaPods Compatible Platform Swift 4.xSwift 5.x

SwiftGen is a tool to automatically generate Swift code for resources of your projects (like images, localised strings, etc), to make them type-safe to use.

SwiftGen Logo Then generate constants for:

There are multiple benefits in using this:

  • Avoid any risk of typo when using a String
  • Free auto-completion
  • Avoid the risk of using a non-existing asset name
  • All this will be ensured by the compiler and thus avoid the risk of crashing at runtime.

Also, it's fully customizable thanks to Stencil templates, so even if it comes with predefined templates, you can make your own to generate whatever code fits your needs and your guidelines!

Installation

There are multiple possibilities to install SwiftGen on your machine or in your project, depending on your preferences and needs:

Download the ZIPfor the latest release

We recommend that youunarchive the ZIP inside your project directoryandcommit its contentto git. This way,all coworkers will use the same version of SwiftGen for this project.

If you unarchived the ZIP file in a folder e.g. calledswiftgenat the root of your project directory, you can then invoke SwiftGen in your Script Build Phase using:

"${PROJECT_DIR}/swiftgen/bin/swiftgen"

ViaCocoaPods

If you're using CocoaPods, simply addpod 'SwiftGen', '~> 6.0'to yourPodfile.

Then executepod install --repo-update(orpod update SwiftGenif you want to update an existing SwiftGen installation) to download and install theSwiftGenbinaries and dependencies inPods/SwiftGen/bin/swiftgennext to your project.

Given that you can specify an exact version forSwiftGenin yourPodfile,this allows you to ensureall coworkers will use the same version of SwiftGen for this project.

You can then invoke SwiftGen in your Script Build Phase using:

if[[-f"${PODS_ROOT}/SwiftGen/bin/swiftgen"]];then
"${PODS_ROOT}/SwiftGen/bin/swiftgen"else
echo"warning: SwiftGen is not installed. Run 'pod install --repo-update' to install it."
fi

Similarly, be sure to usePods/SwiftGen/bin/swiftgeninstead of justswiftgenwhere we mention commands withswiftgenin the rest of the documentation.

Note: SwiftGen isn't really a pod, as it's not a library your code will depend on at runtime; so the installation via CocoaPods is just a trick that installs the SwiftGen binaries in the Pods/ folder, but you won't see any swift files in the Pods/SwiftGen group in your Xcode's Pods.xcodeproj. That's normal; the SwiftGen binary is still present in that folder in the Finder.


ViaHomebrew(system-wide installation)

To install SwiftGen viaHomebrew,simply use:

$ brew update
$ brew install swiftgen

This will install SwiftGensystem-wide.The same version of SwiftGen will be used for all projects on that machine, and you should make sure all your coworkers have the same version of SwiftGen installed on their machine too.

You can then invokeswiftgendirectly in your Script Build Phase (as it will be in your$PATHalready):

swiftgen…

ViaMint(system-wide installation)

❗️SwiftGen 6.0 or higher only.

To install SwiftGen viaMint,simply use:

$ mint install SwiftGen/SwiftGen

Compile from source(only recommended if you need features from the `stable` branch or want to test a PR)

This solution is when you want to build and install the latest version fromstableand have access to features which might not have been released yet.

  • If you havehomebrewinstalled, you can use the following command to build and install the latest commit:
brew install swiftgen --HEAD
  • Alternatively, you can clone the repository and userake cli:installto build the tool and install it from any branch, which could be useful to test SwiftGen in a fork or a Pull Request branch.

Some Ruby tools are used in the build process, and the system Ruby works well if you are running a recent macOS. However, if you are usingrbenvyou can runrbenv installto make sure you have a matching version of Ruby installed.

Then install the Ruby Gems:

#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:

#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 thebinfolder to your$PATHand invokeswiftgendirectly.


Known Installation Issues On macOS Before 10.14.4

Starting withSwiftGen 6.2.1,if you get an error similar todyld: Symbol not found: _$s11SubSequenceSlTlwhen running SwiftGen, you'll need to install theSwift 5 Runtime Support for Command Line Tools.

Alternatively, you can:

  • Update to macOS 10.14.4 or later
  • Install Xcode 10.2 or later at/Applications/Xcode.app
  • Rebuild SwiftGen from source using Xcode 10.2 or later

Configuration File

❗️ If you're migrating from older SwiftGen versions, don't forget toread the Migration Guide.

SwiftGen is provided as a single command-line tool which uses a configuration file to define the various parsers to run (depending on the type of input files you need to parse) and their parameters.

To create a sample configuration file as a starting point to adapt to your needs, runswiftgen config init.

Each parser described in theconfiguration file(strings,fonts,ib,…) typically corresponds to a type of input resources to parse (strings files, IB files, Font files, JSON files,…), allowing you to generate constants for each types of those input files.

To use SwiftGen, simply create aswiftgen.ymlYAML file (either manually or usingswiftgen config init) then edit it to adapt to your project. The config file should list all the parsers to invoke, and for each parser, the list of inputs/outputs/templates/parameters to use for it.

For example:

strings:
inputs:Resources/Base.lproj
outputs:
-templateName:structured-swift5
output:Generated/Strings.swift
xcassets:
inputs:
-Resources/Images.xcassets
-Resources/MoreImages.xcassets
-Resources/Colors.xcassets
outputs:
-templateName:swift5
output:Generated/Assets.swift

Then you just have to invokeswiftgen config run,or even justswiftgenfor short, and it will execute what's described in the configuration file.

The dedicated documentationexplains the syntax and possibilities in details – like how to pass custom parameters to your templates, useswiftgen config lintto validate your config file, how to use alternate config files, and other tips.

There are also additional subcommands you can invoke from the command line to manage and configure SwiftGen:

  • Theswiftgen configsubcommand to help you with the configuration file, especiallyswiftgen config initto create a starting point for your config andswiftgen config lintto validate that your Config file is valid and has no errors
  • Theswiftgen templatesubcommands to help you print, duplicate, find and manage templates bundled with SwiftGen

Lastly, you can use--helponswiftgenor one of its subcommand to see the detailed usage.

Directly invoking a parser without a config file

While we highly recommend the use a configuration file for performance reasons (especially if you have multiple outputs, but also because it's more flexible), it's also possible to directly invoke the available parsers individually usingswiftgen run:

  • swiftgen run colors [OPTIONS] DIRORFILE1…
  • swiftgen run coredata [OPTIONS] DIRORFILE1…
  • swiftgen run files [OPTIONS] DIRORFILE1…
  • swiftgen run fonts [OPTIONS] DIRORFILE1…
  • swiftgen run ib [OPTIONS] DIRORFILE1…
  • swiftgen run json [OPTIONS] DIRORFILE1…
  • swiftgen run plist [OPTIONS] DIRORFILE1…
  • swiftgen run strings [OPTIONS] DIRORFILE1…
  • swiftgen run xcassets [OPTIONS] DIRORFILE1…
  • swiftgen run yaml [OPTIONS] DIRORFILE1…

One rare cases where this might be useful — as opposed to using a config file — is if you are working on a custom template and want to quickly test the specific parser you're working on at each iteration/version of your custom template, until you're happy with it.

Each parser command generally accepts the same options and syntax, and they mirror the options and parameters from the configuration file:

  • --output FILEor-o FILE:set the file where to write the generated code. If omitted, the generated code will be printed onstdout.
  • --templateName NAMEor-n NAME:define the Stencil template to use (by name, seehere for more info) to generate the output.
  • --templatePath PATHor-p PATH:define the Stencil template to use, using a full path.
  • Note: you should specify one and only one template when invoking SwiftGen. You have to use either-tor-pbut should not use both at the same time (it wouldn't make sense anyway and you'll get an error if you try)
  • --filter REGEXor-f REGEX:the filter to apply to each input path. Filters are applied to actual (relative) paths, not just the filename. Each command has a default filter that you can override with this option.
  • Note: use.+to match multiple characters (at least one), and don't forget to escape the dot (\.) if you want to match a literal dot like for an extension. Add$at the end to ensure the path ends with the extension you want. Regular expressions will be case sensitive by default, and not anchored to the start/end of a path. For example, use.+\.xib$to match files with a.xibextension. Use a tool such asRegExrto ensure you're using a valid regular expression.
  • Each command supports multiple input files (or directories where applicable).
  • You can always use the--helpflag to see what options a command accept, e.g.swiftgen run xcassets --help.

Choosing your template

SwiftGen is based on templates (it usesStencilas its template engine). This means thatyou can choose the template that fits the Swift version you're using— and also the one that best fits your preferences — toadapt 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 thetemplateNameoutput 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 thetemplatePathoutput option instead oftemplateName,to specify their path.

💡 You can use theswiftgen template listcommand to list all the available bundled templates for each parser, and useswiftgen template catto 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 useswiftgen template listto list all templates bundled with SwiftGen. For most SwiftGen parsers, we provide, among others:

  • Aswift4template, compatible with Swift 4
  • Aswift5template, compatible with Swift 5
  • Other variants, likeflat-swift4/5andstructured-swift4/5templates for Strings, etc.

You canfind the documentation for each bundled templatehere in the repo,with documentation organized as one folder per SwiftGen parser, then one MarkDown file per template. You can also useswiftgen template docto 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 theparams: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

TheSwiftGen.playgroundavailable 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 relatedStencilSwiftKitrepository as well.

Be sure tocheck the "Documentation" folderof each repository.

Especially, in addition to the previously mentionedMigration GuideandConfiguration Filedocumentation, theDocumentation/folder in the SwiftGen repository also includes:

Tutorials

You can also find other help & tutorial material on the internet, likethis 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 anenum Assetwith onestatic letper 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
internalenumAsset{
internalenumFiles{
internalstaticletdata=DataAsset(value:"Data")
internalstaticletreadme=DataAsset(value:"README")
}
internalenumFood{
internalenumExotic{
internalstaticletbanana=ImageAsset(value:"Exotic/Banana")
internalstaticletmango=ImageAsset(value:"Exotic/Mango")
}
internalstaticlet`private`=ImageAsset(value:"private")
}
internalenumStyles{
internalenumVengo{
internalstaticletprimary=ColorAsset(value:"Vengo/Primary")
internalstaticlettint=ColorAsset(value:"Vengo/Tint")
}
}
internalenumSymbols{
internalstaticletexclamationMark=SymbolAsset(name:"Exclamation Mark")
internalstaticletplus=SymbolAsset(name:"Plus")
}
internalenumTargets{
internalstaticletbottles=ARResourceGroupAsset(name:"Bottles")
internalstaticletpaintings=ARResourceGroupAsset(name:"Paintings")
}
}

Usage Example

// You can create new images by referring to the enum instance and calling `.image` on it:
letbananaImage=Asset.Exotic.banana.image
letprivateImage=Asset.private.image

// You can create colors by referring to the enum instance and calling `.color` on it:
letprimaryColor=Asset.Styles.Vengo.primary.color
lettintColor=Asset.Styles.Vengo.tint.color

// You can create data items by referring to the enum instance and calling `.data` on it:
letdata=Asset.data.data
letreadme=Asset.readme.data

// You can load an AR resource group's items using:
letbottles=Asset.Targets.bottles.referenceObjects
letpaintings=Asset.Targets.paintings.referenceImages

// You can create new symbol images by referring to the enum instance and calling `.image` on it (with or without configuration)
letplus=Asset.Symbols.plus.image
letstyle=UIImage.SymbolConfiguration(textStyle:.headline)
letstyled=Asset.Symbols.exclamationMark.image(with:style)

Colors

❗️ We recommend to define your colors in your Assets Catalogs and use thexcassetsparser (see above) to generate color constants, instead of using thiscolorsparser described below.
Thecolorsparser below is mainly useful if you support older versions of iOS where colors can't be defined in Asset Catalogs, or if you want to use Android'scolors.xmlfiles as input.

colors:
inputs:/path/to/colors-file.txt
outputs:
templateName:swift5
output:Colors.swift

This will generate aenum ColorNamewith onestatic letper color listed in the text file passed as argument.

The input file is expected to be either:

  • aplain text file,with one line per color to register, each line being composed by the Name to give to the color, followed by ":", followed by the Hex representation of the color (likerrggbborrrggbbaa,optionally prefixed by#or0x) or the name of another color in the file. Whitespaces are ignored.
  • aJSON file,representing a dictionary of names -> values, each value being the hex representation of the color
  • aXML file,expected to be the same format as the Android colors.xml files, containing tags<color name= "AColorName" >AColorHexRepresentation</color>
  • a*.clrfileused by Apple's Color Palettes.

For example you can use this command to generate colors from one of your system color lists:

colors:
inputs:~/Library/Colors/MyColors.clr
outputs:
templateName:swift5
output:Colors.swift

Generated code will look the same as if you'd use a text file.

Example of code generated by the bundled template

Given the followingcolors.txtfile:

Cyan-Color: 0xff66ccff
ArticleTitle: #33fe66
ArticleBody: 339666
ArticleFootnote: ff66ccff
Translucent: ffffffcc

The generated code will look like this:

internalstructColorName{
internalletrgbaValue:UInt32
internalvarcolor:Color{returnColor(named:self)}

/// <span style= "display:block;width:3em;height:2em;border:1px solid black;background:#339666" ></span>
/// Alpha: 100% <br/> (0x339666ff)
internalstaticletarticleBody=ColorName(rgbaValue:0x339666ff)
/// <span style= "display:block;width:3em;height:2em;border:1px solid black;background:#ff66cc" ></span>
/// Alpha: 100% <br/> (0xff66ccff)
internalstaticletarticleFootnote=ColorName(rgbaValue:0xff66ccff)

...
}

Usage Example

// You can create colors with the convenience constructor like this:
lettitle=UIColor(named:.articleBody)// iOS
letfootnote=NSColor(named:.articleFootnote)// macOS

// Or as an alternative, you can refer to enum instance and call.color on it:
letsameTitle=ColorName.articleBody.color
letsameFootnote=ColorName.articleFootnote.color

This way, no need to enter the color red, green, blue, Alpha values each time and create ugly constants in the global namespace for them.

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
internalclassMainEntity:NSManagedObject{
internalclassvarentityName:String{
return"MainEntity"
}

internalclassfuncentity(in managedObjectContext:NSManagedObjectContext)->NSEntityDescription?{
returnNSEntityDescription.entity(forEntityName:entityName,in:managedObjectContext)
}

@nonobjcinternalclassfuncmakeFetchRequest()->NSFetchRequest<MainEntity>{
returnNSFetchRequest<MainEntity>(entityName:entityName)
}

@NSManagedinternalvarattributedString:NSAttributedString?
@NSManagedinternalvarbinaryData:Data?
@NSManagedinternalvarboolean:Bool
@NSManagedinternalvardate:Date?
@NSManagedinternalvarfloat:Float
@NSManagedinternalvarint64:Int64
internalvarintegerEnum:IntegerEnum{
get{
letkey="integerEnum"
willAccessValue(forKey:key)
defer{didAccessValue(forKey:key)}

guardletvalue=primitiveValue(forKey:key)as?IntegerEnum.RawValue,
letresult=IntegerEnum(rawValue:value)else{
fatalError("Could not convert value for key '\(key)' to type 'IntegerEnum'")
}
returnresult
}
set{
letkey="integerEnum"
willChangeValue(forKey:key)
defer{didChangeValue(forKey:key)}

setPrimitiveValue(newValue.rawValue,forKey:key)
}
}
@NSManagedinternalvarmanyToMany:Set<SecondaryEntity>
}

// MARK: Relationship ManyToMany

extensionMainEntity{
@objc(addManyToManyObject:)
@NSManagedpublicfuncaddToManyToMany(_ value:SecondaryEntity)

@objc(removeManyToManyObject:)
@NSManagedpublicfuncremoveFromManyToMany(_ value:SecondaryEntity)

@objc(addManyToMany:)
@NSManagedpublicfuncaddToManyToMany(_ values:Set<SecondaryEntity>)

@objc(removeManyToMany:)
@NSManagedpublicfuncremoveFromManyToMany(_ values:Set<SecondaryEntity>)
}

Usage Example

// Fetch all the instances of MainEntity
letrequest=MainEntity.makeFetchRequest()
letmainItems=trymyContext.execute(request)

// Type-safe relationships: `relatedItem` will be a `SecondaryEntity?` in this case
letrelatedItem=myMainItem.manyToMany.first

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 astruct Filefor each matching file, and an hierarchical enum representing the directory structure of files.

Example of code generated by the bundled template
internalenumFiles{
/// test.txt
internalstaticlettestTxt=File(name:"test",ext:"txt",path:"",mimeType:"text/plain")
/// subdir/
internalenumSubdir{
/// subdir/A Video With Spaces.mp4
internalstaticletaVideoWithSpacesMp4=File(name:"A Video With Spaces",ext:"mp4",path:"subdir",mimeType:"video/mp4")
}
}

Usage Example

// Access files using the `url` or `path` fields
lettxt=Files.testTxt.url
letvideo=Files.Subdir.aVideoWithSpacesMp4.path

// In addition, there are `url(locale:)` and `path(locale:)` to specify a locale
letlocaleTxt=Files.testTxt.url(locale:Locale.current)
letlocaleVideo=Files.Subdir.aVideoWithSpacesMp4.path(locale:Locale.current)

Flat Structure Support

SwiftGen also has a template if you're not interested in keeping the folder structure in the generated code.

Example of code generated by the flat bundled template
internalenumFiles{
/// test.txt
internalstaticlettestTxt=File(name:"test",ext:"txt",path:"",mimeType:"text/plain")
/// subdir/A Video With Spaces.mp4
internalstaticletaVideoWithSpacesMp4=File(name:"A Video With Spaces",ext:"mp4",path:"subdir",mimeType:"video/mp4")
}
}

Given the same file and folder structure as above the usage will now be:

// Access files using the `url` or `path` fields
lettxt=Files.testTxt.url
letvideo=Files.aVideoWithSpacesMp4.path

// In addition, there are `url(locale:)` and `path(locale:)` to specify a locale
letlocaleTxt=Files.testTxt.url(locale:Locale.current)
letlocaleVideo=Files.aVideoWithSpacesMp4.path(locale:Locale.current)

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 astruct FontFamilyfor each family, and an enum nested under that family that will represent the font styles.

Example of code generated by the bundled template
internalenumFontFamily{
internalenumSFNSDisplay:String,FontConvertible{
internalstaticletregular=FontConvertible(name:".SFNSDisplay-Regular",family:".SF NS Display",path:"SFNSDisplay-Regular.otf")
}
internalenumZapfDingbats:String,FontConvertible{
internalstaticletregular=FontConvertible(name:"ZapfDingbatsITC",family:"Zapf Dingbats",path:"ZapfDingbats.ttf")
}
}

Usage Example

// You can create fonts with the convenience constructor like this:
letdisplayRegular=UIFont(font:FontFamily.SFNSDisplay.regular,size:20.0)// iOS
letdingbats=NSFont(font:FontFamily.ZapfDingbats.regular,size:20.0)// macOS

// Or as an alternative, you can refer to enum instance and call.font on it:
letsameDisplayRegular=FontFamily.SFNSDisplay.regular.font(size:20.0)
letsameDingbats=FontFamily.ZapfDingbats.regular.font(size:20.0)

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 anenumfor each of yourNSStoryboard/UIStoryboard,with respectively onestatic letper storyboard scene or segue.

Example of code generated by the bundled template

The generated code will look like this:

// output from the scenes template

internalenumStoryboardScene{
internalenumDependency:StoryboardType{
internalstaticletstoryboardName="Dependency"

internalstaticletdependent=SceneType<UIViewController>(storyboard:Dependency.self,identifier:"Dependent")
}
internalenumMessage:StoryboardType{
internalstaticletstoryboardName="Message"

internalstaticletmessagesList=SceneType<UITableViewController>(storyboard:Message.self,identifier:"MessagesList")
}
}

// output from the segues template

internalenumStoryboardSegue{
internalenumMessage:String,SegueType{
casecustomBack="CustomBack"
caseembed="Embed"
casenonCustom="NonCustom"
caseshowNavCtrl="Show-NavCtrl"
}
}

Usage Example

// You can instantiate scenes using the `instantiate` method:
letvc=StoryboardScene.Dependency.dependent.instantiate()

// You can perform segues using:
vc.perform(segue:StoryboardSegue.Message.embed)

// or match them (in prepareForSegue):
overridefuncprepare(for segue:UIStoryboardSegue,sender:Any?){
switchStoryboardSegue.Message(segue){
case.embed?:
// Prepare for your custom segue transition, passing information to the destination VC
case.customBack?:
// Prepare for your custom segue transition, passing information to the destination VC
default:
// Other segues from other scenes, not handled by this VC
break
}
}

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 anenumfor 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 theplistone), 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, seesee the dedicated documentation.

Example of code generated by the bundled template
internalenumJSONFiles{
internalenumInfo{
privatestaticlet_document=JSONDocument(path:"info.json")
internalstaticletkey1:String=_document["key1"]
internalstaticletkey2:String=_document["key2"]
internalstaticletkey3:[String:Any]=_document["key3"]
}
internalenumSequence{
internalstaticletitems:[Int]=objectFromJSON(at:"sequence.json")
}
}

Usage Example

// This will be a dictionary
letfoo=JSONFiles.Info.key3

// This will be an [Int]
letbar=JSONFiles.Sequence.items

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 anenumfor 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 thejsonandyamlones), 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, seesee the dedicated documentation.

Example of code generated by the bundled template
internalenumPlistFiles{
internalenumTest{
internalstaticletitems:[String]=arrayFromPlist(at:"array.plist")
}
internalenumStuff{
privatestaticlet_document=PlistDocument(path:"dictionary.plist")
internalstaticletkey1:Int=_document["key1"]
internalstaticletkey2:[String:Any]=_document["key2"]
}
}

Usage Example

// This will be an array
letfoo=PlistFiles.Test.items

// This will be an Int
letbar=PlistFiles.Stuff.key1

Strings

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

This will generate a Swiftenum L10nthat will map all yourLocalizable.stringsandLocalizable.stringsdict(or other tables) keys to astatic letconstant. And if it detects placeholders like%@,%d,%f,it will generate astatic funcwith 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 – seethe parser documentation.

Example of code generated by the structured bundled template

Given the followingLocalizable.stringsfile:

/* 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 followingLocalizable.stringsdictfile:

<?xmlversion="1.0"encoding="UTF-8"?>
<plistversion="1.0">
<dict>
<key>apples.count</key>
<dict>
<key>NSStringLocalizedFormatKey</key>
<string>%#@apples@</string>
<key>apples</key>
<dict>
<key>NSStringFormatSpecTypeKey</key>
<string>NSStringPluralRuleType</string>
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>zero</key>
<string>You have no apples</string>
<key>one</key>
<string>You have one apple</string>
<key>other</key>
<string>You have %d apples. Wow that is a lot!</string>
</dict>
</dict>
</dict>
</plist>

Reminder: Don't forget to end each line in your*.stringsfiles with a semicolon;!Now that in Swift code we don't need semi-colons, it's easy to forget it's still required by theLocalizable.stringsfile format 😉

The generated code will contain this:

internalenumL10n{
/// Some alert body there
internalstaticletalertMessage=L10n.tr("Localizable","alert__message",fallback:# "Some alert body there" #)
/// Title for an alert
internalstaticletalertTitle=L10n.tr("Localizable","alert__title",fallback:# "Title of the alert" #)
internalenumApples{
/// You have %d apples
internalstaticfunccount(_ p1:Int)->String{
returnL10n.tr("Localizable","apples.count",p1,fallback:# "You have %d apples" #)
}
}
internalenumBananas{
/// A comment with no space above it
internalstaticfuncowner(_ p1:Int,_ p2:Any)->String{
returnL10n.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.stringsand the.stringsdictfiles, SwiftGen will only consider the one in the.stringsdictfile, as that's also how Foundation behaves at runtime.

Usage Example

Once the code has been generated by the script, you can use it this way in your Swift code:

// Simple strings
letmessage=L10n.alertMessage
lettitle=L10n.alertTitle

// with parameters, note that each argument needs to be of the correct type
letapples=L10n.Apples.count(3)
letbananas=L10n.Bananas.owner(5,"Olivier")

Flat Strings Support

SwiftGen also has a template to support flat strings files (i.e. without splitting the keys in substructures using "dot syntax" ). The advantage is that your keys won't be mangled in any way; the disadvantage is that auto-completion won't be as nice.

Example of code generated by the flat bundled template
internalenumL10n{
/// Some alert body there
internalstaticletalertMessage=L10n.tr("Localizable","alert__message",fallback:# "Some alert body there" #)
/// Title for an alert
internalstaticletalertTitle=L10n.tr("Localizable","alert__title",fallback:# "Title of the alert" #)
/// You have %d apples
internalstaticfuncapplesCount(_ p1:Int)->String{
returnL10n.tr("Localizable","apples.count",p1,fallback:# "You have %d apples" #)
}
/// A comment with no space above it
internalstaticfuncbananasOwner(_ p1:Int,_ p2:Any)->String{
returnL10n.tr("Localizable","bananas.owner",p1,String(describing:p2),fallback:# "Those %d bananas belong to %@." #)
}
}

Given the sameLocalizable.stringsandLocalizable.stringsdictas above the usage will now be:

// Simple strings
letmessage=L10n.alertMessage
lettitle=L10n.alertTitle

// with parameters, note that each argument needs to be of the correct type
letapples=L10n.applesCount(3)
letbananas=L10n.bananasOwner(5,"Olivier")

Licence

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

Attributions

This tool is powered by

It is currently mainly maintained by@AliSoftwareand@djbe.But I couldn't thank enough all the othercontributorsto 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 forUITableViewCell,UICollectionViewCelland XIB-based views, you should take a look at my MixinReusable.

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

SwiftGen and Sourcery are complementary tools. In fact, Sourcery usesStenciltoo, as well as SwiftGen'sStencilSwiftKitso you can use the exact same syntax for your templates for both!

You can alsofollow me on twitterfor news/updates about other projects I am creating, orread my blog.