|
8 | 8 |
|
9 | 9 | `Text` composition in SwiftUI can often be cumbersome, especially when there's logic affecting its format and content.
|
10 | 10 |
|
11 |
| -TextBuilder leverages the power of Swift [Result Builders](https://github.com/apple/swift-evolution/blob/main/proposals/0289-result-builders.md) to solve this problem. TextBuilder mimics SwiftUI's [ViewBuilder](https://developer.apple.com/documentation/swiftui/viewbuilder) to make for a familiar experience at the point of use. |
| 11 | +TextBuilder leverages the power of Swift [Macros](https://docs.swift.org/swift-book/documentation/the-swift-programming-language/macros/) to solve this problem. The `@TextBuilder` macro transforms functions into builder-style closures, making text composition intuitive and readable. |
| 12 | + |
| 13 | +## Installation |
| 14 | + |
| 15 | +Add `TextBuilder` to your Swift Package Manager dependencies: |
| 16 | + |
| 17 | +```swift |
| 18 | +.package(url: "https://github.com/davdroman/swiftui-text-builder", from: "4.0.0"), |
| 19 | +``` |
| 20 | + |
| 21 | +Then, add the dependency to your desired target: |
| 22 | + |
| 23 | +```swift |
| 24 | +.product(name: "TextBuilder", package: "swiftui-text-builder"), |
| 25 | +``` |
12 | 26 |
|
13 | 27 | ## Usage
|
14 | 28 |
|
15 |
| -TextBuilder offers 3 ready-made builders out of the box, depending on which text separator you need. |
| 29 | +### Basic Usage |
16 | 30 |
|
17 |
| -### Default (unspaced) |
| 31 | +Apply `@TextBuilder` to functions that return `Text`. The macro will transform the function body into a builder-style closure that concatenates text segments. |
18 | 32 |
|
19 | 33 | ```swift
|
20 | 34 | @TextBuilder
|
21 |
| -var loremIpsum: Text { |
| 35 | +func loremIpsum() -> Text { |
22 | 36 | Text("Lorem").underline().foregroundColor(.blue)
|
23 | 37 | Text("ipsum dolor")
|
24 | 38 | Text("sit").bold()
|
25 | 39 | Text("amet, consectetur")
|
26 | 40 | }
|
27 | 41 | ```
|
28 | 42 |
|
29 |
| - |
| 43 | +This creates a concatenated `Text` without any separators between segments. |
| 44 | + |
| 45 | +### With Separators |
30 | 46 |
|
31 |
| -### With Spaces |
| 47 | +You can specify a separator to be inserted between text segments: |
32 | 48 |
|
33 | 49 | ```swift
|
34 |
| -@TextBuilderWithSpaces |
35 |
| -var loremIpsum: Text { |
| 50 | +@TextBuilder(separator: " ") |
| 51 | +func spacedText() -> Text { |
36 | 52 | Text("Lorem").underline().foregroundColor(.blue)
|
37 | 53 | Text("ipsum dolor")
|
38 | 54 | Text("sit").bold()
|
39 | 55 | Text("amet, consectetur")
|
40 | 56 | }
|
41 | 57 | ```
|
42 | 58 |
|
43 |
| - |
44 |
| - |
45 |
| -### Multiline |
| 59 | +For multiline text: |
46 | 60 |
|
47 | 61 | ```swift
|
48 |
| -@TextBuilderWithNewlines |
49 |
| -var loremIpsum: Text { |
| 62 | +@TextBuilder(separator: "\n") |
| 63 | +func multilineText() -> Text { |
50 | 64 | Text("Lorem").underline().foregroundColor(.blue)
|
51 | 65 | Text("ipsum dolor")
|
52 | 66 | Text("sit").bold()
|
53 | 67 | Text("amet, consectetur")
|
54 | 68 | }
|
55 | 69 | ```
|
56 | 70 |
|
57 |
| - |
58 |
| - |
59 |
| -### Pro Tip ✨ |
| 71 | +### String Support ✨ |
60 | 72 |
|
61 |
| -TextBuilder accepts `String` types directly as if they were plain `Text`, and also provides a `String.text` computed var to remove unwanted code noise when `Text` is explicitly needed. |
| 73 | +TextBuilder accepts `String` types directly and provides a convenient `.text` computed property: |
62 | 74 |
|
63 | 75 | ```swift
|
64 |
| -@TextBuilderWithNewlines |
65 |
| -var loremIpsum: Text { |
66 |
| - "Lorem".text.underline().foregroundColor(.blue) |
67 |
| - "ipsum dolor" |
68 |
| - "sit".text.bold() |
69 |
| - "amet, consectetur" |
| 76 | +@TextBuilder(separator: " ") |
| 77 | +func mixedText() -> Text { |
| 78 | + "Hello" // String literal becomes verbatim Text |
| 79 | + "world".text.bold() // Use .text for chaining modifiers |
| 80 | + String(2025) // Any StringProtocol works |
70 | 81 | }
|
71 | 82 | ```
|
72 | 83 |
|
73 |
| -### Other Separators |
| 84 | +### Control Flow |
74 | 85 |
|
75 |
| -There are two options to customize the separator used to compose your `Text`. |
76 |
| - |
77 |
| -First, you can use `Text.init(separator:content:)`: |
| 86 | +TextBuilder supports Swift's control flow statements: |
78 | 87 |
|
79 | 88 | ```swift
|
80 |
| -var loremIpsum: Text { |
81 |
| - Text(separator: " 🍆 ") { |
82 |
| - "Lorem".text.underline().foregroundColor(.blue) |
83 |
| - "ipsum dolor" |
84 |
| - "sit".text.bold() |
85 |
| - "amet, consectetur" |
| 89 | +@TextBuilder(separator: " ") |
| 90 | +func conditionalText(showDetails: Bool) -> Text { |
| 91 | + "Hello" |
| 92 | + |
| 93 | + if showDetails { |
| 94 | + "with details" |
| 95 | + } else { |
| 96 | + "basic" |
| 97 | + } |
| 98 | + |
| 99 | + if let name = userName { |
| 100 | + name.text.italic() |
| 101 | + } |
| 102 | + |
| 103 | + for i in 1...3 { |
| 104 | + String(i) |
86 | 105 | }
|
87 | 106 | }
|
88 | 107 | ```
|
89 | 108 |
|
90 |
| -But if you prefer to keep using a result builder, you can: |
| 109 | +### Alternative API |
91 | 110 |
|
92 |
| -```swift |
93 |
| -struct EggplantSeparator: TextBuilderSeparator { |
94 |
| - static var separator: String? { " 🍆 " } |
95 |
| -} |
| 111 | +If you prefer not to use macros, you can use the underlying `Text` initializer directly: |
96 | 112 |
|
97 |
| -@TextBuilderWith<EggplantSeparator> |
98 |
| -var loremIpsum: Text { |
99 |
| - "Lorem".text.underline().foregroundColor(.blue) |
100 |
| - "ipsum dolor" |
101 |
| - "sit".text.bold() |
102 |
| - "amet, consectetur" |
| 113 | +```swift |
| 114 | +var body: some View { |
| 115 | + Text(separator: " 👏 ") { |
| 116 | + "Lorem".text.underline().foregroundColor(.blue) |
| 117 | + "ipsum dolor" |
| 118 | + "sit".text.bold() |
| 119 | + "amet, consectetur" |
| 120 | + } |
103 | 121 | }
|
104 | 122 | ```
|
105 | 123 |
|
106 |
| - |
107 |
| - |
108 |
| -## Benchmarks |
| 124 | +This is useful if you simply want to insert some rich text into a view body without defining a separate function. |
109 | 125 |
|
110 |
| -``` |
111 |
| -MacBook Pro (14-inch, 2021) |
112 |
| -Apple M1 Pro (10 cores, 8 performance and 2 efficiency) |
113 |
| -32 GB Memory |
| 126 | +## Limitations |
114 | 127 |
|
115 |
| -$ swift run -c release Benchmarks |
| 128 | +The `@TextBuilder` macro currently cannot be applied to computed properties due to Swift limitations. Use functions instead. |
116 | 129 |
|
117 |
| -name time std iterations |
118 |
| ------------------------------------------------- |
119 |
| -Result Builder 1875.000 ns ± 26.15 % 729940 |
120 |
| -Initializer 2542.000 ns ± 16.88 % 540826 |
121 |
| -``` |
| 130 | +See [Swift Issue #75715](https://github.com/swiftlang/swift/issues/75715) for updates on computed property support. |
0 commit comments