• Stars
    star
    3,538
  • Rank 12,552 (Top 0.3 %)
  • Language
    Swift
  • License
    MIT License
  • Created over 9 years ago
  • Updated 5 months ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Beautiful, easy attributed strings in Swift

BonMot Logo

Swift 5.0 CircleCI Version License Platform Carthage compatible Swift Package Manager compatible codecov

BonMot (pronounced Bon Mo, French for good word) is a Swift attributed string library. It abstracts away the complexities of the iOS, macOS, tvOS, and watchOS typography tools, freeing you to focus on making your text beautiful.

To run the example project, run pod try BonMot, or clone the repo, open BonMot.xcodeproj, and run the Example-iOS target.

AttributedString

BonMot has been sherlocked! If you're targeting iOS 15 or higher, you may want to check out AttributedString instead. If you're an existing user of BonMot using Xcode 13, you may want to add the following typealias somewhere in your project to avoid a conflict with Foundation.StringStyle:

typealias StringStyle = BonMot.StringStyle

Usage

In any Swift file where you want to use BonMot, simply import BonMot.

Basics

Use a StringStyle to specify the style of your attributed string. Then, use the styled(with:) method on String to get your attributed string:

let quote = """
    I used to love correcting people’s grammar until \
    I realized what I loved more was having friends.
    -Mara Wilson
    """

let style = StringStyle(
    .font(UIFont(name: "AmericanTypewriter", size: 17)!),
    .lineHeightMultiple(1.8)
)

let attributedString = quote.styled(with: style)

// You can also get the style’s attributes dictionary
// if you’re using an API that requires it.
let attributes = style.attributes

Glossary

These are the types with which you will most commonly interact when using BonMot to build attributed strings.

  • StringStyle: a collection of attributes which can be used to style a string. These include basics, like font and color, and more advanced settings like paragraph controls and OpenType features. To get a good idea of the full set of features that BonMot supports, look at the interface for this struct.
  • StringStyle.Part: an enum which can be used to concisely construct a StringStyle. You will typically interact with these, rather than constructing StringStyles attribute by attribute.
  • Composable: a protocol defining any type that knows how to append itself to an attributed string. BonMot provides functions, such as the one in this example, to join together multiple Composable values.
  • NamedStyles: use this to register custom, reusable styles in a global namespace.
  • Special: a utility to include special, ambiguous, and non-printing characters in your strings without making your code unreadable.

Style Inheritance

Styles can inherit from each other, which lets you create multiple styles that share common attributes:

let baseStyle = StringStyle(
    .lineHeightMultiple(1.2),
    .font(UIFont.systemFont(ofSize: 17))
)

let redStyle = baseStyle.byAdding(.color(.red))
let blueStyle = baseStyle.byAdding(.color(.blue))

let redBirdString = "bird".styled(with: redStyle)
let blueBirdString = "bird".styled(with: blueStyle)

Styling Parts of Strings with XML

Are you trying to style just part of a string, perhaps even a localized string which is different depending on the locale of the app? No problem! BonMot can turn custom XML tags and simple HTML into attributed strings:

// This would typically be a localized string
let string = "one fish, two fish, <red>red fish</red>,<BON:noBreakSpace/><blue>blue fish</blue>"

let redStyle = StringStyle(.color(.red))
let blueStyle = StringStyle(.color(.blue))

let fishStyle = StringStyle(
    .font(UIFont.systemFont(ofSize: 17)),
    .lineHeightMultiple(1.8),
    .color(.darkGray),
    .xmlRules([
        .style("red", redStyle),
        .style("blue", blueStyle),
        ])
)

let attributedString = string.styled(with: fishStyle)

This will produce:

Note the use of <BON:noBreakSpace/> to specify a special character within the string. This is a great way to add special characters to localized strings, since localizers might not know to look for special characters, and many of them are invisible or ambiguous when viewed in a normal text editor. You can use any characters in the Special enum, or use <BON:unicode value='A1338'/> or &#a1338;

XML Parsing with Error Handling

If the above method encounters invalid XML, the resulting string will be the entire original string, tags and all. If you are parsing XML that is out of your control, e.g. variable content from a server, you may want to use this alternate parsing mechanism, which allows you to handle errors encountered while parsing:

let rules: [XMLStyleRule] = [
    .style("strong", strongStyle),
    .style("em", emStyle),
]

let xml = // some XML from a server

do {
    let attrString = try NSAttributedString.composed(ofXML: xml, rules: rules)
}
catch {
    // Handle errors encountered by Foundation's XMLParser,
    // which is used by BonMot to parse XML.
}

Image Attachments

BonMot uses NSTextAttachment to embed images in strings. You can use BonMot’s NSAttributedString.composed(of:) API to chain images and text together in the same string:

let someImage = ... // some UIImage or NSImage

let attributedString = NSAttributedString.composed(of: [
    someImage.styled(with: .baselineOffset(-4)), // shift vertically if needed
    Special.noBreakSpace, // a non-breaking space between image and text
    "label with icon", // raw or attributed string
    ])

Note the use of the Special type, which gives you easy access to Unicode characters that are commonly used in UIs, such as spaces, dashes, and non-printing characters.

Outputs:

If you need to wrap multiple lines of text after an image, use Tab.headIndent(...) to align the whole paragraph after the image:

let attributedString = NSAttributedString.composed(of: [
    someImage.styled(with: .baselineOffset(-4.0)), // shift vertically if needed
    Tab.headIndent(10), // horizontal space between image and text
    "This is some text that goes on and on and spans multiple lines, and it all ends up left-aligned",
    ])

Outputs:

Dynamic Type

You can easily make any attributed string generated by BonMot respond to the system text size control. Simply add .adapt to any style declaration, and specify whether you want the style to scale like a .control or like .body text.

let style = StringStyle(
    .adapt(.control)
    // other style parts can go here as needed
)

someLabel.attributedText = "Label".styled(with: style).adapted(to: traitCollection)

If you want an attributed string to adapt to the current content size category, when setting it on a UI element, use .adapted(to: traitCollection) as in the above example.

Responding to Content Size Category Changes

If you call UIApplication.shared.enableAdaptiveContentSizeMonitor() at some point in your app setup code, BonMot will update common UI elements as the preferred content size category changes. You can opt your custom controls into automatic updating by conforming them to the AdaptableTextContainer protocol.

If you want more manual control over the adaptation process and are targeting iOS 10+, skip enabling the adaptive content size monitor, and call .adapted(to: traitCollection) inside traitCollectionDidChange(_:). iOS 10 introduced a preferredContentSizeCategory property on UITraitCollection.

Scaling Behaviors

The .control and .body behaviors both scale the same, except that when enabling the "Larger Dynamic Type" accessibility setting, .body grows unbounded. Here is a graph of the default behaviors of the system Dynamic Type styles:

Graph of iOS Dynamic Type scaling behavior, showing that most text tops out at the XXL size, but Body, Large Title, and Title 1 text keeps growing all the way up through AccessibilityXXXL

Storyboard and XIB Integration

You can register global named styles, and use them in Storyboards and XIBs via IBInspectable:

let style = StringStyle(
    .font(UIFont(name: "Avenir-Roman", size: 24)!),
    .color(.red),
    .underline(.styleSingle, .red)
)
NamedStyles.shared.registerStyle(forName: "MyHeadline", style: style)

You can then use MyHeadline in Interface Builder’s Attributes Inspector on common UIKit controls such as buttons and labels:

Editing the Bon Mot Style Name attribute in the Attributes Inspector of Interface Builder, and setting the value to MyHeadline

These same named styles will also be picked up if they are used as tag names in parsed XML.

Debugging & Testing Helpers

Use bonMotDebugString and bonMotDebugAttributedString to print out a version of any attributed string with all of the special characters and image attachments expanded into human-readable XML:

NSAttributedString.composed(of: [
	image,
	Special.noBreakSpace,
	"Monday",
	Special.enDash,
	"Friday"
	]).bonMotDebugString

// Result:
// <BON:image size='36x36'/><BON:noBreakSpace/>Monday<BON:enDash/>Friday

You can use XML Rules to re-parse the resulting string (except for images) back into an attributed string. You can also save the output of bonMotDebugString and use it to validate attributed strings in unit tests.

Vertical Text Alignment

UIKit lets you align labels by top, bottom, or baseline. BonMot includes TextAlignmentConstraint, a layout constraint subclass that lets you align labels by cap height and x-height. For some fonts, this is essential to convey the designer’s intention:

Illustration of different methods of aligning text vertically

TextAlignmentConstraint works with any views that expose a font property. It uses Key-Value Observing to watch for changes to the font property, and adjust its internal measurements accordingly. This is ideal for use with Dynamic Type: if the user changes the font size of the app, TextAlignmentConstraint will update. You can also use it to align a label with a plain view, as illustrated by the red dotted line views in the example above.

Warning: TextAlignmentConstraint holds strong references to its firstItem and secondItem properties. Make sure that a view that is constrained by this constraint does not also hold a strong reference to said constraint, because it will cause a retain cycle.

You can use TextAlignmentConstraint programmatically or in Interface Builder. In code, use it like this:

TextAlignmentConstraint.with(
    item: someLabel,
    attribute: .capHeight,
    relatedBy: .equal,
    toItem: someOtherLabel,
    attribute: .capHeight).isActive = true

In Interface Builder, start by constraining two views to each other with a top constraint. Select the constraint, and in the Identity Inspector, change the class to TextAlignmentConstraint:

setting the class in the Identity Inspector

Next, switch to the Attributes Inspector. TextAlignmentConstraint exposes two text fields through IBInspectables. Type in the attributes you want to align. You will get a run-time error if you enter an invalid value.

setting the alignment attributes in the Attributes Inspector

The layout won’t change in Interface Builder (IBDesignable is not supported for constraint subclasses), but it will work when you run your code.

Note: some of the possible alignment values are not supported in all configurations. Check out Issue #37 for updates.

Objective-C Compatibility

BonMot is written in Swift, but you have a few options if you must use it in an Objective-C code base:

  • For legacy Objective-C code bases, consider using BonMot 3.2, the last major release to be written in Objective-C. Make sure you reference the ReadMe from that tagged release, since the syntax is different than BonMot 4.0 and later.
  • If you are mixing Objective-C and Swift, you can create named styles as in the Interface Builder section, and then access those styles in Objective-C:
UILabel *label = [[UILabel alloc] init];
label.bonMotStyleName = @"MyHeadline";

BonMot 3 → 4+ Migration Guide

BonMot 4 is a major update, but there are some common patterns that you can use to ease the transition. Note that this is mostly for Swift projects that were using BonMot 3. BonMot 4+ has only limited support for Objective-C, so please check that section before attempting to upgrade if you need to maintain Objective-C compatibility.

Separating Style from Content

BonMot 4 introduces the StringStyle struct, which encapsulates style information. When you apply a StringStyle to a plain String, the result is an NSAttributedString. This differs from BonMot 3, where a BONChain or BONText contained both style and string information. The decoupling of content from style follows in the footsteps of HTML/CSS, and makes it easier to test and reason about each component separately from the other.

Inline Styling

The changes required to support inline styling are minimal. It won’t be a completely mechanical process due to some renaming that took place in 4.0, but it should be fairly straightforward:

BonMot 3
let chain = BONChain()
   .color(myColor)
   .font(myFont)
   .figureSpacing(.Tabular)
   .alignment(.Center)
   .string(text)
label.attributedText = chain.attributedString
BonMot 4
label.attributedText = text.styled(
    with:
    .color(myColor),
    .font(myFont),
    .numberSpacing(.monospaced), // renamed in 4.0
    .alignment(.center)
)

Saved Styles

In BonMot 3, you may have stored BONChains for later use. You can accomplish the same thing with BonMot 4’s StringStyle, with one main difference: while a BONChain can contain a string, a StringStyle never does. It is applied to a string, producing an NSAttributedString:

BonMot 3
enum Constants {

    static let myChain = BONChain()
        .color(myColor)
        .font(myFont)
        .tagStyles([
            "bold": myBoldChain,
            ])

}

// and then, later:

let attrString = myChain.string("some string").attributedString
BonMot 4
enum Constants {

    static let myStyle = StringStyle(
        .color(myColor),
        .font(myFont),
        .xmlRules([
            .style("bold", myBoldStyle),
            ]))

}

// and then, later:

let attrString = "some string".styled(with: Constants.myStyle)

Installation

Swift Package Manager

BonMot is available through Swift Package Manager. To install it through Xcode, go to File -> Swift Packages -> Add Package Dependency... and paste the repository URL:

https://github.com/Rightpoint/BonMot.git

CocoaPods

BonMot is available through CocoaPods. To install it, simply add the following line to your Podfile:

pod 'BonMot'

Carthage

BonMot is also compatible with Carthage. To install it, simply add the following line to your Cartfile:

github "Rightpoint/BonMot"

Contributing

Issues and pull requests are welcome! Please ensure that you have the latest SwiftLint installed before committing and that there are no style warnings generated when building.

Contributors are expected to abide by the Contributor Covenant Code of Conduct.

Author

Zev Eisenberg: @ZevEisenberg

Logo by Jon Lopkin: @jonlopkin

License

BonMot is available under the MIT license. See the LICENSE file for more info.

More Repositories

1

RZTransitions

A library of custom iOS View Controller Animations and Interactions.
Objective-C
1,877
star
2

Anchorage

A collection of operators and utilities that simplify iOS layout code.
Swift
628
star
3

RZDataBinding

Lightweight KVO-based data binding options.
Objective-C
543
star
4

Eject

An eject button for Interface Builder to generate swift code
Swift
523
star
5

RZCellSizeManager

Dynamic size computation and caching for cells.
Objective-C
242
star
6

Swiftilities

A collection of useful Swift utilities.
Swift
184
star
7

BentoMap

Map Clustering for Swift.
Swift
182
star
8

AndroidDatabaseLibraryComparison

Java
148
star
9

RZBluetooth

Core Bluetooth helper library
Objective-C
136
star
10

Singleton

135
star
11

RZUtils

Rightpoint Commonly Used Tools
Objective-C
122
star
12

Parser

112
star
13

RZViewActions

A category on UIView that provides animation structure similar to SKAction from SpriteKit.
Objective-C
103
star
14

ios-template

A `cookiecutter` template for iOS projects
Swift
94
star
15

RaisinToast

A UIWindow subclass used to message information to the users of your app.
Objective-C
83
star
16

FRY

UIKit Integration Library
Objective-C
77
star
17

rust-universal-template

Template for creating Rust libraries with bindings to iOS, Android, WebAssembly and more
Shell
58
star
18

UniversalAdapter

A single adapter implementation for any scrolling view or ViewGroup.
Java
53
star
19

ARKit-CoreML

Utilities and examples for using CoreML in conjunction with ARKit
Swift
47
star
20

RZCollectionList

A framework for dealing with displaying data from Core Data and other sources in UITableViews.
Objective-C
44
star
21

FreshAir-Android

Java
41
star
22

RIGImageGallery

An image gallery view controller designed to work with the Raizlabs Interface Guidelines for iOS
Swift
40
star
23

CardParser

Credit Card Type Parsing for Swift
Swift
40
star
24

Raizlabs-Android-Style

The Raizlabs Android Style Guide
38
star
25

RZAndroidBaseUtils

Base Utility set for Android applications
Java
36
star
26

RZTouchID

Basic TouchID implementation
Objective-C
34
star
27

Griddle

Groovy
34
star
28

Raizlabs-Cocoa-Style

The Raizlabs iOS Style Guide
Objective-C
32
star
29

ViewHolderInflater

31
star
30

android-template

A `cookiecutter` template for Android projects
Kotlin
28
star
31

RZTweenSpirit

Piecewise tweening/animation library for iOS
Objective-C
25
star
32

DebugModule

Java
22
star
33

Shift

A library of custom iOS View Controller Animations and Interactions written in Swift.
Swift
22
star
34

RZVinyl

Stack management, ActiveRecord utilities, and seamless importing for Core Data
Objective-C
21
star
35

Broker

21
star
36

RZDebugMenu

Configurable debug menu for iOS apps
Objective-C
20
star
37

Raze

A supplemental graphics engine for apps
Objective-C
20
star
38

RZImport

Automatic importing of data from NSDictionary to Cocoa objects
Objective-C
18
star
39

Stackable

Supercharged UIStackViews for Swift
Swift
18
star
40

AndroidWebServiceManager

Java
17
star
41

RZIntrinsicContentSizeTextView

Objective-C
17
star
42

RZSafariKeychain

A simple implementation of Safari Keychain
Objective-C
17
star
43

RZSplitViewController

A custom iOS SplitViewController.
16
star
44

FrictionLess

A collection of UX-focused swift components for reducing friction in "user work".
Swift
15
star
45

BoardingPass

A navigation controller interactive pan to push and pop.
Swift
14
star
46

WebServiceManager

13
star
47

ViewState

Small library for lightweight UIView and UIViewController state management on iOS
Swift
12
star
48

Pourcast

JavaScript
12
star
49

SketchyCode

An experimental tool to generate Swift code from Sketch files
Swift
12
star
50

CoreUtils

Java
12
star
51

UniversalFontComponents

A library that allows you to set a custom font in xml for all your TextViews
Java
11
star
52

opencv-swift

OpenCV / Swift Package Manager / XCFramework / Podspec
Ruby
10
star
53

RZNumberPad

Never write a custom number pad from scratch again.
Objective-C
9
star
54

conference-room

Conference room management system
C#
9
star
55

CompoundComponents

Android sample project that shows how to use Compound Components to make reusable UI. Also shows examples for creating reusable UI with the <include> tag and with Fragments.
Java
9
star
56

Geode

Location management made easy.
Swift
8
star
57

RZPoseurWebView

RZPoseurWebView
7
star
58

RZAssert

Useful assertion macros from the fine folks at Raizlabs.
Objective-C
7
star
59

RZEffects

Apply shader effects to UIKit elements.
7
star
60

RZBuildScripts

Xcode command line build script for building/signing/packaging iOS builds
Ruby
7
star
61

ai-discipline-ollama-rag

Python
7
star
62

Actionable

A cleaner delegation pattern for iOS.
Swift
6
star
63

RZOpenGL

Raizlabs OpenGL Library
6
star
64

localizable

Swift command line utility to take a CSV input of strings and output localizable string formats for iOS/macOS and Android.
Swift
5
star
65

RZKeychain

Easy keychain manipulation in iOS
4
star
66

FreshAir

Objective-C
4
star
67

RZLogin

A Reusable Login View Controller.
4
star
68

sample-scrolling-controls-ios

Sample code for a blog post about controls in scroll views on iOS
Swift
4
star
69

RZArrayCandy

Some functional sugar for your NSArrays
4
star
70

RZCollectionTableView

A UICollectionView that behaves like a UITableView
4
star
71

RZMapView

Simple custom mapping for iOS
Objective-C
4
star
72

sample-smart-animated-deselection-ios

Sample code for a blog post on smarter animated deselection on iOS
Swift
4
star
73

sample-float-remapping-ios

Sample code for a blog post on float remapping on iOS
Swift
3
star
74

RZDataManager

An iOS framework for Importing and Managing Data in your App.
3
star
75

slog

A simple logging utility for Swift
Shell
3
star
76

Drafter

Drafter - A nice way to draft up some constraints programmatically
3
star
77

DialogFactory

3
star
78

ios-template-output

For running CircleCI on output of ios-template
Swift
3
star
79

example-xcframework-build

Shell
3
star
80

CtrlZ

3
star
81

RZRadioKit

2
star
82

jss-react-template

Sample JSS React project using the OpenWeather API
JavaScript
2
star
83

RZSpriteTools

A library for creating and managing SpriteKit objects.
2
star
84

viewmodel-inject

Utilizes Dagger to generate a ViewModelProvider.Factory
Kotlin
2
star
85

RZAffirm

Raizlabs Swift Assertion Library
Swift
2
star
86

DataHub

Java
2
star
87

RZSegmentViewController

A customizable View Controller Container that lets you switch between view controllers using a segment control.
2
star
88

RZRevealViewController

RZRevealViewController is a Basement Menu style Containment View Controller.
1
star
89

FlowStacks-iOS-15-Bug

Demonstrating SwiftUI navigation bug in FlowStacks on iOS 15
Swift
1
star
90

maven-releases

1
star
91

PeopleCards

Flashcard game to learn new hire names/faces
JavaScript
1
star
92

AirQualityDashboard-iOS

Air quality dashboard for our BLE AQI device
Swift
1
star
93

RZSequenceController

A view controller container that arranges child view controller in either a horizontal or vertical scrolling flow.
1
star
94

air_quality_bluetooth_le

Bluetooth Low Energy interface for SDS011 APM2.5 air quality sensor
Python
1
star
95

react-native-touch-sensor

Java
1
star
96

RPLogging

Log level settings for iOS, macOS, tvOS, & watchOS
Swift
1
star
97

CustomVisionTools

Useful utilities for training object detection models with Custom Vision.
Python
1
star
98

Azure-Search-Query-Builder

This is a library that uses expression tree parsing to build a parameters object for performing search, suggest, and autocomplete actions with the Azure Search .NET SDK.
C#
1
star