iosswiftuikitmarkdown

Custom styling in NSMutableAttributedString with markdown


I'm trying to use the markdown support in iOS with NSMutableAttributedString and UITextView. It works in general, but when I call text.setAttributes(attributes, range: fullRange) to apply some custom styling, the markdown information gets lost and everything is displayed in plain text.

Does setting the attributes override the ones set by the markdown initialiser? I noticed that this initialiser does not take any attributes, so I'm setting those later.

Maybe that's not how it's supposed to be used, but how can I apply custom styling (text color, font, line spacing) to a attributed markdown string without losing the markdown?

let text = try NSMutableAttributedString(
            markdown: markdownText,
            options: AttributedString.MarkdownParsingOptions(interpretedSyntax: .inlineOnlyPreservingWhitespace))

let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.lineSpacing = lineSpacing

let attributes: [NSAttributedString.Key : Any] = [
            .font: font,
            .foregroundColor: textColor,
            .paragraphStyle: paragraphStyle
        ]

let fullRange = NSRange(location: 0, length: text.length)
text.setAttributes(attributes, range: fullRange)```

Solution

  • setAttributes is designed to override all the existing attributes. From the docs:

    These new attributes replace any attributes previously associated with the characters in aRange.

    You should use addAttributes instead.

    Also consider using the Swift AttributedString APIs when manipulating an attributed string, and only convert it to an NSAttributedString at the very last minute.

    var text = try AttributedString(
        markdown: markdownText,
        options: .init(interpretedSyntax: .inlineOnlyPreservingWhitespace))
    
    let paragraphStyle = NSMutableParagraphStyle()
    paragraphStyle.lineSpacing = 5
    
    let attributes = AttributeContainer([
        .font: font,
        .foregroundColor: color,
        .paragraphStyle: paragraphStyle
    ])
    
    text.mergeAttributes(attributes, mergePolicy: .keepCurrent)
    
    textView.attributedText = .init(text)
    

    Here I have used the .keepCurrent merge policy so that your own attributes won't overwrite the ones added by the markdown. You can also choose to use .keepNew instead. (I don't think the markdown parser will ever add font/foregroundColor/paragraphStyle attributes though)