Hi there. I'd like to work on making the fonts template swift5.stencil compatible with SwiftUI. Right now internal typealias Font shadows the SwiftUI type Font and that leads to build errors. I'd like to change the typealias name to CustomFont and also implement SwiftUI's Font extension that allows to use SwiftGen generated fonts in UI code. Usage would look like this:
Text("Hello World!")
.font(.custom(FontFamily.SFNSDisplay.regular, size: 16))
What do you think?
Can we go as far as this? (haven't worked with SwiftUI yet)
Text("Hello World!")
.font(.custom(.SFNSDisplay.regular, size: 16))
// or maybe
Text("Hello World!")
.font(FontFamily.SFNSDisplay.regular, size: 16)
Note that the typealias bug is a separate issue: #647.
I'm currently working on my first pure SwiftUI app and created this custom template which works for my needs:
// Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen
{% if families %}
import SwiftUI
{% for family in families %}
{% set identifierName %}{{family.name|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}}{% endset %}
{% set styleTypeName %}{{family.name|swiftIdentifier:"pretty"|escapeReservedKeywords}}Style{% endset %}
extension Font {
public static func {{identifierName}}(_ style: {{styleTypeName}}, fixedSize: CGFloat) -> Font {
return Font.custom(style.rawValue, fixedSize: fixedSize)
}
public static func {{identifierName}}(_ style: {{styleTypeName}}, size: CGFloat, relativeTo textStyle: TextStyle = .body) -> Font {
return Font.custom(style.rawValue, size: size, relativeTo: textStyle)
}
public enum {{styleTypeName}}: String {
{% for font in family.fonts %}
case {{font.style|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}} = "{{font.name}}"
{% endfor %}
}
}
{% endfor %}
{% else %}
// No fonts found
{% endif %}
With one font included (e.g. Metropolis) this results in a file like this:
// Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen
import SwiftUI
extension Font {
public static func metropolis(_ style: MetropolisStyle, fixedSize: CGFloat) -> Font {
return Font.custom(style.rawValue, fixedSize: fixedSize)
}
public static func metropolis(_ style: MetropolisStyle, size: CGFloat, relativeTo textStyle: TextStyle = .body) -> Font {
return Font.custom(style.rawValue, size: size, relativeTo: textStyle)
}
public enum MetropolisStyle: String {
case black = "Metropolis-Black"
case blackItalic = "Metropolis-BlackItalic"
case bold = "Metropolis-Bold"
case boldItalic = "Metropolis-BoldItalic"
case extraBold = "Metropolis-ExtraBold"
case extraBoldItalic = "Metropolis-ExtraBoldItalic"
case extraLight = "Metropolis-ExtraLight"
case extraLightItalic = "Metropolis-ExtraLightItalic"
case light = "Metropolis-Light"
case lightItalic = "Metropolis-LightItalic"
case medium = "Metropolis-Medium"
case mediumItalic = "Metropolis-MediumItalic"
case regular = "Metropolis-Regular"
case regularItalic = "Metropolis-RegularItalic"
case semiBold = "Metropolis-SemiBold"
case semiBoldItalic = "Metropolis-SemiBoldItalic"
case thin = "Metropolis-Thin"
case thinItalic = "Metropolis-ThinItalic"
}
}
On usage side code looks like this:
var body: some View {
Text("Title").font(.metropolis(.bold, size: 60, relativeTo: .largeTitle)
Text("Lorem ipsum ...").font(.metropolis(.regular, size: 17, relativeTo: .body)
}
The relativeTo is important for dynamic type to know how to properly scale.
I personally abstracted away on top of this to make usage even simpler, here's an exerpt:
extension Font {
static func textStyle(_ textStyle: CustomTextStyle) -> Font {
switch textStyle {
case let .hugeTitle(bold):
return .metropolis(bold ? .bold : .regular, size: 60, relativeTo: .largeTitle)
case let .body(semibold, italic):
return .metropolis(metropolisStyle(semibold: semibold, italic: italic), size: 17, relativeTo: .body)
}
}
/// Decides between .semibold and .regular styles and also takes italic styles into consideration.
private static func metropolisStyle(semibold: Bool, italic: Bool) -> MetropolisStyle {
if semibold {
return italic ? .semiBoldItalic : .semiBold
}
else {
return italic ? .regularItalic : .regular
}
}
}
enum CustomTextStyle {
case hugeTitle(bold: Bool)
case body(semibold: Bool, italic: Bool)
}
extension View {
func textStyle(_ textStyle: CustomTextStyle) -> some View {
font(.textStyle(textStyle))
}
}
The above usage then turns into this instead, which I find much better because it allows me to change the font styles in one place (the CustomTextStyle type) and it will be automatically applied everywhere in the app (useful for design systems):
var body: some View {
Text("Title").textStyle(.hugeTitle(bold: true))
Text("Lorem ipsum ...").textStyle(.body(semibold: false, italic: false))
}
I hope this helps someone.
Most helpful comment
I'm currently working on my first pure SwiftUI app and created this custom template which works for my needs:
With one font included (e.g. Metropolis) this results in a file like this:
On usage side code looks like this:
The
relativeTois important for dynamic type to know how to properly scale.I personally abstracted away on top of this to make usage even simpler, here's an exerpt:
The above usage then turns into this instead, which I find much better because it allows me to change the font styles in one place (the
CustomTextStyletype) and it will be automatically applied everywhere in the app (useful for design systems):I hope this helps someone.