Last active
October 11, 2025 23:17
-
-
Save ValentinWalter/fe36ba6c2b865764b73a59a528eba39b to your computer and use it in GitHub Desktop.
Build Text instances in a declarative manner, similar to regular views, from Text and Image. This ensures the font’s kerning, ligatures, and other properties are preserved across the entire text.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// TextBuilder.swift | |
// SemanticalKit | |
// | |
// Created by Valentin Walter on 14.02.23. | |
// | |
import SwiftUI | |
/// Build Text instances in a declarative manner, similar to regular views, from | |
/// Text and Image. This ensures the font’s kerning, ligatures, and other | |
/// properties are preserved across the entire text. | |
/// | |
/// Example | |
/// | |
/// ```swift | |
/// Text { | |
/// for index in 0..<10 { | |
/// Image(systemName: "\(index).circle") | |
/// .foregroundStyle(.secondary) | |
/// } | |
/// | |
/// if showLabel { | |
/// Text(label) | |
/// .font(.headline) | |
/// } | |
/// } | |
/// ``` | |
/// | |
@resultBuilder | |
public struct TextBuilder { | |
public typealias Component = [Text] | |
@inlinable | |
public static func buildPartialBlock(first: Component) -> Component { | |
first | |
} | |
@inlinable | |
public static func buildPartialBlock(accumulated: Component, next: Component) -> Component { | |
accumulated + next | |
} | |
@inlinable | |
public static func buildArray(_ components: [Component]) -> Component { | |
components.flatMap(\.self) | |
} | |
@inlinable | |
public static func buildEither(first component: Component) -> Component { | |
component | |
} | |
@inlinable | |
public static func buildEither(second component: Component) -> Component { | |
component | |
} | |
@inlinable | |
public static func buildOptional(_ component: Component?) -> Component { | |
component ?? [] | |
} | |
@inlinable | |
public static func buildExpression(_ image: Image) -> Component { | |
[Text("\(image)")] | |
} | |
@inlinable | |
public static func buildExpression(_ text: Text) -> Component { | |
[text] | |
} | |
@inlinable | |
public static func buildFinalResult(_ component: Component) -> Component { | |
component | |
} | |
@inlinable | |
public static func buildFinalResult(_ component: Component) -> Text { | |
component.dropFirst().reduce(component.first ?? Text(""), +) | |
} | |
} | |
// MARK: - Text+ | |
extension Text { | |
/// Build `Text` instances in a declarative manner, similar to regular | |
/// views, from `Text` and `Image`. This ensures the font's kerning, | |
/// ligatures, and other properties are preserved across the entire text. | |
/// | |
/// Example | |
/// | |
/// ```swift | |
/// Text { | |
/// for index in 0..<10 { | |
/// Image(systemName: "\(index).circle") | |
/// .foregroundStyle(.secondary) | |
/// } | |
/// | |
/// if showLabel { | |
/// Text(label) | |
/// .font(.headline) | |
/// } | |
/// } | |
/// ``` | |
/// | |
public init(separator: String = "", @TextBuilder content: () -> [Text]) { | |
let texts = content() | |
self = texts.dropFirst().reduce(texts.first) { | |
if let partialResult = $0 { | |
partialResult + Text(separator) + $1 | |
} else { | |
nil | |
} | |
} ?? Text("") | |
} | |
public init( | |
@TextBuilder content: () -> [Text], | |
@TextBuilder separator: () -> Text, | |
) { | |
let texts = content() | |
self = texts.dropFirst().reduce(texts.first) { | |
if let partialResult = $0 { | |
partialResult + separator() + $1 | |
} else { | |
nil | |
} | |
} ?? Text("") | |
} | |
} | |
// MARK: - Image+ | |
extension Image { | |
@inlinable | |
public func foregroundStyle(_ style: some ShapeStyle) -> Text { | |
Text("\(self)").foregroundStyle(style) | |
} | |
@inlinable | |
public func font(_ font: Font) -> Text { | |
Text("\(self)").font(font) | |
} | |
} | |
// MARK: - Previews | |
#Preview { | |
let showLabel = true | |
return Text { | |
for index in 0..<4 { | |
Image(systemName: "\(index).circle") | |
.foregroundStyle(.secondary) | |
} | |
Text(" ") | |
if showLabel { | |
Text("Label") | |
.font(.headline) | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment