TweetFollow Us on Twitter

Spaced Out

Volume Number: 18 (2002)
Issue Number: 12
Column Tag: Mac OS X

Spaced Out

Adding Paragraph Spacing to the Cocoa Text System

by Andrew C. Stone

One of my favorite Cocoa demos is to make a full featured word processor application in 5 minutes - complete with rulers, tabs, embedded graphics, line spacing, kerning, ligatures, baseline, colors, multi-font, automatic spell checking and more - (see Cocoa's powerful text system is a collection of classes that can meet almost any text need, and I highly recommend reading the documentation for these classes:,NSText and NSTextView (the display classes), NSTextStorage and NSAttributedString (how rich strings store all the attributes of unicode text), NSLayoutManager (manages an NSTextStorage and set of NSTextContainers which display in an NSTextView) and playing with the sample Text layout application available in the Developer distribution in /Developer/Examples/AppKit/TextSizingExample.

These full featured classes are getting more powerful with each major system release. In Mac OS X 10.2, a feature which was previously defined in the API was implemented in NSParagraphStyle:


- (float)paragraphSpacing

Returns the space added at the end of the paragraph to separate it from the following paragraph. This value is always nonnegative.

See Also: - lineSpacing - setParagraphSpacing: (NSMutableParagraphStyle)

Paragraph spacing is the additional distance between paragraphs (that is, whenever a <RETURN> appears in the text). This amount is in points (72 per inch) and by default is 0. It's added to any additional line spacing that applies to that paragraph style.

But, although the implementation is there, the interface is not. The ruler, which comes for free with NXTextView, and which contains controls for alignment, line spacing, and tabs, currently lacks any control for setting the paragraph spacing. This article will show you how to add a custom control to the standard text system ruler.

Figure 1: The standard text ruler in Mac OS X 10.2 doesn't have a paragraph spacing control.

Just using simple subclasses of NSTextView and NSLayoutManager, our page layout and web authoring application Create(R) can flow text through any size containers, place rich text along any path, apply neon and custom pattern effects to any text, and place text outside or inside of any path. Once Jaguar shipped, we could easily add paragraph spacing to our text - if we could figure out a way to add a new control to the ruler that automatically attaches itself to any NSTextView in an NSScrollView.

Like everything with Cocoa - if it's hard it's wrong. So finding an easy solution with modular application is the always the goal of any Cocoa programming challenge.

Our design imperatives include:

  • make it simple

  • make it small so it doesn't get in the way

  • make it a modular nib file

The solution has 3 parts - a user interface built in InterfaceBuilder, the create/update code in an NSLayoutManager subclass, and code in an NSTextView subclass which actually sets the spacing and maintains the undo stack.

So, I chose to make the narrowest UI possible - using the P symbol for paragraph and an NSStepper:

Figure 2: Create(R) adds a paragraph spacer next to the line spacing tools - P

The first problem is how do we get at the ruler to install the new device? It's owned by NSLayoutManager, which provides a method that returns this ruler view - so that seems like the most appropriate place to instantiate and update our own user interface addition which can set the paragraph spacing:

- (NSView *)rulerAccessoryViewForTextView:(NSTextView *)aTextView 
paragraphStyle:(NSParagraphStyle *)paraStyle ruler:(NSRulerView *)aRulerView enabled:(BOOL)flag

Returns the accessory NSView for aRulerView. This accessory contains tab wells, text alignment buttons, and so on. paraStyle is used to set the state of the controls in the accessory NSView; it must not be nil. If flag is YES the accessory view is enabled and accepts mouse and keyboard events; if NO it's disabled.

This method is invoked automatically by the NSTextView object using the layout manager. You should rarely need to invoke it, but you can override it to customize ruler support.

Let's Just Face it

We'll use InterfaceBuilder to create the interface and even the stub files for our new class, ParagraphSpacer:

    1. Launch Interface Builder

    2. File -> New..., Cocoa, "Empty", Click "New"

    3. Save this as "ParagraphSpacer.nib" in your project directory - also add it to your project when asked.

    4. Double-click the "File's Owner" icon in the folio window - NSObject will be selected in the Classes tab

    5. Control-Click NSObject and select "Create Subclass" - name it "ParagraphSpacer"

    6. Add two outlets: stepper and containerView by Control-Clicking ParagraphSpacer and choose "Add Outlet to ParagraphSpacer".

    7. Control-click ParagraphSpacer and choose "Create Files for Paragraph Spacer" - add these to your project

    8. Choose "Instances" tab, select "File's Owner", Info-> Custom Class, select "ParagraphSpacer"

    9. From the Tab icon on the Palette, drag a "Custom View" into the folio window

    10. Set the view's size with , Info -> Size, 26 wide by 28 tall

    11. Drag in "System Font Text", select all, delete, type P, Info -> Size 10 wide by 17 tall, locate on left

    12. Drag in an NSStepper from Slider Icon tab on Palette, adjust location as needed

    13. Connect the File's owner to the two instance variables - the stepper, and the view which holds the stepper and the static P text.

    14. Save ParagraphSpacer.nib


A very simple class which just returns its two instance variables.We need these to install the view into the Ruler view hierarchy and set the value of the stepper during updates:

/* ParagraphSpacer */
#import <Cocoa/Cocoa.h>
@interface ParagraphSpacer : NSObject
    IBOutlet id containerView;
    IBOutlet NSStepper *stepper;
- (NSStepper *)stepper;
- (NSView *)containerView;
#import "ParagraphSpacer.h"
@implementation ParagraphSpacer
- (id) init {
    self = [super init];
    if (![NSBundle loadNibNamed:@"ParagraphSpacer.nib" owner:self])
   NSLog(@"couldn't load ParagraphSpacer\n");
    return self;
- (NSStepper *)stepper; {
    return stepper;
- (NSView *)containerView; {
    return containerView;


We just need to add one method to our NSLayoutManager subclass. Note that we call [super rulerAccessoryViewForTextView: ... ] to get the standard ruler provided for us, then we check to see if we have already initialized the paragraph spacer, and if not, proceed to create it, find it's proper position in the ruler and install it. We'll travel down the view hierarchy, looking at the subviews of each view. When we find a view with several subviews, then we know we're in the right place. When we find the view that starts far to the left, ie, not the tab well, but the NSBox which surrounds the alignment and line spacing controls, we'll place our control right next to it. We just have to hope that the ruler doesn't change drastically - if it does, it will probably have more controls in it, and our layout may be wrong.

Each time this method gets called, we'll set the target of the stepper to be the current text view with an action of changeParagraphSpacing:, and update the value of the stepper so that it sends the target the right value when incrementing or decrementing.

@interface SDLayoutManager : NSLayoutManager
    NSStepper *_paragraphStepper;
@implementation SDLayoutManager
- (NSView *)rulerAccessoryViewForTextView:(NSTextView *)view paragraphStyle:(NSParagraphStyle *)style 
ruler:(NSRulerView *)ruler enabled:(BOOL)isEnabled {

    NSView *accessory = [super rulerAccessoryViewForTextView:view paragraphStyle:style ruler:ruler 
    if (!_paragraphStepper) {
        ParagraphSpacer *spacer = [[ParagraphSpacer allocWithZone:[self zone]] init];
        NSView *viewToAdd = [spacer containerView];
        NSArray *subviews = [accessory subviews];
        NSView *viewToAddTo = accessory;
        unsigned int i, count = [subviews count];
        NSRect viewRect = [viewToAdd bounds];
        if (count == 1) {
            viewToAddTo = [subviews objectAtIndex:0];
            subviews = [[subviews objectAtIndex:0] subviews];
            count = [subviews count];
        _paragraphStepper = [spacer stepper];
        for (i = 0; i < count; i++) {
            NSView *v = [subviews objectAtIndex:i];
            NSRect r = [v frame];
            if (r.origin.x < 10.0) {   // it's the box containing the left controls)
                viewRect.origin.x = r.origin.x + r.size.width;
                viewRect.origin.y = 0.0;
                [viewToAdd setFrame:viewRect];
                [viewToAddTo addSubview:viewToAdd];
    [_paragraphStepper setDoubleValue:style? [style paragraphSpacing] : 0.0];
    [_paragraphStepper setTarget:view];
    [_paragraphStepper setAction:@selector(changeParagraphSpacing:)];
    return accessory;


If you don't want to subclass NSTextView, you could instead add the changeParagraphSpacing: method to a category of NSTextView. This is not the case with the NSLayoutManager subclass, because we need to call super's implementation of rulerAccessoryViewForTextView:paragraphStyle:ruler:enabled:.

The main reason we place this code in NSTextView or a subclass is so we get the free automatic undo associated with Text. To do that, we alert the text system that there will be changes in a certain range with shouldChangeTextInRange: replacementString: with a replacement string of "nil" , which means other attributes are changing, but not any characters. Then, we walk over text paragraph style by paragraph style, setting the paragraph spacing to the value determined by the stepper (up or down a point from the first style in the selection). Note that if there are no paragraph attributes, one is added.

Finally, we alert the text that we are done changing it with didChangeText, and we add a custom action name so the menu will say "Undo Paragraph Spacing" and "Redo Paragraph Spacing".

@interface SDTextView: NSTextView
@implementation SDTextView
- (void)changeParagraphSpacing:(id)sender {
    double value = [sender doubleValue];
    NSRange range = [self rangeForUserParagraphAttributeChange];
    if (range.length > 0) {
        NSRange remainingRange = range;
        NSTextStorage *storage = [self textStorage];
        [self shouldChangeTextInRange:range replacementString:nil];
        while (remainingRange.length > 0) {
                NSRange effectiveRange;
                NSParagraphStyle *para = [storage attribute:NSParagraphStyleAttributeName 
                atIndex:remainingRange.location longestEffectiveRange:&effectiveRange 
                if (para == nil) {
                    para = [[[NSMutableParagraphStyle alloc] init] autorelease];
                    [para setParagraphStyle:[NSParagraphStyle defaultParagraphStyle]];
                } else para = [[para mutableCopyWithZone:[self zone]]autorelease];
                [para setParagraphSpacing:value];
                [storage addAttribute:NSParagraphStyleAttributeName value:para range:remainingRange];
                if (NSMaxRange(effectiveRange) < NSMaxRange(remainingRange)) {
                    remainingRange.length = NSMaxRange(remainingRange) - NSMaxRange(effectiveRange);
                    remainingRange.location = NSMaxRange(effectiveRange);
                } else {
            [self didChangeText];
            [[self undoManager] setActionName:NSLocalizedStringFromTable(@"Paragraph Spacing",
            @"Muktinath",@"change of space between paragraphs")];

All Together Now

Your final task is just to be sure you create your text system with the special SDLayoutManager. If you have a shared text editor, it might look something like this:

static NSTextView *newEditor(TextArea *self) {
    SDTextView *tv;
    NSTextContainer *tc;
    // This method returns an NSTextView whose SDLayoutManager has a refcount of 1.  It is 
    the caller's responsibility to release the SDLayoutManager.  This function is only for the use of
    the following method.
    SDLayoutManager *lm = [[SDLayoutManager allocWithZone:NULL] init];
    tv = [[SDTextView allocWithZone:NULL] initWithFrame:NSMakeRect(0.0, 0.0, 100.0, 100.0) 
    tc = [[NSTextContainer allocWithZone:NULL] initWithContainerSize:NSMakeSize(1.0e6, 1.0e6)];
    [lm addTextContainer:tc];
    [tc release];
    [tc setTextView:tv];
    [tv release];
    return tv;

Figure 3: The new text ruler with paragraph spacing stepper installed.


The Cocoa text system just keeps getting better. And sometimes there are features that are still hidden from the user interface, such as paragraph spacing in Jaguar 10.2. With a little Cocoa magic, it's easy to install your own custom controls and add more functionality to the standard text object.

Andrew Stone, CEO of Stone Design,, has been the principal architect of several solar houses and over a dozen Cocoa applications shipping for Mac OS X.


Community Search:
MacTech Search:

Software Updates via MacUpdate

ExpanDrive - Access cloud storag...
ExpanDrive builds cloud storage in every application, acts just like a USB drive plugged into your Mac. With ExpanDrive, you can securely access any remote file server directly from the Finder or... Read more
Markly 1.5.3 - Create measurement and de...
Markly is a measurement and design-spec plugin/extension for Photoshop and Sketch. It is made for modern Web designers and app front-end developers. You can add specification marks simply by clicking... Read more
Suitcase Fusion 6 17.3.0 - Font manageme...
Suitcase Fusion 6 is the creative professional's font manager. Every professional font manager should deliver the basics: spectacular previews, powerful search tools, and efficient font organization... Read more
Nisus Writer Pro 2.1.2 - Multilingual wo...
Nisus Writer Pro is a powerful multilingual word processor, similar to its entry level products, but brings new features such as table of contents, indexing, bookmarks, widow and orphan control,... Read more
calibre 2.40.0 - Complete e-book library...
Calibre is a complete e-book library manager. Organize your collection, convert your books to multiple formats, and sync with all of your devices. Let Calibre be your multi-tasking digital librarian... Read more
Vivaldi - An advanced browser...
Vivaldi is a browser for our friends. In 1994, two programmers started working on a web browser. Our idea was to make a really fast browser, capable of running on limited hardware, keeping in mind... Read more
OmniPlan 3.0 - Robust project management...
With OmniPlan, you can create logical, manageable project plans with Gantt charts, schedules, summaries, milestones, and critical paths. Break down the tasks needed to make your project a success,... Read more
Yummy FTP 1.11 - FTP/SFTP/FTPS client fo...
Yummy FTP is an FTP + SFTP + FTPS file transfer client which focuses on speed, reliability and productivity. Whether you need to transfer a few files or a few thousand, schedule automatic backups, or... Read more
Tweetbot 2.1 - Popular Twitter client. (...
Tweetbot is a full-featured OS X Twitter client with a lot of personality. Whether it's the meticulously-crafted interface, sounds and animation, or features like multiple timelines and column views... Read more
MacPilot 8.0 - Enable over 1,200 hidden...
MacPilot gives you the power of UNIX and the simplicity of Macintosh, which means a phenomenal amount of untapped power in your hands! Use MacPilot to unlock over 1,200 features, and access them all... Read more

Balloony Land offers a fresh twist on th...
Balloony Land by Palringo offers a fresh twist on the match three genre and is out now on iOS and Android. First-off, you'll be popping balloons instead of crushing candy and the balloons will float up and fill the empty spaces instead of dropping... | Read more »
Graphic - vector illustration and design...
Graphic - vector illustration and design 1.0 Device: iOS iPhone Category: Productivity Price: $2.99, Version: 1.0 (iTunes) Description: Autodesk Graphic is a powerful full-featured vector drawing and illustration application right in... | Read more »
Sago Mini Babies (Education)
Sago Mini Babies 1.0 Device: iOS Universal Category: Education Price: $2.99, Version: 1.0 (iTunes) Description: Introducing the Sago Mini babies. Boys and girls love caring for these adorable characters. Feed Robin her favorite mush... | Read more »
PAUSE - Relaxation at your fingertip (H...
PAUSE - Relaxation at your fingertip 1.1 Device: iOS iPhone Category: Healthcare & Fitness Price: $1.99, Version: 1.1 (iTunes) Description: | Read more »
Super Sharp (Games)
Super Sharp 1.1 Device: iOS Universal Category: Games Price: $1.99, Version: 1.1 (iTunes) Description: Your finger has never been so sharp! Cut with skill to complete the 120 ingenious physics levels of Super Sharp and become a cut... | Read more »
Assembly - Graphic design for everyone...
Assembly - Graphic design for everyone 1.0 Device: iOS Universal Category: Photography Price: $2.99, Version: 1.0 (iTunes) Description: Assembly is the easiest most powerful design tool on the App Store. Create anything you can... | Read more »
Dub Dash (Games)
Dub Dash 1.0 Device: iOS Universal Category: Games Price: $1.99, Version: 1.0 (iTunes) Description: ARE YOU READY FOR THE ULTIMATE CHALLENGE? UNIQUE SYMBIOSIS OF MUSIC AND GRAPHICS | Read more »
Leave Me Alone (Games)
Leave Me Alone 1.0 Device: iOS Universal Category: Games Price: $1.99, Version: 1.0 (iTunes) Description: 33% off launch sale!!! Somewhere between the 1980s and 1990s there exists a world that never was. A world of skatepunks,... | Read more »
YAMGUN (Games)
YAMGUN 1.0 Device: iOS Universal Category: Games Price: $2.99, Version: 1.0 (iTunes) Description: The invasion has begun! Protect the walls of the citadel against waves of enemies! But watch out, you will soon run out of ammo...... | Read more »
Chesh (Games)
Chesh 1.0 Device: iOS Universal Category: Games Price: $2.99, Version: 1.0 (iTunes) Description: It’s like chess, only not at all. ***40% off for a limited time to celebrate our launch!*** Chesh is a game of skill, strategy, luck,... | Read more »

Price Scanner via

Apple restocks refurbished Mac minis for up t...
Apple has restocked Certified Refurbished 2014 Mac minis, with models available starting at $419. Apple’s one-year warranty is included with each mini, and shipping is free: - 1.4GHz Mac mini: $419 $... Read more
TP-LINK Next-Gen Routers Support a Large Numb...
TP-LINK, specialists in consumer and business networking products, have announced the availability of Archer C2600, the company’s next-generation router featuring wireless AC, multi-user MIMO, and 4-... Read more
Apple’s Education discount saves up to $300 o...
Purchase a new Mac or iPad using Apple’s Education Store and take up to $300 off MSRP. All teachers, students, and staff of any educational institution qualify for the discount. Shipping is free, and... Read more
Save up to $350 with Apple refurbished iMacs
Apple has Certified Refurbished iMacs available for up to $350 off the cost of new models. Apple’s one-year warranty is standard, and shipping is free: - 27″ 3.5GHz 5K iMac – $1949 $350 off MSRP - 27... Read more
Mac Pros on sale for up to $300 off MSRP
B&H Photo has Mac Pros on sale for up to $300 off MSRP. Shipping is free, and B&H charges sales tax in NY only: - 3.7GHz 4-core Mac Pro: $2818.99, $181 off MSRP - 3.5GHz 6-core Mac Pro: $3699... Read more
5K iMacs on sale for up to $150 off MSRP, fre...
B&H Photo has the 27″ 3.3GHz 5K iMac on sale for $1899.99 including free shipping plus NY tax only. Their price is $100 off MSRP. They have the 27″ 3.5GHz 5K iMac on sale for $2149, $150 off MSRP... Read more
Twelve South Redesigns BookArc For Today’s Sm...
Twelve South has announced a redesigned version of their very first product, BookArc for MacBook. Tailored specifically for the newest generation of MacBooks, BookArc holds the new, smaller Apple... Read more
Phone 6s Tips & Tricks – Tips Book For iP...
Poole, United Kingdom based Tap Guides Ltd. has announced the release and immediate availability of iPhone 6s Tips & Tricks, an in-depth eBook available in the iBookstore that’s priced just $2.99... Read more
Apple refurbished 2014 13-inch Retina MacBook...
Apple has Certified Refurbished 2014 13″ Retina MacBook Pros available for up to $400 off original MSRP, starting at $979. An Apple one-year warranty is included with each model, and shipping is free... Read more
13-inch 2.5GHz MacBook Pro on sale for $994,...
Best Buy has the 13″ 2.5GHz MacBook Pro available for $994.99 on their online store. Choose free shipping or free instant local store pickup (if available). Their price is $105 off MSRP. Price valid... Read more

Jobs Board

*Apple* Retail - Multiple Positions (US) - A...
Sales Specialist - Retail Customer Service and Sales Transform Apple Store visitors into loyal Apple customers. When customers enter the store, you're also the Read more
iOS Passbook & *Apple* Pay Engineer - A...
…place. New in iOS 8, you can now add credit and debit cards to Passbook using Apple Pay. You can use Apple Pay in stores accepting contactless payments and in apps Read more
*Apple* Online Store UAT Lead - Apple (Unite...
**Job Summary** The Apple Online Store is a fast paced and ever evolving business environment. A UAT lead in this organization is able to have a direct impact on one of Read more
*Apple* Retail - Multiple Positions (US) - A...
Sales Specialist - Retail Customer Service and Sales Transform Apple Store visitors into loyal Apple customers. When customers enter the store, you're also the Read more
*Apple* Retail - Multiple Positions (US) - A...
Job Description:SalesSpecialist - Retail Customer Service and SalesTransform Apple Store visitors into loyal Apple customers. When customers enter the store, you're Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.