Skip to content

Instantly share code, notes, and snippets.

@pronebird
Last active May 26, 2023 12:36
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save pronebird/73edceb3daf1b266ef91a6bad6ea16ae to your computer and use it in GitHub Desktop.
Save pronebird/73edceb3daf1b266ef91a6bad6ea16ae to your computer and use it in GitHub Desktop.
Basic markdown support for attributed string
import UIKit
import PlaygroundSupport
extension NSAttributedString {
enum MarkdownElement {
case paragraph, bold
}
struct MarkdownStylingOptions {
var font: UIFont
var paragraphStyle: NSParagraphStyle = .default
var boldFont: UIFont {
let fontDescriptor = font.fontDescriptor.withSymbolicTraits(.traitBold) ?? font.fontDescriptor
return UIFont(descriptor: fontDescriptor, size: font.pointSize)
}
}
convenience init(
markdownString: String,
options: MarkdownStylingOptions,
applyEffect: ((MarkdownElement, String) -> [NSAttributedString.Key: Any])? = nil
) {
let attributedString = NSMutableAttributedString()
for paragraph in markdownString.split(separator: "\n\n") {
let attributedParagraph = NSMutableAttributedString()
// Replace \n with \u2028 to prevent attributed string from picking up single line breaks as paragraphs.
let components = paragraph.replacingOccurrences(of: "\n", with: "\u{2028}")
.components(separatedBy: "**")
for (index, string) in components.enumerated() {
var attributes: [NSAttributedString.Key: Any] = [:]
if index % 2 == 0 {
attributes[.font] = options.font
} else {
attributes[.font] = options.boldFont
attributes.merge(applyEffect?(.bold, string) ?? [:], uniquingKeysWith: { $1 })
}
attributedParagraph.append(NSAttributedString(string: string, attributes: attributes))
}
attributedParagraph.addAttributes(
applyEffect?(.paragraph, attributedParagraph.string) ?? [:],
range: NSRange(location: 0, length: attributedParagraph.length)
)
// Add single line break to form a paragraph.
attributedParagraph.append(NSAttributedString(string: "\n"))
attributedString.append(attributedParagraph)
}
attributedString.addAttribute(
.paragraphStyle,
value: options.paragraphStyle,
range: NSRange(location: 0, length: attributedString.length)
)
self.init(attributedString: attributedString)
}
}
var paragraphStyle = NSParagraphStyle.default.mutableCopy() as! NSMutableParagraphStyle
paragraphStyle.paragraphSpacing = 20
let attributedString = NSAttributedString(
markdownString: """
Paragraph **A** goes here.
Paragraph **B** goes here.
Paragraph **C** goes here and **that's it**.
""",
options: .init(
font: .systemFont(ofSize: 17),
paragraphStyle: paragraphStyle
),
applyEffect: { element, string in
print("Apply style to \(element) with source string: \(string)")
return [:]
}
)
let textLabel = UILabel(frame: CGRect(x: 0, y: 0, width: 320, height: 480))
textLabel.numberOfLines = 0
textLabel.attributedText = attributedString
PlaygroundPage.current.liveView = textLabel
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment