Skip to content

Latest commit

 

History

History
252 lines (187 loc) · 7.68 KB

README.md

File metadata and controls

252 lines (187 loc) · 7.68 KB

💰 RichStringKit 🔡


GitHub Workflow Status Updated

RichStringKit is a declarative DSL for building rich text in Swift.

Table of Contents

  1. Motivation 🧐
  2. Documentation 📖
  3. Installation 💻
  4. Usage 🔡

Motivation 🧐

NSAttributedString is a powerful tool for creating rich text, but using it can be cumbersome and error-prone. Consider the following attributed text:

Image of an attributed string that reads 'For the love of Swift' with the Swift programming language logo

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))
}

Documentation 📖

Full documentation coming soon

Until then, take a look at some example usage.

Installation 💻

RichStringKit requires Swift 5.7

Swift Package Manager 📦

RichStringKit can be added as a package dependency via Xcode or in your Package.swift file.

Xcode

  • File > Swift Packages > Add Package Dependency
  • Add https://github.com/moyerr/RichStringKit.git
  • Select "Up to Next Major" with 0.0.1

Package.swift

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"),
  ]
)

Usage 🔡

Rich String Result Builder

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.

Convenience Initializers

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)
        }
    }
}

Modifiers

Style your text using the modifiers that RichStringKit provides, or by defining your own modifiers using the RichStringModifier protocol and the modifier(_:) method.

Built-in Modifiers

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:

  1. .backgroundColor(_:)
  2. .baselineOffset(_:)
  3. .font(_:)
  4. .foregroundColor(_:)
  5. .kern(_:)
  6. .link(_:)
  7. .strikethroughStyle(_:)
  8. .underlineColor(_:)
  9. .underlineStyle(_:)

More attributes will be added soon (#7).

Combining Modifiers

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()
}

Advanced Usage

Formatted Strings

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