TweetFollow Us on Twitter

Using NSParagraphStyle

Volume Number: 19 (2003)
Issue Number: 8
Column Tag: Cocoa

Using NSParagraphStyle

How to programmatically set tabs and more in an NSTextView

by Clark Jackson

The need to become acquainted with NSParagraphStyle occurred when I attempted to automate some reporting. At times "enterprise" computing stands in contrast to typical "consumer" computing because the latter is user-centric while the former is report-centric. For example, consumer computing facilitates lots of user control whereas enterprise computing just wants to-generate-reports-unattended-at-midnight-thank-you. Since much of the text system in Cocoa "just works" for the user, the details of how to let the computer take control in making a report are often left a mystery. Let's shed a little light on some of that mystery.

What we need is to know how to use just one piece of Cocoa's extensive and capable text system that will allow a program to format text inside an NSTextView. The key is using the NSParagraphStyle class.

Some preliminaries

Explaining Interface Builder and Project Builder is beyond the scope of this article. Suffice it to say, the project uses the Model-View-Controller paradigm employing MyModel, MyWindowController, and NSTextView classes. Here's what the view looks like:


Figure 1. The interface window containing an NSTextView.

The only thing noteworthy about this window is that it contains an NSTextView because that is where our formatted text will appear.

To see the results of our formatting it will be useful to use NSTextView's ruler. To see the ruler it is necessary to add the Format menu to our menu bar. Therefore, before leaving IB, we drag the Format menu onto our project's main menu bar and give Show Ruler a key equivalent:


Figure 2. For convenience, in Interface Builder we drag a Format menu onto the menu bar and give Show Ruler a key equivalent.

The view

For our purposes, the important thing to know about NSTextView is that the text displayed in an NSTextView is its textStorage and that textStorage holds attributed strings. The textStorage is an instance of NSTextStorage which inherits from NSAttributedString itself. Attributed strings are Cocoa Foundation objects that contain both ordinary NSString's and formatting objects that apply to those strings, for example, NSFont and NSParagraphStyle. You can bypass dealing with NSTextView's textStorage directly, that is, you can [myNSTextView setString:@"aString"], but @"aString" will adopt either the default paragraph style or a previous style already residing in the textStorage.

Therefore, in order for text to appear formatted in an NSTextView we have to: 1) create an NSAttributedString instance initialized with the string we want to format, and 2) give that NSAttributedString instance the objects that it will use to format the string-in this case an NSParagraphStyle object. This attributed string goes into our NSTextView's textStorage where it appears formatted to the user.

The Controller

The interface buttons are connected to the controller's actions in a typical fashion. Basically, each button initiates a controller action to display a different formatted paragraph in the NSTextView. The controller does this task by sending a string (NSString) to the model object. The model object will return attributed strings (NSAttributedString's attributed with NSParagraphStyle's) to the controller. Finally, the controller will pass the attributed string to the text storage of the NSTextView where it will appear formatted to the user.

Here is one of the three controller actions:

applyStyle1

This method sets a pattern for the other two belonging to the controller. An unformatted string is 
put in the NSTextView for comparison purposes only. The program pauses so the user can see it. Next, 
the string is sent to the model object which returns it as an attributed string. Finally, the 
attributed string goes into the NSTextView revealing the new format. Refer to the source to see the 
other two action methods, applyStyle2 and applyStyle3. 

- (IBAction)applyStyle1:(id)sender
{
   MyModel *theModel;
   NSAttributedString *anAttString;
   NSString *aString;
   // a string containing multiple paragraphs
   aString = @"On a merry-go-round in the night, ";
   aString = [aString stringByAppendingString:
      @"Coriolis was shaken with fright.\n"];
   aString = [aString stringByAppendingString:
      @"Despite how he walked, "];
   aString = [aString stringByAppendingString:
      @"'Twas like he was stalked,\n"];
   aString = [aString stringByAppendingString:
      @"By some fiend always pushing him right."];
// limerick by David Morin, Eric Zaslow, E'beth Haley, John Golden, and Nathan Salwen,
//   http://www.aps.org/apsnews/11855.html
   // a convenience method to restore the default paragraph style to the NSTextView
   [self resetTextView]; 
   // install string w/o changing paragraph style for comparison
   [myNSTextView setString:aString]; 
   [myNSTextView display]; // force redraw
   sleep(1); // pause
   
   // now convert aString to an NSAttributedString by applying an NSParagraphStyle
   //   using the model object
   theModel = [[MyModel alloc]init];
   anAttString = [theModel attributeString1:aString];
   [theModel release];
   
   // now that we have the attributed string, put it into the NSTextView.
   // In order to get multiple attributed strings into a textStorage use
//      [myNSTextView insertText:attributedString] instead
   [[myNSTextView textStorage]
      setAttributedString:anAttString];
   [myNSTextView setNeedsDisplay:YES];
   return;
}

The Model

MyModel has a method for each of the controller's actions. It is here that we make use of NSParagraphStyle. Each method takes an ordinary NSString and returns it as an NSAttributedString so it can end up formatted in the NSTextView. When running the program, click inside the NSTextView and make the ruler visible. Some of the paragraph style attributes that we set show up on the NSTextView's ruler:


Figure 3. Many of NSParagraphStyle attributes appear in the NSTextView ruler.

Following are the methods used to create NSParagraphStyle's and apply them to strings thus creating NSAttributedString's:

attributeString1

This method creates an NSMutableAttributedString, using an NSString and an NSMutableParagraphStyle.

-(NSMutableAttributedString *) attributeString1:
      (NSString *) aString
{
   NSMutableParagraphStyle *aMutableParagraphStyle;
   NSMutableAttributedString   *attString;
   /*
   The only way to instantiate an NSMutableParagraphStyle is to mutably copy an
   NSParagraphStyle. And since we don't have an existing NSParagraphStyle available
   to copy, we use the default one.
   
   The default values supplied by the default NSParagraphStyle are:
      Alignment   NSNaturalTextAlignment
      Tab stops   12 left-aligned tabs, spaced by 28.0 points
      Line break mode   NSLineBreakByWordWrapping
      All others   0.0
   */
   aMutableParagraphStyle =
      [[NSParagraphStyle defaultParagraphStyle]mutableCopy];
   
   // Now adjust our NSMutableParagraphStyle formatting to be whatever we want.
   // The numeric values below are in points (72 points per inch)
   [aMutableParagraphStyle
      setAlignment:NSLeftTextAlignment];
   [aMutableParagraphStyle setLineSpacing:5.5];
   [aMutableParagraphStyle setParagraphSpacing:25.5];
   [aMutableParagraphStyle setHeadIndent:25.0];
   [aMutableParagraphStyle setTailIndent:-45.0];
   // setTailIndent: if negative, offset from right margin (right margin mark does
   //      NOT appear); if positive, offset from left margin (margin mark DOES appear)
   [aMutableParagraphStyle setFirstLineHeadIndent:65.0];
   [aMutableParagraphStyle
      setLineBreakMode:NSLineBreakByWordWrapping];
   /*
    possible allignments
      NSLeftTextAlignment
      NSRightTextAlignment
      NSCenterTextAlignment
      NSJustifiedTextAlignment
      NSNaturalTextAlignment
    possible line wraps
      NSLineBreakByWordWrapping
      NSLineBreakByCharWrapping
      NSLineBreakByClipping
    */
   
   // Instantiate the NSMutableAttributedString with the argument string
   attString = [[NSMutableAttributedString alloc]
      initWithString:aString];
   // Apply your paragraph style attribute over the entire string
   [attString addAttribute:NSParagraphStyleAttributeName
      value:aMutableParagraphStyle
      range:NSMakeRange(0,[aString length])];
   [aMutableParagraphStyle release]; // since it was copy'd
   [attString autorelease]; // since it was alloc'd
   return attString;
}

If your NSTextView already has attributed strings in its textStorage, you can get the NSParagraphStyle by:

aMutableParagraphStyle = [[myTextView typingAttributes]
   objectForKey:@"NSParagraphStyle"];

The NSParagraphStyle returned above comes from the attributes of the text from where the cursor is found inside the NSTextView. NSParagraphStyle's can span multiple paragraphs but there cannot be more than one per paragraph.

attributeString2

This method creates an array of NSTextTab's and applies them to the NSMutableParagraphStyle before 
it is used to create the NSMutableAttributedString.

-(NSMutableAttributedString *)
   attributeString2:(NSString *) aString
{
   float firstColumnInch = 1.75,
      otherColumnInch = 0.6, pntPerInch = 72.0;
   int i;
   NSTextTab *aTab;
   NSMutableArray *myArrayOfTabs;
   NSMutableParagraphStyle *aMutableParagraphStyle;
   NSMutableAttributedString   *attString;
   myArrayOfTabs = [NSMutableArray arrayWithCapacity:14];
   aTab = [[NSTextTab alloc]
      initWithType:NSLeftTabStopType
      location:firstColumnInch*pntPerInch];
   [myArrayOfTabs addObject:aTab];
   [aTab release]; // aTab was alloc'd and the array owns it now so release it
   for(i=1;i<14;i++)
   {
      aTab = [[NSTextTab alloc]
         initWithType:NSRightTabStopType
         location:(firstColumnInch*pntPerInch)
         + ((float)i * otherColumnInch * pntPerInch)];
      [myArrayOfTabs addObject:aTab];
[aTab release]; // aTab was alloc'd and the array owns it now so release it
   }
   /*
   possible tab stop types
      NSLeftTabStopType
      NSRightTabStopType
      NSCenterTabStopType
      NSDecimalTabStopType
    */
    
   aMutableParagraphStyle =
      [[NSParagraphStyle defaultParagraphStyle]mutableCopy];
   [aMutableParagraphStyle setTabStops:myArrayOfTabs];
   attString = [[NSMutableAttributedString alloc]
      initWithString:aString];
   [attString addAttribute:NSParagraphStyleAttributeName
      value:aMutableParagraphStyle
      range:NSMakeRange(0,[aString length])];
   [aMutableParagraphStyle release];
   [attString autorelease];
   return attString;
}

Tabbed text appears as below without using NSParagraphStyle:


Figure 4. Default behavior of an NSTextView containing tabbed text. The tab spacing is arbitrary and the lines wrap even though there is space enough to hold the whole line.

Tabbed text appears as below after creating an NSMutableParagraphStyle and assigning it to an NSMutableAttributedString using the attributeString2 method:


Figure 5. By using an NSMutableParagraphStyle the tab stops have been precisely set and the lines do not wrap unexpectedly.

attributeString3

This method supplies a way to parse a string containing multiple paragraphs and apply different 
paragraph styles to each.

-(NSMutableArray *) attributeString3:(NSString *)aString 
{
   NSRange myRange,rangeOfLine,rangeOfLineContent;
   unsigned startIndex,lineEndIndex,contentsEndIndex;
   NSMutableParagraphStyle
      *aMutableParagraphStyle1,*aMutableParagraphStyle2;
   NSMutableAttributedString   *attString;
   int lineCtr;
   NSMutableArray * arrayOfNSMutableAttributedString;
   NSString *pulledOutParagraph;
   arrayOfNSMutableAttributedString
      = [NSMutableArray arrayWithCapacity:5];
   /*
    lines can be terminated in the following ways for use with
      getLineStart:end:contentsEnd:forRange:
    *    U+000D (\r or CR), Mac
    *    U+2028 (Unicode line separator)
    *    U+000A (\n or LF), Unix
    *    U+2029 (Unicode paragraph separator)
    *    \r\n, in that order (also known as CRLF), Windows
    */
   /*
   Using getLineStart:end:contentsEnd:forRange: 
   @"Big\r\nMac    --example string
   in this case
      1st contentsEndIndex    is set to 3,
         the first character past the line content,
         line content being defined as the characters not including
         line termination character(s)
      1st lineEndIndex    is set to 5
         the first character past the end of line character(s)
   */
   // make 2 different paragraph styles
   aMutableParagraphStyle1 =
      [[NSParagraphStyle defaultParagraphStyle]mutableCopy];
   aMutableParagraphStyle2 =
      [[NSParagraphStyle defaultParagraphStyle]mutableCopy];
   [aMutableParagraphStyle1
      setAlignment:NSLeftTextAlignment];
   [aMutableParagraphStyle2
      setAlignment:NSRightTextAlignment];
   
   // initialize
   myRange = NSMakeRange(0,0); // (location,length)
   lineEndIndex = 0;
   lineCtr = 0;
   /*
    The while() block below walks down the string pulling out lines (paragraphs)
   including their end of line (paragraph) characters, converts them to attributed strings,
   and adds them to an array.
   The range in the getLineStart:end:contentsEnd:forRange: method has to be picky
   about where it's location is but doesn't have to be picky about length (i.e. 0 will do)
   */
   while(lineEndIndex < [aString length])
   {
      [aString getLineStart: &startIndex
         end: &lineEndIndex
         contentsEnd: &contentsEndIndex
         forRange: myRange];
      // rangeOfLineContent excludes line termination character(s)
      rangeOfLineContent =
         NSMakeRange(startIndex,contentsEndIndex-startIndex); 
      // rangeOfLine includes line termination character(s)
      rangeOfLine =
         NSMakeRange(startIndex,lineEndIndex-startIndex); 
      // apply paragraph style to pulledOutParagraph
      pulledOutParagraph =
         [aString substringWithRange:rangeOfLine];
      attString = [[NSMutableAttributedString alloc]
         initWithString:pulledOutParagraph];
      if(lineCtr % 2 == 0) // alternate paragraph styles on odd/even lines
      {
         // If range.length below is zero then the paragraph style is not applied.
         //   For strings with length > 0, range.length should be > 0 and <= length of the
         //   string so NSMakeRange(0,1) would work as well below
         //   provided you don't have an empty string.
         [attString addAttribute:NSParagraphStyleAttributeName
            value: aMutableParagraphStyle1
            range: NSMakeRange(0,[pulledOutParagraph length])];
      }
      else
      {
         [attString addAttribute:NSParagraphStyleAttributeName
            value:aMutableParagraphStyle2
            range:NSMakeRange(0,[pulledOutParagraph length])];
      }
      [arrayOfNSMutableAttributedString addObject:attString];
      [attString release]; // the array now owns it
      lineCtr++;
      // recalculate the range before looping
      myRange = NSMakeRange(lineEndIndex,0); 
   }
   [aMutableParagraphStyle1 release];
   [aMutableParagraphStyle2 release];
   return arrayOfNSMutableAttributedString;
}

Conclusion

Cocoa's text system is extensive involving many classes not even mentioned here. But if you just need to get some formatted, tabbed text into an NSTextView for that special report, the methods explained here should do the trick.


Clark Jackson test drove the first Mac and recognized that it was about to change the world. Lately he has been busy wearing many hats automating energy accounting in the rapidly changing electrical power industry at Tacoma Power. He can be contacted at cjackson@cityoftacoma.org.

 

Community Search:
MacTech Search:

Software Updates via MacUpdate

How to become the ultimate robot warrior...
Chrono Strike is a delightfully immersive beat ‘em up with a sense of humor (any game with a good Sims reference gets points in my book). [Read more] | Read more »
Tips and tricks to get a higher score in...
Snow Roll is a devilish endless runner very much in the vein of Flappy Bird. It revels in its dastardly level of difficulty, and doesn’t really care how angry you get at it as it knows you’ll keep coming back for more. [Read more] | Read more »
How to win big in Slots Deluxe
Cheating while gambling is illegal and morally wrong, and in some parts of the world it leads to men with names like Vinnie "Six Knuckles" Manchenzo beating you to a pulp in a dark alley. [Read more] | Read more »
How to take over the world in Dictator 2
Running a country isn't easy - especially when you're a dictator who wants to take over the world and crush everyone in your path while you do it. [Read more] | Read more »
Tips and tricks to get a higher score in...
Tank.iois - you guessed it! - another multiplayer arena battler likeAgar.io and Slither.io. It does differentiate itself by putting you in a tiny tank though, so it's not exactly the same. To help you get that all-important high score, we've got a... | Read more »
How to unlock characters in One Tap Tenn...
As the title suggests, One Tap Tennis requires only a single tap to play its particular brand of tennis, and rewards you with a ton of unlockable characters if you perform well. Fortunately for you, we at 148Apps have got a few tips and tricks to... | Read more »
Grab it now: Game Craft’s Legend of War...
The real time strategy game is now available for you to sink your teeth into, through the App Store and Google Play. Combining elements of skill, strategy and empire building, Legend of War is a real gamers’ game. [Read more] | Read more »
Skateboard Party 3 ft. Greg Lutzka (Gam...
Skateboard Party 3 ft. Greg Lutzka 1.0 Device: iOS Universal Category: Games Price: $1.99, Version: 1.0 (iTunes) Description: Skateboard Party is back! This third edition of the popular sports franchise features professional skater... | Read more »
Cubious (Games)
Cubious 1.0 Device: iOS Universal Category: Games Price: $.99, Version: 1.0 (iTunes) Description: Cubious – How smart are you? How high is your IQube? Solve the impossible puzzles to find out, and help a lost little cube find his... | Read more »
Goat Simulator Waste of Space (Games)
Goat Simulator Waste of Space 1.1 Device: iOS Universal Category: Games Price: $4.99, Version: 1.1 (iTunes) Description: ** IMPORTANT - SUPPORTED DEVICESiPhone 4S, iPad 2, iPod Touch 5 or better.** | Read more »

Price Scanner via MacPrices.net

Enterprise Workers Pick Technology Over Perks...
New Adobe study shows surprising attitudes about office jobs and where the future of work is heading. Adobe has released survey findings revealing that a surprising 70 percent of U.S. office workers... Read more
Memorial Day Weekend Sale: $50-$100 off 11-in...
B&H Photo has 13″ and 11″ MacBook Airs with 256GB SSDs on sale for $50-$100 off MSRP. Shipping is free, and B&H charges NY sales tax only: - 11″ 1.6GHz/256GB MacBook Air: $999 $100 off MSRP... Read more
Memorial Day Weekend Sales: Apple MacBook Pro...
B&H Photo has 13″ and 15″ Retina MacBook Pros on sale for up to $210 off MSRP. Shipping is free, and B&H charges NY tax only: - 15″ 2.2GHz Retina MacBook Pro: $1799 $200 off MSRP - 15″ 2.5GHz... Read more
Memorial Day Weekend Sales: Apple iMacs and M...
Take up to $150 off the price of a new iMac or Mac mini at the following Apple resellers this Memorial Day weekend: iMacs: B&H Photo has 21″ and 27″ iMacs on sale for up to $150 off MSRP... Read more
Apple refurbished Retina MacBook Pros availab...
Apple has Certified Refurbished 2015 13″ and 15″ Retina MacBook Pros available for up to $380 off the cost of new models. An Apple one-year warranty is included with each model, and shipping is free... Read more
Apple refurbished 11-inch MacBook Airs availa...
Apple has Certified Refurbished 11″ MacBook Airs (the latest models), available for up to $170 off the cost of new models. An Apple one-year warranty is included with each MacBook, and shipping is... Read more
Goal Zero and OtterBox Partner to Expand iPh...
Goal Zero, specialists in portable power, have announced a partnership with OtterBox, brand smartphone case protection, to offer the Slide and Slide Plus Batteries as modules compatible with the new... Read more
15-inch Retina MacBook Pros on sale for up to...
B&H Photo has 15″ Retina MacBook Pros on sale for up to $210 off MSRP. Shipping is free, and B&H charges NY tax only: - 15″ 2.2GHz Retina MacBook Pro: $1799 $200 off MSRP - 15″ 2.5GHz Retina... Read more
Clearance 2015 13-inch MacBook Airs available...
B&H Photo has clearance 2015 13″ MacBook Airs available for $250 off original MSRP. Shipping is free, and B&H charges NY sales tax only: - 13″ 1.6GHz/4GB/128GB MacBook Air (MJVE2LL/A): $799... Read more
Apple refurbished Apple TVs available for up...
Apple has Certified Refurbished 32GB and 64GB Apple TVs available for up to $30 off the cost of new models. Apple’s standard one-year warranty is included with each model, and shipping is free: -... Read more

Jobs Board

*Apple* Solutions Consultant - APPLE (United...
Job Summary As an Apple Solutions Consultant, you'll be the link between our future customers and our products. You'll showcase your entrepreneurial spirit as you Read more
*Apple* Project Engineer - Smart Source Inc...
SmartSource is in need of an Apple Project Engineer for a 12 month contract opportunity in Pittsburg, PA. Role: Apple Project Engineer Location: Pittsburg, PA Read more
Automotive Sales Consultant - Apple Ford Linc...
…you. The best candidates are smart, technologically savvy and are customer focused. Apple Ford Lincoln Apple Valley is different, because: $30,000 annual salary Read more
Service Assistant - *Apple* Chevrolet *App...
Apple Automotive is one of the fastest growing dealer...and it shows. Consider making the switch to the Apple Automotive Group today! At Apple Automotive, we Read more
Editor, *Apple* News - APPLE (United States...
Job Summary The Apple News team is looking for a passionate and knowledgeable editor with experience covering entertainment/pop culture and experience running social Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.