Skip to content

Instantly share code, notes, and snippets.

@JasonCanCode
Last active May 13, 2021 20:49
Show Gist options
  • Save JasonCanCode/c890b280abb20ab5c15a3e498c5daba6 to your computer and use it in GitHub Desktop.
Save JasonCanCode/c890b280abb20ab5c15a3e498c5daba6 to your computer and use it in GitHub Desktop.
Extending NSAttributedString with a convenient way to generate text with mixed attributes
import Foundation
extension NSAttributedString {
struct FontTypes: OptionSet {
static let `default` = FontTypes(rawValue: 1 << 0)
static let bold = FontTypes(rawValue: 1 << 1)
static let italic = FontTypes(rawValue: 1 << 2)
static let underlined = FontTypes(rawValue: 1 << 3)
let rawValue: Int8
}
/**
Generates an Attributed String by iterating through an array of strings with associated FontTypes.
Example use:
thoughtBubbleLabel.attributedText = NSAttributedString.fromComponents([
( "\"What a handy extension,\" ", .italic ),
( "he thought to himself. ", .default ),
( "\n", .default ),
( "Indeed. ", .bold ),
( "It was.", [.underlined, .bold] ),
])
Things to consider:
- When providing FontTypes, `[]` and `.default` produce the same results.
- When supplying a font, you can technically provide a bold font. Doing so would mean `[]`, `.default`, and `.bold` produce the same results.
- parameter components: A tuple array of strings with associated FontTypes.
- parameter font: Provide a font if you don't want to use the default font.
*/
static func fromComponents(_ components: [(String, FontTypes)], font: UIFont? = nil) -> NSAttributedString {
let font = font ?? .size7
let attrString = NSMutableAttributedString()
for component in components {
let (text, types) = component
var attr: [NSAttributedString.Key: Any] = [:]
var traits: UIFontDescriptor.SymbolicTraits = []
if font.fontDescriptor.symbolicTraits.contains(.traitBold)
|| types.contains(.bold) {
traits.insert(.traitBold)
}
if font.fontDescriptor.symbolicTraits.contains(.traitItalic)
|| types.contains(.italic) {
traits.insert(.traitItalic)
}
if let desc = font.fontDescriptor.withSymbolicTraits(traits) {
attr[.font] = UIFont(descriptor: desc, size: font.pointSize)
} else {
attr[.font] = font
}
if types.contains(.underlined) {
attr[.underlineStyle] = NSUnderlineStyle.single.rawValue
}
attrString.append(NSAttributedString(string: text, attributes: attr))
}
return attrString
}
}
extension XCTestCase {
/// Verify that a range of text within an attributed string has the correct attributes
/// - Parameters:
/// - attrText: Full attributed string to be inspected at provided range
/// - expectedRange: Range within `attrText` to verify
/// - link: Path string of an embedded link if one should be present
/// - fontTypes: What font types should be present if at all
func verifySubAttributes(
attrText: NSAttributedString,
expectedRange: NSRange,
link: String? = nil,
fontTypes: NSAttributedString.FontTypes = []
) {
var range = NSRange(location: 0, length: attrText.length)
let attributes = attrText.attributes(at: expectedRange.location, effectiveRange: &range)
XCTAssertEqual(range, expectedRange)
XCTAssertEqual(attributes[.link] as? String, link)
if fontTypes.contains(.underlined) {
XCTAssertEqual(attributes[.underlineStyle] as? Int, NSUnderlineStyle.single.rawValue)
}
guard let font = attributes[.font] as? UIFont else {
return
}
if fontTypes.contains(.bold) {
XCTAssertTrue(font.fontDescriptor.symbolicTraits.contains(.traitBold))
}
if fontTypes.contains(.italic) {
XCTAssertTrue(font.fontDescriptor.symbolicTraits.contains(.traitItalic))
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment