iosswiftnsattributedstringnstexttabnsmutableparagraphstyle

Unable to render an NSAttributedString as a 2 column tabbed bullet list in a PDF


I am constructing a large string that is output into a PDF file, but right now, I'd like to have a 2 column, bulleted list in my document. However, I have yet to figure out the correct settings that will allow me to get the desired tabbing effect.

Currently, I am testing the following code:

let mutableString = NSMutableAttributedString()
let words = ["this", "is", "really", "getting", "old"]

let paragraphStyle = NSMutableParagraphStyle()
var tabStops = [NSTextTab]()
let tabInterval: CGFloat = 250.0
for index in 0..<12 {
    tabStops.append(NSTextTab(textAlignment: .left,
                              location: tabInterval * CGFloat(index),
                              options: [:]))
}
paragraphStyle.tabStops = tabStops

for index in 0..<words.count {
    if index != 0 && index % 2 == 0 {
        mutableString.append(NSAttributedString(string: "\n"))
    }
    if index % 2 == 1 {
        let attributedText = NSAttributedString(string: "\t", attributes: [NSAttributedString.Key.paragraphStyle: paragraphStyle])
        mutableString.append(attributedText)
    }
    let word = words[index]
    let attributedString = NSMutableAttributedString(string: "\u{2022}  \(word)",
        attributes: [:])
    mutableString.append(attributedString)
}

When I feed this into my PDF generator, it produces the following result:

enter image description here

Ultimately, I want "is" and "getting" to be aligned with the middle of the document, so that I can accommodate much larger words.


Solution

  • It turns out that I was in the ballpark, but definitely not close.

    The following provides the desired split column effect:

    let paragraphStyle = NSMutableParagraphStyle()
    paragraphStyle.tabStops = [
        // 274 would be the midpoint of my document
        NSTextTab(textAlignment: .left, location: 274, options: [:])
    ]
    
    let string = "\u{2022} This\t\u{2022} is\n\u{2022} getting\t\u{2022} really\n\u{2022} old"
    
    let attributedString = NSAttributedString(
        string: string,
        attributes: [NSAttributedString.Key.paragraphStyle: paragraphStyle]
    )
    

    enter image description here

    For bonus points, should you want to have multiple columns in your document, the following will accomplish this (pardon my crude formatting):

    let paragraphStyle = NSMutableParagraphStyle()
    paragraphStyle.tabStops = [
        NSTextTab(textAlignment: .left, location: 100, options: [:]),
        NSTextTab(textAlignment: .left, location: 300, options: [:])
    ]
    
    let string = "\u{2022} This\t\u{2022} is\t\u{2022} getting\n\u{2022} really\t\u{2022} old"
    
    let attributedString = NSAttributedString(
        string: string,
        attributes: [NSAttributedString.Key.paragraphStyle: paragraphStyle]
    )
    

    And will look like this:

    enter image description here

    What is going on here?

    So, what I learned here is that the tabStops tells iOS what location within the line to place the tab:

    1. The first tab will go to position 100
    2. The second tab will go to position 300
    3. A third tab will wrap around the document and go to position 100 as well

    Regarding tabbing, if you assign a tab with location 0 in the first index, then tabbing to a newline will get it aligned with the left edge.

    As to what fixed the issue for me. I was relying on an approach where each component of the string was added as it was encountered. However, this string would fail to format properly. Instead, by merging everything into a single string and applying the attributes seen in my working code, I was able to get it to align properly.

    I also tested using the individual components as seen in my question, but with the paragraph style attributes applied as well, and that resulted in a working solution as well.

    Based on this, it appears that my mistake was to mix strings that had, and did not have, the desired tabbing behavior.