swiftswiftuiattributedstring

How to display NSMutableAttributedString in Text view in SwiftUI?


Given a string, The * quick * brown * fox * jumps * over * the * lazy * dog, I'm applying font modifiers as follows in order to style all occurrences of asterisk differently than the rest of the text.

func customDesc(_ text: String) -> NSMutableAttributedString {
        let asteriskTextRange = (text as NSString).range(of: "*")
        let attributedString = NSMutableAttributedString(string: text, attributes: [NSAttributedString.Key.font : UIFont.systemFont(ofSize: 15, weight: .semibold)])
        attributedString.setAttributes([NSAttributedString.Key.font : UIFont.systemFont(ofSize: 20, weight: .heavy)], range: asteriskTextRange)
        return attributedString
    }

It seems range(of:) will only pick the first occurrence so how do update all occurrences and display the resultant text in a Text view? I could not find any initialisers that accept NSMutableAttributedString. Any help is appreciated.


Solution

  • Texts take the Swift AttributedStrings instead of the NSAttributedStrings from Objective-C. You can convert to AttributedString simply by using one of its initialisers.

    Text(AttributedString(customDesc(...)))
    

    However, I would suggest that customDesc return a AttributedString directly, as it is more Swifty. You can use ranges(of:) to find all the ranges of a particular substring.

    func customDesc(_ text: String) -> AttributedString {
        var attributedString = AttributedString(text)
        attributedString.font = Font.system(size: 15, weight: .semibold)
    
        for range in text.ranges(of: "*") {
            // this converts Range<String.Index> to Range<AttributedString.Index>
            if let lowerBound = AttributedString.Index(range.lowerBound, within: attributedString),
               let upperBound = AttributedString.Index(range.upperBound, within: attributedString) {
                let attrRange = lowerBound..<upperBound
                attributedString[attrRange].font = Font.system(size: 20, weight: .heavy)
            }
        }
        return attributedString
    }
    
    Text(customDesc(...))