Devarshi
Devarshi

Reputation: 16758

Simple NSTextList question

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?

Upvotes: 3

Views: 2125

Answers (2)

definitelyokay
definitelyokay

Reputation: 387

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))
    // paraRange 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))
}

Upvotes: 4

Suhas Aithal
Suhas Aithal

Reputation: 852

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:)

Upvotes: 7

Related Questions