SwiftGen

SwiftGen

自动生成类型安全Swift资源代码的工具

SwiftGen是一个Swift代码生成工具,可为项目资源文件自动创建类型安全的代码。它支持图片、本地化字符串等多种资源类型,并通过Stencil模板实现自定义。SwiftGen帮助开发者避免拼写错误,提供自动补全,在编译时捕获资源名称问题,从而提升代码质量和开发效率。该工具可根据不同项目需求进行灵活配置。

SwiftGen代码生成Swift资源管理自动化Github开源项目

SwiftGen

CocoaPods 兼容 平台 Swift 4.x Swift 5.x

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>

我们建议你将 ZIP 解压到项目目录中将其内容提交到 git。这样,所有同事都将为这个项目使用相同版本的 SwiftGen

如果你将 ZIP 文件解压到项目目录根目录下的一个文件夹中(例如名为 swiftgen),那么你可以在脚本构建阶段使用以下方式调用 SwiftGen:

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

</details> <details> <summary>通过 <strong>CocoaPods</strong></summary>

如果你使用 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 中的该文件夹中。


</details> <details> <summary>通过 <strong>Homebrew</strong> <em>(系统范围安装)</em></summary>

要通过 Homebrew 安装 SwiftGen,只需使用:

$ brew update $ brew install swiftgen

这将系统范围安装 SwiftGen。同一版本的 SwiftGen 将用于该机器上的所有项目,你应该确保你的所有同事在他们的机器上也安装了相同版本的 SwiftGen。

然后你可以直接在脚本构建阶段调用 swiftgen(因为它已经在你的 $PATH 中):

swiftgen …

</details> <details> <summary>通过 <strong>Mint</strong> <em>(系统范围安装)</em></summary>

❗️仅适用于 SwiftGen 6.0 或更高版本。

要通过 Mint 安装 SwiftGen,只需使用:

$ mint install SwiftGen/SwiftGen

</details> <details> <summary><strong>从源代码编译</strong> <em>(仅在你需要 `stable` 分支的功能或想测试 PR 时推荐)</em></summary>

这个解决方案适用于当你想构建和安装 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


</details>

macOS 10.14.4 之前的已知安装问题

SwiftGen 6.2.1 开始,如果在运行 SwiftGen 时出现类似 dyld: Symbol not found: _$s11SubSequenceSlTl 的错误,你需要安装 Swift 5 Runtime Support for Command Line Tools

或者,你可以:

  • 更新到 macOS 10.14.4 或更高版本
  • /Applications/Xcode.app 安装 Xcode 10.2 或更高版本
  • 使用 Xcode 10.2 或更高版本从源代码重新构建 SwiftGen

配置文件

❗️ 如果你从旧版本的 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 查看详细用法。

<details> <summary><strong>不使用配置文件直接调用解析器</strong></summary>

虽然我们强烈建议使用配置文件以提高性能(尤其是如果你有多个输出,但也因为它更灵活),也可以使用 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模板。
  • 注意:调用SwiftGen时应只指定一个模板。你必须使用 -t-p 之一,但不应同时使用两者(这样做没有意义,如果你尝试这样做会收到错误)
  • --filter 正则表达式-f 正则表达式: 应用于每个输入路径的过滤器。过滤器应用于实际(相对)路径,而不仅仅是文件名。每个命令都有一个默认过滤器,你可以用这个选项覆盖它。
  • 注意:使用 .+ 匹配多个字符(至少一个),如果要匹配文字点(如扩展名),别忘了转义点(\.)。在末尾添加 $ 以确保路径以你想要的扩展名结尾。正则表达式默认区分大小写,并且不会锚定到路径的开头/结尾。例如,使用 .+\.xib$ 匹配扩展名为 .xib 的文件。使用像 RegExr 这样的工具来确保你使用的是有效的正则表达式。
  • 每个命令支持多个输入文件(或适用的目录)。
  • 你始终可以使用 --help 标志查看命令接受的选项,例如 swiftgen run xcassets --help

选择模板

SwiftGen 基于模板(它使用 Stencil 作为模板引擎)。这意味着你可以选择适合你正在使用的 Swift 版本的模板 - 也是最适合你偏好的模板 - 以使生成的代码适应你自己的约定和 Swift 版本

内置模板 vs. 自定义模板

SwiftGen 随附了一些适用于每个解析器的模板(colors, coredata, files, fonts, ib, json, plist, strings, xcassets, yaml),这些模板可以满足大多数需求;只需使用 templateName 输出选项指定要使用的模板名称即可。但如果内置模板不适合你的编码约定或需求,你也可以创建自己的模板:只需将它们存储在任何地方(比如你的项目仓库中),并使用 templatePath 输出选项而不是 templateName 来指定它们的路径。

💡 你可以使用 swiftgen template list 命令列出每个解析器可用的所有内置模板,并使用 swiftgen template cat 显示模板内容并复制它以创建自己的变体。

有关如何创建自己的模板的更多信息,请参阅专门的文档

SwiftGen 随附的模板:

如上所述,你可以使用 swiftgen template list 列出 SwiftGen 随附的所有模板。对于大多数 SwiftGen 解析器,我们提供:

  • 一个 swift4 模板,兼容 Swift 4
  • 一个 swift5 模板,兼容 Swift 5
  • 其他变体,如用于 Strings 的 flat-swift4/5structured-swift4/5 模板等。

你可以在这里的仓库中找到每个内置模板的文档这里,文档按每个 SwiftGen 解析器组织为一个文件夹,然后每个模板一个 MarkDown 文件。你还可以使用 swiftgen template doc 直接从终端在浏览器中打开该文档页面。

每个 MarkDown 文件记录了它针对的 Swift 版本,该模板的用例(在哪些情况下你可能会选择该模板而不是其他模板),调用时可用的自定义参数(使用配置文件中的 params: 键),以及一些代码示例。

不要犹豫,提出 PR 分享你对内置模板的改进建议 😉

其他文档

Playground

本仓库中提供的 SwiftGen.playground 允许你使用该工具通常生成的代码进行试验,并查看一些如何利用它的示例。

这允许你快速了解 SwiftGen 生成的典型代码的样子,以及如何在代码中使用生成的常量。

Markdown 专用文档

本仓库以及相关的 StencilSwiftKit 仓库中有大量以 Markdown 格式提供的文档。

请务必查看每个仓库的 "Documentation" 文件夹

特别是,除了前面提到的迁移指南配置文件文档外,SwiftGen 仓库的 Documentation/ 文件夹还包括:

教程

你还可以在互联网上找到其他帮助和教程材料,比如我在 2017 年 9 月 FrenchKit 上给出的这个关于代码生成的课堂 — 以及其 wiki 详细介绍了关于安装和使用 SwiftGen(以及 Sourcery)的分步教程


可用的解析器

Asset Catalog

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)

Colors

❗️ 我们建议在 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

输入文件预期是:

  • 一个纯文本文件,每行一个要注册的颜色,每行由要给颜色的名称组成,后跟":",然后是颜色的十六进制表示(如 rrggbbrrggbbaa,可选前缀为 #0x)或文件中另一种颜色的名称。空白将被忽略。
  • 一个JSON 文件,表示名称 -> 值的字典,每个值都是颜色的十六进制表示
  • 一个XML 文件,预期格式与 Android colors.xml 文件相同,包含标签 <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值,也不需要为它们创建全局命名空间中的丑陋常量。

Core Data

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

这将解析指定的核心数据模型,为模型中的每个实体生成一个类,其中包含所有属性,并在需要时为关系和预定义的获取请求生成一些扩展。

<details> <summary>内置模板生成的代码示例</summary>
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>) }
</details>

使用示例

// 获取 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> <summary>内置模板生成的代码示例</summary>
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") } }
</details>

使用示例

// 使用 `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>
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") } }
</details>

给定与上面相同的文件和文件夹结构,使用方式现在将是:

// 使用 `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> <summary>内置模板生成的代码示例</summary>
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") } }
</details>

使用示例

// 您可以使用便利构造函数这样创建字体: 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)

Interface Builder

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> <summary>内置模板生成的代码示例</summary>

生成的代码将如下所示:

// 场景模板的输出 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" } }
</details>

使用示例

// 您可以使用 `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 和 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

这将解析给定的文件,或者在给定目录时,递归搜索 JSON 和 YAML 文件。它将为每个文件(以及需要时文件中的文档)定义一个 enum,并为文件内容定义类型安全的常量。

与其他解析器不同,这个解析器旨在允许您使用更多自定义输入(因为这些格式相当开放,可以满足您的需求)来生成代码。这意味着对于这些解析器(以及 plist 解析器),您可能更有可能使用自定义模板来生成适当适应/调整到您的输入的代码,而不是使用内置模板。要了解更多关于编写自己的自定义模板的信息,请参阅专门的文档

<details> <summary>内置模板生成的代码示例</summary> ```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") } } ```

使用示例

// 这将是一个字典 let foo = JSONFiles.Info.key3 // 这将是一个 [Int] 数组 let bar = JSONFiles.Sequence.items

Plist 文件

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

这将解析给定的文件,或者当给定一个目录时,递归搜索 Plist 文件。它将为每个文件(以及文件中需要的文档)定义一个 enum,并为文件内容定义类型安全的常量。

与其他解析器不同,这个解析器旨在允许您使用更多自定义输入(因为格式相当开放以满足您的需求)来生成代码。这意味着对于这个解析器(以及 jsonyaml 解析器),您可能更倾向于使用自定义模板来生成适当调整/适应您的输入的代码,而不是使用捆绑的模板。要了解更多关于编写自定义模板的信息,请参阅专门的文档

<details> <summary>捆绑模板生成的代码示例</summary>
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"] } }
</details>

使用示例

// 这将是一个数组 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.stringsLocalizable.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 在运行时的行为。

</details>

使用示例

一旦脚本生成了代码,您可以在 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>
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 根香蕉属于 %@。"#) } }
</details>

给定与上面相同的 Localizable.stringsLocalizable.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,还包括 UITableViewCellUICollectionViewCell 和基于 XIB 的视图,您应该看看我的 Mixin Reusable

如果您想从自己的 Swift 代码生成 Swift 代码(太元了!),比如为您的类型生成 Equatable 一致性和许多其他类似的事情,请使用 Sourcery

SwiftGen 和 Sourcery 是互补工具。事实上,Sourcery 也使用 Stencil,以及 SwiftGen 的 StencilSwiftKit,所以您可以为两者使用完全相同的模板语法!

您也可以在 Twitter 上关注我,了解我正在创建的其他项目的新闻/更新,或者阅读我的博客

编辑推荐精选

讯飞星火

讯飞星火

深度推理能力全新升级,全面对标OpenAI o1

科大讯飞的星火大模型,支持语言理解、知识问答和文本创作等多功能,适用于多种文件和业务场景,提升办公和日常生活的效率。讯飞星火是一个提供丰富智能服务的平台,涵盖科技资讯、图像创作、写作辅助、编程解答、科研文献解读等功能,能为不同需求的用户提供便捷高效的帮助,助力用户轻松获取信息、解决问题,满足多样化使用场景。

模型训练热门AI工具内容创作智能问答AI开发讯飞星火大模型多语种支持智慧生活
Spark-TTS

Spark-TTS

一种基于大语言模型的高效单流解耦语音令牌文本到语音合成模型

Spark-TTS 是一个基于 PyTorch 的开源文本到语音合成项目,由多个知名机构联合参与。该项目提供了高效的 LLM(大语言模型)驱动的语音合成方案,支持语音克隆和语音创建功能,可通过命令行界面(CLI)和 Web UI 两种方式使用。用户可以根据需求调整语音的性别、音高、速度等参数,生成高质量的语音。该项目适用于多种场景,如有声读物制作、智能语音助手开发等。

Trae

Trae

字节跳动发布的AI编程神器IDE

Trae是一种自适应的集成开发环境(IDE),通过自动化和多元协作改变开发流程。利用Trae,团队能够更快速、精确地编写和部署代码,从而提高编程效率和项目交付速度。Trae具备上下文感知和代码自动完成功能,是提升开发效率的理想工具。

热门AI工具生产力协作转型TraeAI IDE
咔片PPT

咔片PPT

AI助力,做PPT更简单!

咔片是一款轻量化在线演示设计工具,借助 AI 技术,实现从内容生成到智能设计的一站式 PPT 制作服务。支持多种文档格式导入生成 PPT,提供海量模板、智能美化、素材替换等功能,适用于销售、教师、学生等各类人群,能高效制作出高品质 PPT,满足不同场景演示需求。

讯飞绘文

讯飞绘文

选题、配图、成文,一站式创作,让内容运营更高效

讯飞绘文,一个AI集成平台,支持写作、选题、配图、排版和发布。高效生成适用于各类媒体的定制内容,加速品牌传播,提升内容营销效果。

AI助手热门AI工具AI创作AI辅助写作讯飞绘文内容运营个性化文章多平台分发
材料星

材料星

专业的AI公文写作平台,公文写作神器

AI 材料星,专业的 AI 公文写作辅助平台,为体制内工作人员提供高效的公文写作解决方案。拥有海量公文文库、9 大核心 AI 功能,支持 30 + 文稿类型生成,助力快速完成领导讲话、工作总结、述职报告等材料,提升办公效率,是体制打工人的得力写作神器。

openai-agents-python

openai-agents-python

OpenAI Agents SDK,助力开发者便捷使用 OpenAI 相关功能。

openai-agents-python 是 OpenAI 推出的一款强大 Python SDK,它为开发者提供了与 OpenAI 模型交互的高效工具,支持工具调用、结果处理、追踪等功能,涵盖多种应用场景,如研究助手、财务研究等,能显著提升开发效率,让开发者更轻松地利用 OpenAI 的技术优势。

Hunyuan3D-2

Hunyuan3D-2

高分辨率纹理 3D 资产生成

Hunyuan3D-2 是腾讯开发的用于 3D 资产生成的强大工具,支持从文本描述、单张图片或多视角图片生成 3D 模型,具备快速形状生成能力,可生成带纹理的高质量 3D 模型,适用于多个领域,为 3D 创作提供了高效解决方案。

3FS

3FS

一个具备存储、管理和客户端操作等多种功能的分布式文件系统相关项目。

3FS 是一个功能强大的分布式文件系统项目,涵盖了存储引擎、元数据管理、客户端工具等多个模块。它支持多种文件操作,如创建文件和目录、设置布局等,同时具备高效的事件循环、节点选择和协程池管理等特性。适用于需要大规模数据存储和管理的场景,能够提高系统的性能和可靠性,是分布式存储领域的优质解决方案。

TRELLIS

TRELLIS

用于可扩展和多功能 3D 生成的结构化 3D 潜在表示

TRELLIS 是一个专注于 3D 生成的项目,它利用结构化 3D 潜在表示技术,实现了可扩展且多功能的 3D 生成。项目提供了多种 3D 生成的方法和工具,包括文本到 3D、图像到 3D 等,并且支持多种输出格式,如 3D 高斯、辐射场和网格等。通过 TRELLIS,用户可以根据文本描述或图像输入快速生成高质量的 3D 资产,适用于游戏开发、动画制作、虚拟现实等多个领域。

下拉加载更多