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:
RichStringKitcurrently 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