SwiftGen 是一个工具,用于自动为项目资源(如图像、本地化字符串等)生成 Swift 代码,使其在使用时具有类型安全性。
<table border="0"><tr> <td> <img alt="SwiftGen 标志" src="https://yellow-cdn.veclightyear.com/2b54e442/6e61729a-436a-4a38-87c3-5ee473a6b5cd.png" /> </td><td> <ul> <li><a href="#installation">安装</a> <li><a href="#configuration-file">配置文件</a> <li><a href="#choosing-your-template">选择模板</a> <li><a href="#additional-documentation">其他文档</a> </ul> 然后为以下内容生成常量: <ul> <li><a href="#asset-catalog">资产目录</a> <li><a href="#colors">颜色</a> <li><a href="#core-data">Core Data</a> <li><a href="#files">文件</a> <li><a href="#fonts">字体</a> <li><a href="#interface-builder">Interface Builder 文件</a> <li><a href="#json-and-yaml">JSON 和 YAML 文件</a> <li><a href="#plists">Plist</a> <li><a href="#strings">可本地化字符串</a> </ul> </td> </tr></table> <span style="float:none" />使用这个工具有多个好处:
此外,由于使用 Stencil 模板,它是完全可定制的,因此即使它附带预定义的模板,你也可以创建自己的模板来生成符合你的需求和指南的任何代码!
根据你的偏好和需求,有多种方式可以在你的机器或项目中安装 SwiftGen:
<details> <summary><strong>下载 ZIP</strong> 获取最新版本</summary>swiftgen-x.y.z.zip
文件我们建议你将 ZIP 解压到项目目录中并将其内容提交到 git。这样,所有同事都将为这个项目使用相同版本的 SwiftGen。
如果你将 ZIP 文件解压到项目目录根目录下的一个文件夹中(例如名为 swiftgen
),那么你可以在脚本构建阶段使用以下方式调用 SwiftGen:
"${PROJECT_DIR}/swiftgen/bin/swiftgen" …
如果你使用 CocoaPods,只需在 Podfile
中添加 pod 'SwiftGen', '~> 6.0'
。
然后执行 pod install --repo-update
(或 pod update SwiftGen
如果你想更新现有的 SwiftGen 安装),以下载并安装 SwiftGen
二进制文件和依赖项到项目旁边的 Pods/SwiftGen/bin/swiftgen
中。
由于你可以在 Podfile
中为 SwiftGen
指定确切的版本,这允许你确保所有同事都将为这个项目使用相同版本的 SwiftGen。
然后你可以在脚本构建阶段使用以下方式调用 SwiftGen:
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
类似地,在文档的其余部分中提到使用
swiftgen
的命令时,请确保使用Pods/SwiftGen/bin/swiftgen
而不是仅仅swiftgen
。
注意: SwiftGen 并不真正是一个 pod,因为它不是你的代码在运行时依赖的库;所以通过 CocoaPods 安装只是一个技巧,它将 SwiftGen 二进制文件安装在 Pods/ 文件夹中,但你不会在 Xcode 的 Pods.xcodeproj 的 Pods/SwiftGen 组中看到任何 swift 文件。这是正常的;SwiftGen 二进制文件仍然存在于 Finder 中的该文件夹中。
要通过 Homebrew 安装 SwiftGen,只需使用:
$ brew update $ brew install swiftgen
这将系统范围安装 SwiftGen。同一版本的 SwiftGen 将用于该机器上的所有项目,你应该确保你的所有同事在他们的机器上也安装了相同版本的 SwiftGen。
然后你可以直接在脚本构建阶段调用 swiftgen
(因为它已经在你的 $PATH
中):
swiftgen …
❗️仅适用于 SwiftGen 6.0 或更高版本。
要通过 Mint 安装 SwiftGen,只需使用:
$ mint install SwiftGen/SwiftGen
这个解决方案适用于当你想构建和安装 stable
的最新版本,并访问可能尚未发布的功能。
homebrew
,可以使用以下命令构建和安装最新提交:brew install swiftgen --HEAD
rake cli:install
从任何分支构建工具并安装它,这对于在 fork 或 Pull Request 分支中测试 SwiftGen 可能很有用。构建过程中使用了一些 Ruby 工具,如果你运行的是最新的 macOS,系统 Ruby 可以很好地工作。但是,如果你使用 rbenv
,可以运行 rbenv install
以确保安装了匹配版本的 Ruby。
然后安装 Ruby Gems:
# 如果未安装 bundle,则安装它 gem install bundle # 从 Gemfile 安装 Ruby gems bundle install
现在你可以安装到默认位置(无参数)或自定义位置:
# 二进制文件安装在 `./.build/swiftgen/bin` $ rake cli:install # - 或 - # 二进制文件将安装在 `~/swiftgen/bin` $ rake cli:install[~/swiftgen/bin]
然后你可以使用安装它的二进制文件的路径调用 SwiftGen:
~/swiftgen/bin/swiftgen …
或将 bin
文件夹的路径添加到你的 $PATH
中,并直接调用 swiftgen
。
从 SwiftGen 6.2.1 开始,如果在运行 SwiftGen 时出现类似 dyld: Symbol not found: _$s11SubSequenceSlTl
的错误,你需要安装 Swift 5 Runtime Support for Command Line Tools。
或者,你可以:
/Applications/Xcode.app
安装 Xcode 10.2 或更高版本❗️ 如果你从旧版本的 SwiftGen 迁移,不要忘记阅读迁移指南。
SwiftGen 作为一个单一的命令行工具提供,它使用配置文件来定义要运行的各种解析器(取决于你需要解析的输入文件类型)及其参数。
要创建一个样例配置文件作为适应你需求的起点,运行 swiftgen config init
。
配置文件中描述的每个解析器(strings
, fonts
, ib
, …)通常对应于要解析的输入资源类型(字符串文件、IB 文件、字体文件、JSON 文件…),允许你为这些输入文件的每种类型生成常量。
要使用 SwiftGen,只需创建一个 swiftgen.yml
YAML 文件(手动或使用 swiftgen config init
),然后编辑它以适应你的项目。配置文件应列出要调用的所有解析器,以及每个解析器要使用的输入/输 出/模板/参数列表。
例如:
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
然后你只需调用 swiftgen config run
,或者简单地 swiftgen
,它就会执行配置文件中描述的内容。
专门的文档详细解释了语法和可能性 - 比如如何向模板传递自定义参数,使用 swiftgen config lint
验证你的配置文件,如何使用备用配置文件,以及其他提示。
还有一些额外的子命令,你可以从命令行调用来管理和配置 SwiftGen:
swiftgen config
子命令帮助你处理配置文件,特别是 swiftgen config init
为你的配置创建一个起点,swiftgen config lint
验证你的配置文件是否有效且没有错误swiftgen template
子命令帮助你打 印、复制、查找和管理 SwiftGen 捆绑的模板最后,你可以在 swiftgen
或其子命令上使用 --help
查看详细用法。
虽然我们强烈建议使用配置文件以提高性能(尤其是如果你有多个输出,但也因为它更灵活),也可以使用 swiftgen run
直接单独调用可用的解析器:
swiftgen run colors [选项] 目录或文件1 …
swiftgen run coredata [选项] 目录或文件1 …
swiftgen run files [选项] 目录或文件1 …
swiftgen run fonts [选项] 目录或文件1 …
swiftgen run ib [选项] 目录或文件1 …
swiftgen run json [选项] 目录或文件1 …
swiftgen run plist [选项] 目录或文件1 …
swiftgen run strings [选项] 目录或文件1 …
swiftgen run xcassets [选项] 目录或文件1 …
swiftgen run yaml [选项] 目录或文件1 …
在少数情况下使用这种方式可能会有用 - 相对于使用配置文件而言 - 比如当你正在开发自定义模板时,想要在每次迭代/版本的自定义模板中快速测试特定的解析器,直到你对它满意为止。
每个解析器命令通常接受相同的选项和语法,它们反映了配置文件中的选项和参数:
--output 文件
或 -o 文件
: 设置写入生成代码的文件。如果省略,生成的代码将打印到 stdout
。--templateName 名称
或 -n 名称
: 定义要使用的Stencil模板(按名称,更多信息请参见此处)以生成输出。--templatePath 路径
或 -p 路径
: 使用完整路径定义要使用的Stencil模板。-t
或 -p
之一,但不应同时使用两者(这样做没有意义,如果你尝试这样做会收到错误)--filter 正则表达式
或 -f 正则表达式
: 应用于每个输入路径的过滤器。过滤器应用于实际(相对)路径,而不仅仅是文件名。每个命令都有一个默认过滤器,你可以用这个选项覆盖它。.+
匹配多个字符(至少一个),如果要匹配文字点(如扩展名),别忘了转义点(\.
)。在末尾添加 $
以确保路径以你想要的扩展名结尾。正则表达式默认区分大小写,并且不会锚定到路径的开头/结尾。例如,使用 .+\.xib$
匹配扩展名为 .xib
的文件。使用像 RegExr 这样的工具来确保你使用的是有效的正则表达式。--help
标志查看命令接受的选项,例如 swiftgen run xcassets --help
。SwiftGen 基于模板(它使用 Stencil 作为模板引擎)。这意味着你可以选择适合你正在使用的 Swift 版本的模板 - 也是最适合你偏好的模板 - 以使生成的代码适应你自己的约定和 Swift 版本。
SwiftGen 随附了一些适用于每个解析器的模板(colors
, coredata
, files
, fonts
, ib
, json
, plist
, strings
, xcassets
, yaml
),这些模板可以满足大多数需求;只需使用 templateName
输出选项指定要使用的模板名称即可。但如果内置模板不适合你的编码约定或需求,你也可以创建自己的模板:只需将它们存储在任何地方(比如你的项目仓库中),并使用 templatePath
输出选项而不是 templateName
来指定它们的路径。
💡 你可以使用 swiftgen template list
命令列出每个解析器可用的所有内置模板,并使用 swiftgen template cat
显示模板内容并复制它以创建自己的变体。
有关如何创建自己的模板的更多信息,请参阅专门的文档。
如上所述,你可以使用 swiftgen template list
列出 SwiftGen 随附的所有模板。对于大多数 SwiftGen 解析器,我们提供:
swift4
模板,兼容 Swift 4swift5
模板,兼容 Swift 5flat-swift4/5
和 structured-swift4/5
模板等。你可以在这里的仓库中找到每个内置模板的文档这里,文档按每个 SwiftGen 解析器组织为一个文件夹,然后每个模板一个 MarkDown 文件。你还可以使用 swiftgen template doc
直接从终端在浏览器中打开该文档页面。
每个 MarkDown 文件记录了它针对的 Swift 版本,该模板的用例(在哪些情况下你可能会选择该模板而不是其他模板),调用时可用的自定义参数(使用配置文件中的 params:
键),以及一些代码示例。
不要犹豫,提出 PR 分享你对内置模板的改进建议 😉
本仓库中提供的 SwiftGen.playground
允许你使用该工具通常生成的代码进行试验,并查看一些如何利用它的示例。
这允许你快速了解 SwiftGen 生成的典型代码的样子,以及如何在代码中使用生成的常量。
本仓库以及相关的 StencilSwiftKit 仓库中有大量以 Markdown 格式提供的文档。
请务必查看每个仓库的 "Documentation" 文件夹。
特别是,除了前面提到的迁移指南和配置文件文档外,SwiftGen 仓库的 Documentation/
文件夹还包括:
templates
子目录,详细说明了 SwiftGen 随附的每个模板的文档(何时使用每个模板,输出会是什么样子,以及自定义参数以调整它们,...)SwiftGenKit Contexts
子目录,详细说明了"Stencil 上下文"的结构,即解析输入文件后得到的字典/YAML 表示。这份文档对想要编写自己的模板的人很有用,这样他们在编写模板时就知道可用的结构和各种键,以相应地构造所需的生成输出。你还可以在互联网上找到其他帮助和教程材料,比如我在 2017 年 9 月 FrenchKit 上给出的这个关于代码生成的课堂 — 以及其 wiki 详细介绍了关于安装和使用 SwiftGen(以及 Sourcery)的分步教程
xcassets: inputs: /dir/to/search/for/imageset/assets outputs: templateName: swift5 output: Assets.swift
这将生成一个 enum Asset
,每个资产(图像集、颜色集、数据集等)都有一个 static let
,这样你就可以将它们用作常量。
// 你可以通过引用枚举实例并在其上调用 `.image` 来创建新图像: let bananaImage = Asset.Exotic.banana.image let privateImage = Asset.private.image // 你可以通过引用枚举实例并在其上调用 `.color` 来创建颜色: let primaryColor = Asset.Styles.Vengo.primary.color let tintColor = Asset.Styles.Vengo.tint.color // 你可以通过引用枚举实例并在其上调用 `.data` 来创建数据项: let data = Asset.data.data let readme = Asset.readme.data // 你可以使用以下方式加载 AR 资源组的项目: let bottles = Asset.Targets.bottles.referenceObjects let paintings = Asset.Targets.paintings.referenceImages // 你可以通过引用枚举实例并在其上调用 `.image` 来创建新的符号图像(带配置或不带配置) let plus = Asset.Symbols.plus.image let style = UIImage.SymbolConfiguration(textStyle: .headline) let styled = Asset.Symbols.exclamationMark.image(with: style)
❗️ 我们建议在 Assets Catalogs 中定义颜色,并使用
xcassets
解析器(见上文)生成颜色常量,而不是使用下面描述的colors
解析器。
如果你支持无法在 Asset Catalogs 中定义颜色的较旧版本的 iOS,或者想要使用 Android 的colors.xml
文件作为输入,那么下面的colors
解析器主要有用。
colors: inputs: /path/to/colors-file.txt outputs: templateName: swift5 output: Colors.swift
这将生成一个 enum ColorName
,文本文件中列出的每种颜色都有一个 static let
。
输入文件预期是:
rrggbb
或 rrggbbaa
,可选前缀为 #
或 0x
)或文件中另一种颜色的名称。空白将被忽略。<color name="AColorName">AColorHexRepresentation</color>
*.clr
文件,由 Apple 的 Color Palettes 使用。例如,你可以使用以下命令从系统色彩列表之一生成颜色:
colors: inputs: ~/Library/Colors/MyColors.clr outputs: templateName: swift5 output: Colors.swift
生成的代码将与使用文本文件相同。
考虑到以下 colors.txt
文件:
Cyan-Color : 0xff66ccff
ArticleTitle : #33fe66
ArticleBody : 339666
ArticleFootnote : ff66ccff
Translucent : ffffffcc
生成的代码将如下所示:
internal struct ColorName { internal let rgbaValue: UInt32 internal var color: Color { return Color(named: self) } /// <span style="display:block;width:3em;height:2em;border:1px solid black;background:#339666"></span> /// Alpha: 100% <br/> (0x339666ff) internal static let articleBody = ColorName(rgbaValue: 0x339666ff) /// <span style="display:block;width:3em;height:2em;border:1px solid black;background:#ff66cc"></span> /// Alpha: 100% <br/> (0xff66ccff) internal static let articleFootnote = ColorName(rgbaValue: 0xff66ccff) ... }
// 您可以使用便利构造函数这样创建颜色: let title = UIColor(named: .articleBody) // iOS let footnote = NSColor(named: .articleFootnote) // macOS // 或者作为替代方案,您可以引用枚举实例并在其上调用 .color: let sameTitle = ColorName.articleBody.color let sameFootnote = ColorName.articleFootnote.color
这样,就不需要每次都输入颜色 的红、绿、蓝、alpha值,也不需要为它们创建全局命名空间中的丑陋常量。
coredata: inputs: /path/to/model.xcdatamodeld outputs: templateName: swift5 output: CoreData.swift
这将解析指定的核心数据模型,为模型中的每个实体生成一个类,其中包含所有属性,并在需要时为关系和预定义的获取请求生成一些扩展。
<details> <summary>内置模板生成的代码示例</summary></details>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<MainEntity> { return NSFetchRequest<MainEntity>(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<SecondaryEntity> } // 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<SecondaryEntity>) @objc(removeManyToMany:) @NSManaged public func removeFromManyToMany(_ values: Set<SecondaryEntity>) }
// 获取 MainEntity 的所有实例 let request = MainEntity.makeFetchRequest() let mainItems = try myContext.execute(request) // 类型安全的关系: 在这种情况下 `relatedItem` 将是 `SecondaryEntity?` 类型 let relatedItem = myMainItem.manyToMany.first
files: inputs: path/to/search filter: .+\.mp4$ outputs: templateName: structured-swift5 output: Files.swift
文件解析器旨在列出给定目录中文件和子目录的名称和MIME类型。这将使用给定的过滤器(默认 .*
)递归搜索指定的目录,为每个匹配的文件定义一个 struct File
,并定义一个表示文件目录结构的分层枚举。
</details>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") } }
// 使用 `url` 或 `path` 字段访问文件 let txt = Files.testTxt.url let video = Files.Subdir.aVideoWithSpacesMp4.path // 此外,还有 `url(locale:)` 和 `path(locale:)` 来指定区域设置 let localeTxt = Files.testTxt.url(locale: Locale.current) let localeVideo = Files.Subdir.aVideoWithSpacesMp4.path(locale: Locale.current)
SwiftGen 还有一个模板,如果您对在生成的代码中保持文件夹结构不感兴趣。
<details> <summary>扁平内置模板生成的代码示例</summary></details>internal enum Files { /// test.txt internal static let testTxt = File(name: "test", ext: "txt", path: "", mimeType: "text/plain") /// subdir/A Video With Spaces.mp4 internal static let aVideoWithSpacesMp4 = File(name: "A Video With Spaces", ext: "mp4", path: "subdir", mimeType: "video/mp4") } }
给定与上面相同的文件和文件夹结构,使用方式现在将是:
// 使用 `url` 或 `path` 字段访问文件 let txt = Files.testTxt.url let video = Files.aVideoWithSpacesMp4.path // 此外,还有 `url(locale:)` 和 `path(locale:)` 来指定区域设置 let localeTxt = Files.testTxt.url(locale: Locale.current) let localeVideo = Files.aVideoWithSpacesMp4.path(locale: Locale.current)
fonts: inputs: /path/to/font/dir outputs: templateName: swift5 output: Fonts.swift
这将递归遍历指定的目录,查找任何字体文件(TTF、OTF等),为每个字体系列定义一个 struct FontFamily
,并在该系列下嵌套一个枚举,表示字体样式。
</details>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") } }
// 您可以使用便利构造函数这样创建字体: let displayRegular = UIFont(font: FontFamily.SFNSDisplay.regular, size: 20.0) // iOS let dingbats = NSFont(font: FontFamily.ZapfDingbats.regular, size: 20.0) // macOS // 或者作为替代方案,您可以引用枚举实例并在其上调用 .font: let sameDisplayRegular = FontFamily.SFNSDisplay.regular.font(size: 20.0) let sameDingbats = FontFamily.ZapfDingbats.regular.font(size: 20.0)
ib: inputs: /dir/to/search/for/storyboards outputs: - templateName: scenes-swift5 output: Storyboard Scenes.swift - templateName: segues-swift5 output: Storyboard Segues.swift
这将为您的每个 NSStoryboard
/UIStoryboard
生成一个 enum
,分别为每个故事板场景或转场生成一个 static let
。
生成的代码将如下所示:
</details>// 场景模板的输出 internal enum StoryboardScene { internal enum Dependency: StoryboardType { internal static let storyboardName = "Dependency" internal static let dependent = SceneType<UIViewController>(storyboard: Dependency.self, identifier: "Dependent") } internal enum Message: StoryboardType { internal static let storyboardName = "Message" internal static let messagesList = SceneType<UITableViewController>(storyboard: Message.self, identifier: "MessagesList") } } // 转场模板的输出 internal enum StoryboardSegue { internal enum Message: String, SegueType { case customBack = "CustomBack" case embed = "Embed" case nonCustom = "NonCustom" case showNavCtrl = "Show-NavCtrl" } }
// 您可以使用 `instantiate` 方法实例化场景: let vc = StoryboardScene.Dependency.dependent.instantiate() // 您可以执行转场: vc.perform(segue: StoryboardSegue.Message.embed) // 或匹配它们(在 prepareForSegue 中): override func prepare(for segue: UIStoryboardSegue, sender: Any?) { switch StoryboardSegue.Message(segue) { case .embed?: // 为您的自定义转场准备,将信息传递给目标 VC case .customBack?: // 为您的自定义转场准备,将信息传递给目标 VC default: // 来自其他场景的其他转场,不由此 VC 处理 break } }
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
这将解析给定的文件,或者在给定目录时,递归搜索 JSON 和 YAML 文件。它将为每个文件(以及需要时文件中的文档)定义一个 enum
,并为文件内容定义类型安全的常量。
与其他解析器不同,这个解析器旨在允许您使用更多自定义输入(因为这些格式相当开放,可以满足您的需求)来生成代码。这意味着对于这些解析器(以及 plist
解析器),您可能更有可能使用自定义模板来生成适当适应/调整到您的输入的代码,而不是使用内置模板。要了解更多关于编写自己的自定义模板的信息,请参阅专门的文档。
// 这将是一个字典 let foo = JSONFiles.Info.key3 // 这将是一个 [Int] 数组 let bar = JSONFiles.Sequence.items
plist: inputs: /path/to/plist/dir-or-file outputs: templateName: runtime-swift5 output: Plist.swift
这将解析给定的文件,或者当给定一个目录时,递归搜索 Plist 文件。它将为每个文件(以及文件中需要的文档)定义一个 enum
,并为文件内容定义类型安全的常量。
与其他解析器不同,这个解析器旨在允许您使用更多自定义输入(因为格式相当开放以满足您的需求)来生成代码。这意味着对于这个解析器(以及 json
和 yaml
解析器),您可能更倾向于使用自定义模板来生成适当调整/适应您的输入的代码,而不是使用捆绑的模板。要了解更多关于编写自定义模板的信息,请参阅专门的文档。
</details>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"] } }
// 这将是一个数组 let foo = PlistFiles.Test.items // 这将是一个 Int let bar = PlistFiles.Stuff.key1
strings: inputs: /path/to/language.lproj outputs: templateName: structured-swift5 output: Strings.swift
这将生成一个 Swift enum L10n
,它将所有 Localizable.strings
和 Localizable.stringsdict
(或其他表)的键映射到 static let
常量。如果检测到像 %@
、%d
、%f
这样的占位符,它将生成一个具有适当参数类型的 static func
,以提供类型安全的格式化。默认情况下,它将使用字符串文件中的注释(如果存在)或字符串的默认翻译,为生成的常量和函数添加注释。
<details> <summary>结构化捆绑模板生成的代码示例</summary>请注意,键名中的所有点都会转换为代码中的点(通过使用嵌套枚举)。您可以使用解析器选项提供一个不同于
.
的分隔符来将键名拆分为子结构 - 参见解析器文档。
给定以下 Localizable.strings
文件:
/* 警告标题 */ "alert_title" = "警告标题"; "alert_message" = "一些警告内容"; /* 上面没有空格的注释 */ "bananas.owner" = "这 %d 根香蕉属于 %@。";
以及以下 Localizable.stringsdict
文件:
<?xml version="1.0" encoding="UTF-8"?> <plist version="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>你没有苹果</string> <key>one</key> <string>你有一个苹果</string> <key>other</key> <string>你有 %d 个苹果。哇,真多!</string> </dict> </dict> </dict> </plist>
提醒:不要忘记在
*.strings
文件中的每一行末尾都加上分号;
!现在在 Swift 代码中我们不需要分号,很容易忘记Localizable.strings
文件格式仍然需要它 😉
生成的代码将包含以下内容:
internal enum L10n { /// 一些警告内容 internal static let alertMessage = L10n.tr("Localizable", "alert__message", fallback: #"一些警告内容"#) /// 警告标题 internal static let alertTitle = L10n.tr("Localizable", "alert__title", fallback: #"警告标题"#) internal enum Apples { /// 你有 %d 个苹果 internal static func count(_ p1: Int) -> String { return L10n.tr("Localizable", "apples.count", p1, fallback: #"你有 %d 个苹果"#) } } internal enum Bananas { /// 上面没有空格的注释 internal static func owner(_ p1: Int, _ p2: Any) -> String { return L10n.tr("Localizable", "bananas.owner", p1, String(describing: p2), fallback: #"这 %d 根香蕉属于 %@。"#) } } }
请注意,如果相同的键同时出现在 .strings
和 .stringsdict
文件中,SwiftGen 将只考虑 .stringsdict
文件中的内容,因为这也是 Foundation 在运行时的行为。
一旦脚本生成了代码,您可以在 Swift 代码中这样使用它:
// 简单字符串 let message = L10n.alertMessage let title = L10n.alertTitle // 带参数,注意每个参数都需要是正确的类型 let apples = L10n.Apples.count(3) let bananas = L10n.Bananas.owner(5, "Olivier")
SwiftGen 还有一个模板来支持平面字符串文件(即不使用"点语法"将键拆分为子结构)。优点是您的键不会以任何方式被修改;缺点是自动完成不会那么好。
<details> <summary>平面捆绑模板生成的代码示例</summary></details>internal enum L10n { /// 一些警告内容 internal static let alertMessage = L10n.tr("Localizable", "alert__message", fallback: #"一些警告内容"#) /// 警告标题 internal static let alertTitle = L10n.tr("Localizable", "alert__title", fallback: #"警告标题"#) /// 你有 %d 个苹果 internal static func applesCount(_ p1: Int) -> String { return L10n.tr("Localizable", "apples.count", p1, fallback: #"你有 %d 个苹果"#) } /// 上面没有空格的注释 internal static func bananasOwner(_ p1: Int, _ p2: Any) -> String { return L10n.tr("Localizable", "bananas.owner", p1, String(describing: p2), fallback: #"这 %d 根香蕉属于 %@。"#) } }
给定与上面相同的 Localizable.strings
和 Localizable.stringsdict
,使用方式现在将是:
// 简单字符串 let message = L10n.alertMessage let title = L10n.alertTitle // 带参数,注意每个参数都需要是正确的类型 let apples = L10n.applesCount(3) let bananas = L10n.bananasOwner(5, "Olivier")
此代码和工具采用 MIT 许可证。请参阅此存储库中的 LICENCE
文件。
此工具由以下技术支持
目前主要由 @AliSoftware 和 @djbe 维护。但我无法充分感谢所有其他贡献者,他们在不同版本中帮助使 SwiftGen 变得出色! 🎉
如果您想贡献,不要犹豫,打开一个 Pull Request,甚至加入团队!
如果您想摆脱不仅是资源的基于字符串的 API,还包括 UITableViewCell
、UICollectionViewCell
和基于 XIB 的视图,您应该看看我的 Mixin Reusable。
如果您想从自己的 Swift 代码生成 Swift 代码(太元了!),比如为您的类型生成 Equatable
一致性和许多其他类似的事情,请使用 Sourcery。
SwiftGen 和 Sourcery 是互补工具。事实上,Sourcery 也使用 Stencil
,以及 SwiftGen 的 StencilSwiftKit
,所以您可以为两者使用完全相同的模板语法!
您也可以在 Twitter 上关注我,了解我正在创建的其他项目的新闻/更新,或者阅读我的博客。
深度推理能力全新升级,全面对标OpenAI o1
科大讯飞的星火大模型,支持语言理解、知识问答和文本创作等多功能,适用于多种文件和业务场景,提升办公和日常生活的效率。讯飞星火是一个提供丰富智能服务的平台,涵盖科技资讯、图像创作、写作辅助、编程解答、科研文献解读等功能,能为不同需求的用户提供便捷高效的帮助,助力用户轻松获取信息、解决问题,满足多样化使用场景。
一种基于大语言模型的高效单流解耦语音令牌文本到语音合成模型
Spark-TTS 是一个基于 PyTorch 的开源文本到语音合成项目,由多个知名机构联合参与。该项目提供了高效的 LLM(大语言模型)驱动的语音合成方案,支持语音克隆和语音创建功能,可通过命令行界面(CLI)和 Web UI 两种方式使用。用户可以根据需求调整语音的性别、音高、速度等参数,生成高质量的语音。该项目适用于多种场景,如有声读物制作、智能语音助手开发等。
字节跳动发布的AI编程神器IDE
Trae是一种自适应的集成开发环境(IDE),通过自动化和多元协作改变开发流程。利用Trae,团队能够更快速、精确地编写和部署代码,从而提高编程效率和项目交付速度。Trae具备上下文感知和代码自动完成功能,是提升开发效率的理想工具。
AI助力,做PPT更简单!
咔片是一款轻量化在线演示设计工具,借助 AI 技术,实现从内容生成到智能设计的一站式 PPT 制作服务。支持多种文档格式导入生成 PPT,提供海量模板、智能美化、素材替换等功能,适用于销售、教师、学生等各类人群,能高效制作出高品质 PPT,满足不同场景演示需求。
选题、配图、成文,一站式创作,让内容运营更高效
讯飞绘文,一个AI集成平台,支持写作、选题、配图、排版和发布。高效生成适用于各类媒体的定制内容,加速品牌传播,提升内容营销效果。
专业的AI公文写作平台,公文写作神器
AI 材料星,专业的 AI 公文写作辅助平台,为体制内工作人员提供高效的公文写作解决方案。拥有海量公文文库、9 大核心 AI 功能,支持 30 + 文稿类型生成,助力快速完成领导讲话、工作总结、述职报告等材料,提升办公效率,是体制打工人的得力写作神器。
OpenAI Agents SDK,助力开发者便捷使用 OpenAI 相关功能。
openai-agents-python 是 OpenAI 推出的一款强大 Python SDK,它为开发者提供了与 OpenAI 模型交互的高效工具,支持工具调用、结果处理、追踪等功能,涵盖多种应用场景,如研究助手、财务研究等,能显著提升开发效率,让开发者更轻松地利用 OpenAI 的技术优势。
高分辨率纹理 3D 资产生成
Hunyuan3D-2 是腾讯开发的用于 3D 资产生成的强大工具,支持从文本描述、单张图片或多视角图片生成 3D 模型,具备快速形状生成能力,可生成带纹理的高质量 3D 模型,适用于多个领域,为 3D 创作提供了高效解决方案。
一个具备存储、管理和客户端操作等多种功能的分布式文件系统相关项目。
3FS 是一个功能强大的分布式文件系统项目,涵盖了存储引擎、元数据管理、客户端工具等多个模块。它支持多种文件操作,如创建文件和目录、设置布局等,同时具备高效的事件循环、节点选择和协程池管理等特性。适用于需要大规模数据存储和管理的场景,能够提高系统的性能和可靠性,是分布式存储领域的优质解决方案。
用于可扩展和多功能 3D 生成的结构化 3D 潜在表示
TRELLIS 是一个专注于 3D 生成的项目,它利用结构化 3D 潜在表示技术,实现了可扩展且多功能的 3D 生成。项目提供了多种 3D 生成的方法和工具,包括文本到 3D、图像到 3D 等,并且支持多种输出格式,如 3D 高斯、辐射场和网格等。通过 TRELLIS,用户可以根据文本描述或图像输入快速生成高质量的 3D 资产,适用于游戏开发、动画制作、虚拟现实等多个领域。
最新AI工具、AI资讯
独家AI资源、AI项目落地
微信扫一扫关注公众号