3

I am trying to use NSTextList to display a numeric list in a multi-line NSTextField but it is not working as intended.

The sample code used is:

- (IBAction)displayResult:(id)sender
{
    NSTextList *list = [[NSTextList alloc] initWithMarkerFormat:self.markerFormat options:1]; // {Decimal} passed as marker format
    NSMutableParagraphStyle *paragraph = [[NSParagraphStyle defaultParagraphStyle] mutableCopy];
    [paragraph setTextLists:[NSArray arrayWithObject:list]];
    [list release];
    NSDictionary *attributes = [NSDictionary dictionaryWithObjectsAndKeys:paragraph, NSParagraphStyleAttributeName, nil];
    NSAttributedString *attrString = [[NSAttributedString alloc] initWithString:self.inputString attributes:attributes] ; // input string is- George \n Thomas \n Ashok
    [self setOutputString:attrString]; 
    [attrString release];
    [paragraph release];
}

The input is -

George 

Thomas 

Ashok

The output should be -

1 George

2 Thomas

3 Ashok

but output it is showing is -

George 

Thomas 

Ashok

Can anyone suggest how to achieve the expected output?

Rob Keniger
  • 45,830
  • 6
  • 101
  • 134
Devarshi
  • 16,440
  • 13
  • 72
  • 125

2 Answers2

6

Everything you are doing looks fine and ornately to me:) There is a problem with your input string Try this,

NSTextList *list1 = [[NSTextList alloc] initWithMarkerFormat:@"{decimal}" options:0]; // {Decimal} passed as marker format
NSMutableParagraphStyle *paragraph = [[NSParagraphStyle defaultParagraphStyle] mutableCopy];
[list1 setStartingItemNumber:1];
[paragraph setTextLists:[NSArray arrayWithObject:list1]];

NSDictionary *attributes = [NSDictionary dictionaryWithObjectsAndKeys:paragraph, NSParagraphStyleAttributeName, nil];
NSMutableAttributedString *attrString = [[NSMutableAttributedString alloc] initWithString:[NSString stringWithFormat:@"\t%@ Suhas \n \t%@ Devarshi \n \t%@ Rohith\n", [list1 markerForItemNumber:1],[list1 markerForItemNumber:2],[list1 markerForItemNumber:3]] attributes:attributes] ;
 [self.text setStringValue:attrString];//self.text is a NSTextField instance. kindly ignore the compiler warning:)
Devarshi
  • 16,440
  • 13
  • 72
  • 125
Suhas Aithal
  • 842
  • 8
  • 20
  • Awesome, this helped me out. I added an extra \t so that the NSTextView would automatically insert another bullet on the next line. [NSString stringWithFormat:@"\t%@\t%@", [textList markerForItemNumber:0], lineText.string] – Brad G Mar 26 '15 at 18:50
  • I am glad that it helped you, would have been happier if it get accepted as an answer.:) – Suhas Aithal Mar 27 '15 at 06:40
  • I'm glad you commented! I forgot to upvote, hah. +1 good sir. – Brad G Mar 27 '15 at 12:52
3

I know this is four years late, but I encountered the same problem recently and was able to solve it using Swift.

I wanted to be able to convert the selected text in an NSTextView into a bulleted list, but a selection can obviously span across parts of multiple paragraphs. The first step is to figure out the proper selection range by accessing NSTextView.rangeForUserParagraphAttributeChange, which guarantees that you'll always be selecting a range that includes complete paragraphs, not partial ones.

Now, the more difficult part: You have to find each paragraph inside the modified selection range and add a bullet to it, as well as add the proper text list object and replace the text in the modified selected range to include the new text. This is easier said than done, and took me a couple of days to figure out (I'm still in college, so go figure).

I wasn't able to find hardly anything about this on the internet, so hopefully this will spare future internet travelers the trouble I had to go through.

if let range = textView?.rangeForUserParagraphAttributeChange {
// So, the user has selected some text. Range may or may not be the user's exact selection...
// The start of range has possibly back-tracked from the start of the selection to the start of
// the nearest paragraph, and the end has possibly extended from the end of the selection to the
// end of the nearest paragraph at that point.
// Therefore, we are guarunteed to be selecting complete paragraphs.
//
// Now, for each paragraph, we want to insert a bullet.
// The trick is finding the range of each paragraph.
//
// Find the attributed string contents of the entire possibly multi-paragraph selection:
let entireRangeText = textView!.textStorage!.attributedSubstringFromRange(range)
// Find the string contents
let entireRangeString = entireRangeText.string as NSString

// Let's make a list to store all of the paragraph ranges inside range
var paragraphRanges: [NSRange] = []

// Find the range of the first paragraph (possibly the only paragraph)
var paraRange = entireRangeString.paragraphRangeForRange(NSMakeRange(0, 0))
// Para range is relative to range, so let's keep track of it's actual position.
var actualRange = NSMakeRange(paraRange.location + range.location, paraRange.length)
// Add it to the list
paragraphRanges += [actualRange]

// Now find each paragraph inside the selection
while NSMaxRange(paraRange) < entireRangeString.length {
    // Find the next range relative to the range
    paraRange = entireRangeString.paragraphRangeForRange(NSMakeRange(NSMaxRange(paraRange), 0))
    // Find it's actual range relative to the entire text
    actualRange = NSMakeRange(paraRange.location + range.location, paraRange.length)
    // Add it to our list of paragraph ranges
    paragraphRanges += [actualRange]
}

// This is the attributed string that we will use to replace the entireRangeText
// with the bulleted version.
let newText = NSMutableAttributedString()

// Start counting our bullets at 1...
// Todo: Respect the starting number in the preferences
var bulletNumber = 1

// Make a list object to add to each paragraph
// Todo: Respect the list type specified
let list = NSTextList(markerFormat: "{decimal}", options: 0)

// Go through each paragraph range and add bullets to the text inside it
for paragraphRange in paragraphRanges {
    // Construct the bullet header:
    let bulletText = "\t" + list.markerForItemNumber(bulletNumber) + "\t"
    // Find the text from the paragraph
    let paragraphText = textView!.textStorage!.attributedSubstringFromRange(paragraphRange)
    let mutableParagraphText = NSMutableAttributedString(attributedString: paragraphText)

    // A range pointer we really don't need:
    var effectiveRange = NSRange()

    // Construct our new string appropriately
    // Get the paragraph attribute and modify it to have the text list
    if let paragraphStyle = paragraphText.attribute(NSParagraphStyleAttributeName,
                                                    atIndex: 0,
                                                    longestEffectiveRange: &effectiveRange,
                                                    inRange: NSMakeRange(0, paragraphText.length))
        as? NSParagraphStyle {
        let newParagraphStyle = paragraphStyle.mutableCopy() as! NSMutableParagraphStyle
        // Attach the list object to the paragraph style of the paragraph text
        // So that auto list continuation will work properly
        newParagraphStyle.textLists = [list]
        // Update the text's paragraph style:
        mutableParagraphText.addAttributes([NSParagraphStyleAttributeName: newParagraphStyle],
                                            range: NSMakeRange(0, paragraphText.length))
    }

    // Make the bullet's attributes match the attributes of the text it's near, just
    // like TextEdit does.
    newText.appendAttributedString(
        NSAttributedString(string: bulletText,
            attributes: mutableParagraphText.attributesAtIndex(0,
                longestEffectiveRange: &effectiveRange,
                inRange: NSMakeRange(0, paragraphText.length))))
    newText.appendAttributedString(mutableParagraphText)

    // Increase to the next bullet number:
    bulletNumber += 1
}

// Finally, insert our string
textView?.shouldChangeTextInRange(range, replacementString: nil)
textView?.insertText(newText, replacementRange: range)

// Tell the undo manager what happened:
textView?.didChangeText()
undoManager?.setActionName("Paragraph List") // ToDo: Localize

// Change the selection for auto-insertion to work
// IMPORTANT: Note that you have to change the selection twice to get auto list insertion to work
// (at least on my system). Must be a bug.
textView?.setSelectedRange(NSMakeRange(range.location + newText.string.characters.count - 1, 0))
textView?.setSelectedRange(NSMakeRange(range.location + newText.string.characters.count, 0))
}
definitelyokay
  • 377
  • 2
  • 14
  • Note that this algorithm is not bullet-proof, and shouldn't be used as-is for list creation, as its actually far more difficult to implement with nested bullets, etc. Hopefully this will point you in the right direction, though :) – definitelyokay Nov 15 '17 at 18:33