Last active
May 26, 2023 12:36
-
-
Save pronebird/73edceb3daf1b266ef91a6bad6ea16ae to your computer and use it in GitHub Desktop.
Basic markdown support for attributed string
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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