RichStringKit is a declarative DSL for building rich text in Swift.
NSAttributedString
is a powerful tool for creating rich text, but using it can be cumbersome and error-prone. Consider the following attributed text:
Creating this text with NSAttributedString
might look something like this:
let attributedString = NSMutableAttributedString(
string: "For the love of Swift ",
attributes: [.font: UIFont.systemFont(ofSize: 40)]
)
let strongAttributes: [NSAttributedString.Key: Any] = [
.foregroundColor: UIColor.swift,
.font: UIFont.boldSystemFont(ofSize: 40)
]
if let swiftLogo = UIImage(systemName: "swift") {
let swiftLogoAttachment = NSTextAttachment(image: swiftLogo)
let swiftLogoString = NSAttributedString(attachment: swiftLogoAttachment)
attributedString.append(swiftLogoString)
if let swiftRange = attributedString.string.range(of: "Swift") {
let strongRange = swiftRange.lowerBound ..< attributedString.string.endIndex
attributedString.addAttributes(
strongAttributes,
range: NSRange(strongRange, in: attributedString.string)
)
}
}
Creating the same text with RichStringKit
looks like this:
let richString = NSAttributedString {
"For the love of "
Group {
"Swift "
Attachment(systemName: "swift")
}
.foregroundColor(.swift)
.font(.boldSystemFont(ofSize: 40))
}
Full documentation coming soon
Until then, take a look at some example usage.
RichStringKit requires Swift 5.7
RichStringKit
can be added as a package dependency via Xcode or in your Package.swift
file.
- File > Swift Packages > Add Package Dependency
- Add
https://github.com/moyerr/RichStringKit.git
- Select "Up to Next Major" with
0.0.1
Add the following value to the dependencies
array:
.package(url: "https://github.com/moyerr/RichStringKit", from: "0.0.1")
Include it as a dependency for one or more of your targets:
.target(
name: "<your-target>",
dependencies: [
.product(name: "RichStringKit", package: "RichStringKit"),
]
)
The mechanism that enables RichStringKit's declarative DSL is the RichStringBuilder
, which is a @resultBuilder
similar to SwiftUI's ViewBuilder
. Closures, methods, and computed properties that return some RichString
can be decorated with the @RichStringBuilder
attribute to enable the DSL within them. For example:
@RichStringBuilder
var richText: some RichString {
"Underlined text"
.underlineStyle(.single)
" and "
"strikethrough text"
.strikethroughStyle(.single)
}
For more general information about Swift's result builder types, see the Result Builder section of The Swift Programming Language.
For convenience, RichStringKit
provides initializers on NSAttributedString
, AttributedString
, and SwiftUI's Text
view, all of which can either accept a RichString
or a @RichStringBuilder
closure. So you can start start using rich strings with your UI framework of choice.
Use the NSAttributedString
initializers when working with UIKit:
let label = UILabel()
label.attributedString = NSAttributedString {
"UIKit"
.font(.boldSystemFont(ofSize: 14))
" is "
"fun"
.kern(4)
.foregroundColor(.green)
}
Use the Text
or AttributedString
initializers when working with SwiftUI:
struct ContentView: View {
var body: some View {
Text {
"SwiftUI"
.font(.boldSystemFont(ofSize: 14))
" is also "
"fun"
.kern(4)
.foregroundColor(.green)
}
}
}
Style your text using the modifiers that RichStringKit
provides, or by defining your own modifiers using the RichStringModifier
protocol and the modifier(_:)
method.
RichStringKit
provides many modifiers for styling text, most of which map directly to attribute keys from NSAttributedString.Key
. The modifier methods that are currently available are:
.backgroundColor(_:)
.baselineOffset(_:)
.font(_:)
.foregroundColor(_:)
.kern(_:)
.link(_:)
.strikethroughStyle(_:)
.underlineColor(_:)
.underlineStyle(_:)
More attributes will be added soon (#7).
Adopt the RichStringModifier
protocol when you want to create a reusable modifier that you can apply to any RichString
. The example below combines modifiers to create a new modifier that you can use to create highlighted text with a yellow background and a dark foreground:
struct Highlight: RichStringModifier {
func body(_ content: Content) -> some RichString {
content
.foregroundColor(.black)
.backgroundColor(.yellow)
}
}
You can apply modifier(_:)
directly to a rich string, but a more common and idiomatic approach uses modifier(_:)
to define an extension on RichString
itself that incorporates the modifier:
extension RichString {
func highlighted() -> some RichString {
modifier(Highlight())
}
}
Then you can apply the highlight modifier to any rich string:
var body: some RichText {
"Draw the reader's attention to important information "
"by applying a highlight"
.highlighted()
}
The Format
type is provided to apply styling to formatted strings and their arguments independently.
Format("For the love of %@") {
Group {
"Swift "
Attachment(systemName: "swift")
}
.foregroundColor(.swift)
.font(.boldSystemFont(ofSize: 40))
}
.font(.systemFont(ofSize: 40))
Note:
RichStringKit
currently supports only the%@
format specifier. If your use case requires more advanced formatting, you can apply the formatting before inserting it into the final format string. For example:let receiptLine = AttributedString { Format("Iced latte: %@") { String(format: "%10.2f", -4.49) .foregroundColor(.red) } } // Iced latte: -4.49