虽然我们欣赏 Swift 宏在减少样板代码方面的强大功能,但我们发现它在某些情况下并非直接(甚至由于其设计目标限制而无法实现)。 这个软件包旨在弥合这一差距,使我们能够基于某些其他代码,使用模板生成模式代码。 它对于以下情况非常实用:
EnvironmentKey
在 EnvironmentValues
扩展中生成相应的变量。UITraitDefinition
在 UITraitCollection
扩展中生成相应的变量。ViewModifer
在 View
扩展中生成相应的方法。.package(url: "https://github.com/ShenghaiWang/SwiftCodePatterns.git", from: "1.0.0")
相同的功能可以通过 SPM 命令插件和 SPM 构建工具插件来实现。 使用 SPM 命令插件配置规则并首先进行探索。 在此命令的输出末尾,找到生成的代码文件名和路径。 打开它并确认生成的代码是否符合预期。 在对结果满意后,将构建工具插件集成到目标构建阶段。 在此模式下,生成的代码将自动包含到项目中。 有关 SPM 插件的更多信息,请参见 此处。
此软件包中包含一个示例配置文件 AutoCodePatterns.yml
,它提供了为新定义的 EnvironmentKey
,UITraitDefinition
,ViewModifer
生成代码的规则。 它还具有两种方法,用于为类类型生成符合 Equatable
,Hashable
的代码。 请根据项目需要调整模板,并将此配置文件放在项目的根文件夹中(请保持文件名不变)。
目前,它支持两种类型的转换。 一种是使用 template
,另一种是使用 Swift
代码。 请查看下面的示例以了解详细信息。
scope:
# include: # either using `include` or `exclude`, if both are configured, the `exclude` will be ingored.
# - /a.swift # include this file - ending with `.swift`
# - /a/ # include this path prefix
exclude:
- /a.swift # exclude this file - ending with `.swift`
- /a/ # exclude this path prefix
rules:
-
rule: autoEnvironmentKey # rule name to help reason about the purpose of the rule
selector: # the criteria to apply this rule, could be a combinstion of type, inheritence, included names, excluded names
type: struct # the data type
inherits: # the inheritence
- EnvironmentKey # only the type that inherits this type will be eligible for this rule
imports: # the frameworks that need to be imported in the file
- SwiftUI
# The transform if the rule applies. The code quoted by `#` will be expanded.
# If the code inside two # need to expand again, quote it using two `|`. In this case, #name# becomes |name|
# In total, we have 3 types of expansion so far: name, type, properties.
# Name expansion will be based on the type name
# Type expansion will be based on the data type name
# Properties expansion will be based on the properties defined in the type.
# Properties expansion is different from name and type expansion as it could be a loop in cased of more than 1 properties defined in the type.
# Both name and type expansion can have transformers followed.
# All the transformers are separated using `*` and they will be applied into name and type in order.
# And they all have a straightforward name to reason about their functionality.
# The possible transformers for name and type are:
# `identity`, `lowerInitial`, `upperInitial`: these 3 don't need parameter
# `replaceTo(Value)`, `removeSuffix(Value)`, `addSuffix(Value)`, `removePrefix(Value)`, `addPrefix(Value)`: these 4 need one parameters, just put a new string in bracket without quotes.
# `replace(oldValue,newValue)`: these one need two parameters, it will replace the oldValue to newValue using `String.replacingOccurrences(of:with:)`
# Combine these in orders to get the right possible name
# Properties expansion needs a `joiner`. For example `#properties*joiner( && )<lhs.|name| == rhs.|name|>#`.
# in this case the name inside this property expansion was quoted using `|`.
# Can also do the same for type here if you need type in the destination code.
# Can even following the transformers like |name.upperInital| or |type.lowerInitial| etc. Combine them in a sensible way to get the code looks right.
# transform: >
# extension EnvironmentValues {
# var #name*lowerInitial#: String {
# get { self[#name#.self] }
# set { self[#name#.self] = newValue }
# }
#
# var #name*lowerInitial*removeSuffix(Key)*addSuffix(Value)#: String {
# get { self[#name#.self] }
# set { self[#name#.self] = newValue }
# }
# }
# If feeling swifty, choose code transform instead. The codeTransform below is identical to the transform above.
# Even though it looks wordy for this case, this approach might be useful for some cases where need more flexibilities of transforming code.
# The `name` is a `String` type and `properties` is an array of tuple type of `(name: String, type: String)`
# The code write can directly access to these two values
codeTransform: | # Write swift code as usual and assign the final result to `generatedCode` variable in `String` format
let name1 = name.prefix(1).lowercased() + name.dropFirst()
let name2 = (name.prefix(1).lowercased() + name.dropFirst()).replacingOccurrences(of: "Key", with: "Value")
generatedCode =
"""
extension EnvironmentValues {
var \(name1): String {
get { self[\(name).self] }
set { self[\(name).self] = newValue }
}
var \(name2): String {
get { self[\(name).self] }
set { self[\(name).self] = newValue }
}
}
"""
-
rule: autoTraitDefinition
selector:
type: struct
inherits:
- UITraitDefinition
imports:
- SwiftUI
transform: >
extension UITraitCollection {
var #name*addPrefix(is)*removeSuffix(Trait)#: Bool { self[#name#.self] }
}
extension UIMutableTraits {
var #name*addPrefix(is)*removeSuffix(Trait)#: Bool {
get { self[#name#.self] }
set { self[#name#.self] = newValue }
}
}
-
rule: autoViewModifier
selector:
type: struct
inherits:
- ViewModifier
imports:
- SwiftUI
transform: >
extension View {
func #name*lowerInitial#(#properties*joiner(, )<|name|: |type|>#) -> some View {
modifier(#name#(#properties*joiner(, )<|name|: |name|>#))
}
}
-
rule: autoEquatable
selector:
type: class
inherits:
- MyType
transform: >
extension #name#: Equatable {
static func == (lhs: #name#, rhs: #name#) -> Bool {
#properties*joiner( && )<lhs.|name| == rhs.|name|>#
}
}
-
rule: autoHashable
selector:
type: class
inherits:
- MyType
transform: >
extension #name#: Hashable {
func hash(into hasher: inout Hasher) {
#properties*joiner()<hasher.combine(|name|)>#
}
}
import Foundation
struct MyEnvironmentKey: EnvironmentKey {
static let defaultValue: String = "Default value"
}
struct ContainedInSettingsTrait: UITraitDefinition {
static let defaultValue = false
}
struct SimpleViewModifier: ViewModifier {
func body(content: Content) -> some View {
content
.font(.caption2)
}
}
struct ViewModifierWithInit: ViewModifier {
let font: Font
let padding: CGFloat
let color: Color?
func body(content: Content) -> some View {
content
.font(font)
.padding(padding)
.foregroundColor(color)
}
}
protocol MyType {}
class ABC: MyType {
var a: String
var b: Int
}
import SwiftUI
extension EnvironmentValues {
var myEnvironmentKey: String {
get {
self [MyEnvironmentKey.self]
}
set {
self [MyEnvironmentKey.self] = newValue
}
}
var myEnvironmentValue: String {
get {
self [MyEnvironmentKey.self]
}
set {
self [MyEnvironmentKey.self] = newValue
}
}
}
extension UITraitCollection {
var isContainedInSettings: Bool {
self [ContainedInSettingsTrait.self]
}
}
extension UIMutableTraits {
var isContainedInSettings: Bool {
get {
self [ContainedInSettingsTrait.self]
}
set {
self [ContainedInSettingsTrait.self] = newValue
}
}
}
extension View {
func simpleViewModifier() -> some View {
modifier(SimpleViewModifier())
}
}
extension View {
func viewModifierWithInit(font: Font, padding: CGFloat, color: Color?) -> some View {
modifier(ViewModifierWithInit(font: font, padding: padding, color: color))
}
}
extension ABC: Equatable {
static func == (lhs: ABC, rhs: ABC) -> Bool {
lhs.a == rhs.a && lhs.b == rhs.b
}
}
extension ABC: Hashable {
func hash(into hasher: inout Hasher) {
hasher.combine(a)
hasher.combine(b)
}
}