Search code examples
iosswiftuitextviewnsattributedstring

NSAttributedString bullet list issues


I am trying to create a bullet list using NSAttributedString and UITextView. And, here is what I was able to achieve so far:enter image description here

As one can see, there is a small "shift" between two lines. Here is a fragment of code which I use to build attributed string:

func add(bulletList strings: [String],
         indentation: CGFloat = 15,
         lineSpacing: CGFloat = 3,
         paragraphSpacing: CGFloat = 10) {

    func createParagraphAttirbute() -> NSParagraphStyle {
        var paragraphStyle: NSMutableParagraphStyle
        let nonOptions = NSDictionary() as! [NSTextTab.OptionKey: Any]

        paragraphStyle = NSParagraphStyle.default.mutableCopy() as! NSMutableParagraphStyle
        paragraphStyle.tabStops = [
            NSTextTab(textAlignment: .left, location: indentation, options: nonOptions)]
        paragraphStyle.defaultTabInterval = indentation
        paragraphStyle.firstLineHeadIndent = 0
        paragraphStyle.lineSpacing = lineSpacing
        paragraphStyle.paragraphSpacing = paragraphSpacing
        paragraphStyle.headIndent = indentation
        return paragraphStyle
    }

    var buffer = NSMutableAttributedString.init()

    for string in strings {
        let formattedString = "\u{2022} \(string)\n"
        let attributedString = NSMutableAttributedString(string: formattedString)
        let paragraphStyle = createParagraphAttirbute()

        attributedString.addAttributes(
            [NSAttributedStringKey.paragraphStyle : paragraphStyle],
            range: NSMakeRange(0, attributedString.length))

        attributedString.addAttributes(
            textAttributes,
            range: NSMakeRange(0, attributedString.length))

        let string:NSString = NSString(string: formattedString)
        let rangeForBullet:NSRange = string.range(of: bulletPoint)
        attributedString.addAttributes(bulletAttirbutes, range: rangeForBullet)
        buffer.append(attributedString)
    }
}

Do you think something is wrong with selected paragraph parameters? Because the code does almost what is expected, excluding this gap.

Update 1

Following @the4kman advice, I've changed provided code like this:

paragraphStyle.firstLineHeadIndent = indentation

But now I have all lines aligned with each other, including the bullet point:

enter image description here

Update 2

Ok, the solution was quite simple - replace space with tab. See updated code below.


Solution

  • @the4kman, @Krunal, Thank you for your responses! The solution was even more simple. Replacing space symbol with \t in let formattedString = "\u{2022} \(string)\n gives you valid indentation.

    For completeness, the full solution code is (just replacing one char):

    func add(bulletList strings: [String],
             font: UIFont,
             indentation: CGFloat = 15,
             lineSpacing: CGFloat = 3,
             paragraphSpacing: CGFloat = 10,
             textColor: UIColor = .black,
             bulletColor: UIColor = .red) -> NSAttributedString {
    
        func createParagraphAttirbute() -> NSParagraphStyle {
            var paragraphStyle: NSMutableParagraphStyle
            let nonOptions = NSDictionary() as! [NSTextTab.OptionKey: Any]
    
            paragraphStyle = NSParagraphStyle.default.mutableCopy() as! NSMutableParagraphStyle
            paragraphStyle.tabStops = [
                NSTextTab(textAlignment: .left, location: indentation, options: nonOptions)]
            paragraphStyle.defaultTabInterval = indentation
            paragraphStyle.firstLineHeadIndent = 0
            paragraphStyle.lineSpacing = lineSpacing
            paragraphStyle.paragraphSpacing = paragraphSpacing
            paragraphStyle.headIndent = indentation
            return paragraphStyle
        }
    
        let bulletPoint = "\u{2022}"
        let textAttributes: [NSAttributedStringKey: Any] = [.font: font, .foregroundColor: textColor]
        let bulletAttributes: [NSAttributedStringKey: Any] = [.font: font, .foregroundColor: bulletColor]
        let buffer = NSMutableAttributedString.init()
    
        for string in strings {
            let formattedString = "\(bulletPoint)\t\(string)\n"
            let attributedString = NSMutableAttributedString(string: formattedString)
            let paragraphStyle = createParagraphAttirbute()
    
            attributedString.addAttributes(
                [NSAttributedStringKey.paragraphStyle : paragraphStyle],
                range: NSMakeRange(0, attributedString.length))
    
            attributedString.addAttributes(
                textAttributes,
                range: NSMakeRange(0, attributedString.length))
    
            let string:NSString = NSString(string: formattedString)
            let rangeForBullet:NSRange = string.range(of: bulletPoint)
            attributedString.addAttributes(bulletAttributes, range: rangeForBullet)
            buffer.append(attributedString)
        }
    
        return buffer
    }