Top Related Projects
Quick Overview
BonMot is a Swift library that provides a set of extensions and utilities for working with attributed strings in iOS and macOS applications. It aims to simplify the process of creating and manipulating rich text, making it easier to work with complex text formatting and layout.
Pros
- Simplifies Attributed String Creation: BonMot provides a fluent API for creating and modifying attributed strings, reducing the boilerplate code required.
- Supports Advanced Formatting: The library offers a wide range of formatting options, including font styles, colors, paragraph styles, and more.
- Cross-platform Compatibility: BonMot works on both iOS and macOS, allowing developers to share code between platforms.
- Active Development and Community: The project is actively maintained, with regular updates and a responsive community.
Cons
- Limited Documentation: While the library is well-designed, the documentation could be more comprehensive, making it harder for new users to get started.
- Potential Performance Impact: Depending on the complexity of the attributed strings being used, there may be a slight performance impact, which could be a concern for some use cases.
- Dependency on Swift: BonMot is written in Swift, so it may not be suitable for projects that are primarily written in Objective-C.
- Lack of Automatic Migration: When upgrading to a new version of the library, developers may need to manually update their code to accommodate any breaking changes.
Code Examples
Here are a few examples of how to use BonMot:
- Creating an Attributed String:
let attributedString = "Hello, world!".styled(
with: .font(.systemFont(ofSize: 18)),
.foregroundColor(.blue)
)
- Applying Paragraph Styles:
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.alignment = .center
paragraphStyle.lineSpacing = 8
let attributedString = "Centered text with extra line spacing".styled(
with: .paragraphStyle(paragraphStyle)
)
- Combining Multiple Styles:
let attributedString = "Bold and Italic".styled(
with: .font(.boldSystemFont(ofSize: 16)),
.italic
)
- Applying Styles to a Substring:
let attributedString = "This is a test".styled(
with: .font(.systemFont(ofSize: 14)),
.foregroundColor(.gray)
)
attributedString.addAttribute(.font, value: UIFont.boldSystemFont(ofSize: 14), range: NSRange(location: 8, length: 4))
Getting Started
To get started with BonMot, you can follow these steps:
-
Add the BonMot dependency to your project using a package manager like CocoaPods or Carthage.
-
Import the BonMot library in your Swift file:
import BonMot
- Start using the library's APIs to create and manipulate attributed strings:
let attributedString = "Hello, BonMot!".styled(
with: .font(.systemFont(ofSize: 18)),
.foregroundColor(.blue)
)
// Use the attributed string in your UI components
label.attributedText = attributedString
- Explore the library's documentation and examples to learn more about the available features and how to use them in your project.
Competitor Comparisons
A tool for defining design systems and using them to generate cross-platform UI code, Sketch files, and other artifacts.
Pros of Lona
- Lona provides a visual design tool that allows designers to create and maintain UI components, which can be exported as code for various platforms.
- The project has a strong focus on cross-platform support, with the ability to generate code for iOS, Android, and web.
- Lona has a well-documented API and a growing community of contributors.
Cons of Lona
- Lona is a relatively new project, and it may not have the same level of maturity and feature set as some other UI libraries.
- The learning curve for Lona may be steeper than for some other UI libraries, as it requires both design and development skills.
- Lona's integration with existing development workflows may not be as seamless as some other UI libraries.
Code Comparison
Lona (Swift):
import UIKit
class MyView: UIView {
let label = UILabel()
override init(frame: CGRect) {
super.init(frame: frame)
setupUI()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
setupUI()
}
private func setupUI() {
addSubview(label)
label.text = "Hello, Lona!"
label.translatesAutoresizingMaskIntoConstraints = false
label.centerXAnchor.constraint(equalTo: centerXAnchor).isActive = true
label.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true
}
}
BonMot (Swift):
import UIKit
class MyView: UIView {
let label = UILabel()
override init(frame: CGRect) {
super.init(frame: frame)
setupUI()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
setupUI()
}
private func setupUI() {
addSubview(label)
label.text = "Hello, BonMot!"
label.translatesAutoresizingMaskIntoConstraints = false
label.centerXAnchor.constraint(equalTo: centerXAnchor).isActive = true
label.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true
}
}
A tool to enforce Swift style and conventions.
Pros of SwiftLint
- SwiftLint is a widely-used and well-maintained open-source project, with a large and active community.
- It provides a comprehensive set of rules and guidelines for Swift code style and best practices, helping to enforce consistency and improve code quality.
- SwiftLint is highly customizable, allowing developers to enable or disable specific rules, and even create their own custom rules.
Cons of SwiftLint
- SwiftLint can be more complex to set up and configure than BonMot, as it requires more configuration options and settings.
- The large number of rules and guidelines provided by SwiftLint can be overwhelming for some developers, especially those new to the project.
- SwiftLint may have a higher performance impact on larger projects, as it needs to analyze the entire codebase for potential issues.
Code Comparison
BonMot:
let attributedString = "Hello, world!".styled(
with: .font(.systemFont(ofSize: 24)),
.foregroundColor(.red)
)
SwiftLint:
let attributedString = "Hello, world!".styled(
with: .font(.systemFont(ofSize: 24)),
.foregroundColor(.red)
)
As you can see, the code for both BonMot and SwiftLint is very similar, as they both provide a simple and intuitive API for creating attributed strings.
A simple, decentralized dependency manager for Cocoa
Pros of Carthage
- Carthage is a decentralized dependency manager, allowing for more flexibility in managing dependencies.
- Carthage provides a simple and straightforward approach to dependency management, with a focus on ease of use.
- Carthage supports a wide range of platforms, including iOS, macOS, tvOS, and watchOS.
Cons of Carthage
- Carthage may have a steeper learning curve compared to BonMot, especially for developers new to dependency management.
- Carthage's decentralized nature can make it more challenging to manage and update dependencies across multiple projects.
- Carthage may have a smaller ecosystem compared to other dependency managers, with fewer available libraries and resources.
Code Comparison
Carthage:
github "Carthage/Carthage"
BonMot:
pod 'BonMot'
Convert designs to code with AI
Introducing Visual Copilot: A new AI model to turn Figma designs to high quality code using your components.
Try Visual CopilotREADME
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 aStringStyle
. You will typically interact with these, rather than constructingStringStyle
s 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 multipleComposable
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 theSpecial
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:
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:
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:
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
:
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.
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";
- Use the inspectable properties of common UIKit elements as in the Interface Builder section.
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 BONChain
s 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.
Top Related Projects
Convert designs to code with AI
Introducing Visual Copilot: A new AI model to turn Figma designs to high quality code using your components.
Try Visual Copilot