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

Latest Forum Discussions

See All

Make the passage of time your plaything...
While some of us are still waiting for a chance to get our hands on Ash Prime - yes, don’t remind me I could currently buy him this month I’m barely hanging on - Digital Extremes has announced its next anticipated Prime Form for Warframe. Starting... | Read more »
If you can find it and fit through the d...
The holy trinity of amazing company names have come together, to release their equally amazing and adorable mobile game, Hamster Inn. Published by HyperBeard Games, and co-developed by Mum Not Proud and Little Sasquatch Studios, it's time to... | Read more »
Amikin Survival opens for pre-orders on...
Join me on the wonderful trip down the inspiration rabbit hole; much as Palworld seemingly “borrowed” many aspects from the hit Pokemon franchise, it is time for the heavily armed animal survival to also spawn some illegitimate children as Helio... | Read more »
PUBG Mobile teams up with global phenome...
Since launching in 2019, SpyxFamily has exploded to damn near catastrophic popularity, so it was only a matter of time before a mobile game snapped up a collaboration. Enter PUBG Mobile. Until May 12th, players will be able to collect a host of... | Read more »
Embark into the frozen tundra of certain...
Chucklefish, developers of hit action-adventure sandbox game Starbound and owner of one of the cutest logos in gaming, has released their roguelike deck-builder Wildfrost. Created alongside developers Gaziter and Deadpan Games, Wildfrost will... | Read more »
MoreFun Studios has announced Season 4,...
Tension has escalated in the ever-volatile world of Arena Breakout, as your old pal Randall Fisher and bosses Fred and Perrero continue to lob insults and explosives at each other, bringing us to a new phase of warfare. Season 4, Into The Fog of... | Read more »
Top Mobile Game Discounts
Every day, we pick out a curated list of the best mobile discounts on the App Store and post them here. This list won't be comprehensive, but it every game on it is recommended. Feel free to check out the coverage we did on them in the links below... | Read more »
Marvel Future Fight celebrates nine year...
Announced alongside an advertising image I can only assume was aimed squarely at myself with the prominent Deadpool and Odin featured on it, Netmarble has revealed their celebrations for the 9th anniversary of Marvel Future Fight. The Countdown... | Read more »
HoYoFair 2024 prepares to showcase over...
To say Genshin Impact took the world by storm when it was released would be an understatement. However, I think the most surprising part of the launch was just how much further it went than gaming. There have been concerts, art shows, massive... | Read more »
Explore some of BBCs' most iconic s...
Despite your personal opinion on the BBC at a managerial level, it is undeniable that it has overseen some fantastic British shows in the past, and now thanks to a partnership with Roblox, players will be able to interact with some of these... | Read more »

Price Scanner via MacPrices.net

You can save $300-$480 on a 14-inch M3 Pro/Ma...
Apple has 14″ M3 Pro and M3 Max MacBook Pros in stock today and available, Certified Refurbished, starting at $1699 and ranging up to $480 off MSRP. Each model features a new outer case, shipping is... Read more
24-inch M1 iMacs available at Apple starting...
Apple has clearance M1 iMacs available in their Certified Refurbished store starting at $1049 and ranging up to $300 off original MSRP. Each iMac is in like-new condition and comes with Apple’s... Read more
Walmart continues to offer $699 13-inch M1 Ma...
Walmart continues to offer new Apple 13″ M1 MacBook Airs (8GB RAM, 256GB SSD) online for $699, $300 off original MSRP, in Space Gray, Silver, and Gold colors. These are new MacBook for sale by... Read more
B&H has 13-inch M2 MacBook Airs with 16GB...
B&H Photo has 13″ MacBook Airs with M2 CPUs, 16GB of memory, and 256GB of storage in stock and on sale for $1099, $100 off Apple’s MSRP for this configuration. Free 1-2 day delivery is available... Read more
14-inch M3 MacBook Pro with 16GB of RAM avail...
Apple has the 14″ M3 MacBook Pro with 16GB of RAM and 1TB of storage, Certified Refurbished, available for $300 off MSRP. Each MacBook Pro features a new outer case, shipping is free, and an Apple 1-... Read more
Apple M2 Mac minis on sale for up to $150 off...
Amazon has Apple’s M2-powered Mac minis in stock and on sale for $100-$150 off MSRP, each including free delivery: – Mac mini M2/256GB SSD: $499, save $100 – Mac mini M2/512GB SSD: $699, save $100 –... Read more
Amazon is offering a $200 discount on 14-inch...
Amazon has 14-inch M3 MacBook Pros in stock and on sale for $200 off MSRP. Shipping is free. Note that Amazon’s stock tends to come and go: – 14″ M3 MacBook Pro (8GB RAM/512GB SSD): $1399.99, $200... Read more
Sunday Sale: 13-inch M3 MacBook Air for $999,...
Several Apple retailers have the new 13″ MacBook Air with an M3 CPU in stock and on sale today for only $999 in Midnight. These are the lowest prices currently available for new 13″ M3 MacBook Airs... Read more
Multiple Apple retailers are offering 13-inch...
Several Apple retailers have 13″ MacBook Airs with M2 CPUs in stock and on sale this weekend starting at only $849 in Space Gray, Silver, Starlight, and Midnight colors. These are the lowest prices... Read more
Roundup of Verizon’s April Apple iPhone Promo...
Verizon is offering a number of iPhone deals for the month of April. Switch, and open a new of service, and you can qualify for a free iPhone 15 or heavy monthly discounts on other models: – 128GB... Read more

Jobs Board

Relationship Banker - *Apple* Valley Financ...
Relationship Banker - Apple Valley Financial Center APPLE VALLEY, Minnesota **Job Description:** At Bank of America, we are guided by a common purpose to help Read more
IN6728 Optometrist- *Apple* Valley, CA- Tar...
Date: Apr 9, 2024 Brand: Target Optical Location: Apple Valley, CA, US, 92308 **Requisition ID:** 824398 At Target Optical, we help people see and look great - and Read more
Medical Assistant - Orthopedics *Apple* Hil...
Medical Assistant - Orthopedics Apple Hill York Location: WellSpan Medical Group, York, PA Schedule: Full Time Sign-On Bonus Eligible Remote/Hybrid Regular Apply Now Read more
*Apple* Systems Administrator - JAMF - Activ...
…**Public Trust/Other Required:** None **Job Family:** Systems Administration **Skills:** Apple Platforms,Computer Servers,Jamf Pro **Experience:** 3 + years of Read more
Liquor Stock Clerk - S. *Apple* St. - Idaho...
Liquor Stock Clerk - S. Apple St. Boise Posting Begin Date: 2023/10/10 Posting End Date: 2024/10/14 Category: Retail Sub Category: Customer Service Work Type: Part Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.