How to make a Scrolling Text View on Mac OS X

This post will show how to put together a horizontal scrolling Text View using Cocoa. This is a simple task that doesn’t require advanced Objective C and Cocoa knowledge.

Introduction

If you’ve tried to display information on the Mac OS X status bar then you know that space is limited and the available width changes based on the user’s installed apps.

A lot of applications show only an icon, that will become highlighted to notify the user when something happens, waiting then for a click or mouse hover to display a menu (some applications even have customized panels, usually mimicking Growl notifications).

There is (at least) one application that comes with Mac OS X that does a nice and simple work-around to this problem, scrolling text horizontally for the user, the VPN Status Notifier. When you connect it, it wides itself and then scrolls the states right to left until it establishes a connection.

A month ago I was looking for a way to do the same, I never liked those “Growl style” panels and I needed to show more than one simple icon, so scrolling text seemed to be the best idea. Unfortunately I couldn’t find a way to do this with NSTextField (I’m sure it can be made though, I was just lazy) so I made myself a custom NSView to solve this.

This is a very simple thing to make if you’ve been learning Cocoa for more than two weeks, nonetheless I decided to post it because I couldn’t find a “download, plug and play” ScrollingTextView online, and feel that there are people like me searching for it.

So Where Is The Download Button?

You can find FBScrollingTextView on Github. If you find something wrong with it and you would like to fix it just open an issue or a pull request.

To the Code

@interface FBScrollingTextView : NSView {   
    NSString *string;
    CGFloat scrollingSpeed;
    NSFont *font;
@private
    CGFloat refreshRate;
    NSTimer *tickTockStartScrolling;
    NSTimer *tickTockScroll;
    NSPoint cursor;
}
@property (readwrite, retain, nonatomic) NSFont *font;
@property (readwrite) CGFloat scrollingSpeed;
@property (readwrite, retain, nonatomic) NSString *string;
@end

As I stated before, FBScrollingTextView is a NSView and most of it’s variables are self explanatory. The default scrollingSpeed is 2, and I would recommend a value between 1 and 10.

The timer tickTockStartScrolling is just to introduce delay and tickTockScroll does the trick.

For example: when you set a string you wouldn’t want it to start scrolling right away as (depending on your scrollingSpeed) the user will not be able to read the first letter. tickTockStartScrolling starts tickTockScroll after a specific time (kFBScrollingTextViewStartScrollingDelay) so we have readable information.

- (void)scrollText {    
    cursor.x-=1;
    [self setNeedsDisplay:YES];
}

- (void)startScrolling {
    if (!tickTockScroll) {      
        tickTockScroll =
                [NSTimer scheduledTimerWithTimeInterval:refreshRate/scrollingSpeed
                            target:self
                            selector:@selector(scrollText)
                            userInfo:nil repeats:YES];
    }
    [tickTockStartScrolling release];
    tickTockStartScrolling = nil;
}

- (CGFloat)stringWidth {
    if (!string) return 0;
    NSSize stringSize = [string sizeWithAttributes:
        [NSDictionary dictionaryWithObjectsAndKeys:font,NSFontAttributeName,nil]];
    return stringSize.width;
}

-[FBScrollingTextView scrollText] is the method where we do the “scrolling”, it just decrements a cursor and calls setNeedsDisplay.

- (void)drawRect:(CGRect)rect {
    [super drawRect:rect];
    // Drawing code.
    CGFloat sWidth = round([self stringWidth]); 
    CGFloat rWidth = round(rect.size.width);
    CGFloat spacing = round(rWidth*kFBScrollingTextViewSpacing);
    
    if ((cursor.x*-1) == sWidth) {      
        CGFloat diff = spacing - (sWidth+cursor.x);
        cursor.x = rWidth-diff;     
    }
    
    NSDictionary *attrs = 
        [NSDictionary dictionaryWithObjectsAndKeys:font, NSFontAttributeName,nil];
    [string drawAtPoint:cursor withAttributes:attrs];
    
    CGFloat diff = spacing - (sWidth+cursor.x); 
    if (diff >= 0) {
        NSPoint point = NSMakePoint(rWidth-diff, cursor.y);
        [string drawAtPoint:point withAttributes:attrs];
    }
}

As you can see drawRect is where we do the magic, making some calculations to get to the point where the beginning of the string is supposed to be and then calling -[NSString drawAtPoint:withAttributes:] with that point.

Conclusion

The aim of this post was to show a very simple View that you can add to a NSStatusItem and just scroll the entire bible if you’re inclined.

Tweet me if you make something cool with it.

Hello World!

Hello World - BenjaminTsai

Two years ago I started developing my first iPhone application. Has I made my first steps into Cocoa and Objective C, reading Hillegass’s Cocoa Programming for Mac OS X , I was amazed not only with the book, which I highly recommend to beginners, but also with Cocoa. But after a while I was on my own and quickly realized that you can easily find help online about any easy task but if you’re trying to, for example, make a grid view on iOS you would need to search deeper (in this case I ended up using Jim Dovey’s AQGridView, you can find it forked at my Github). 

The point is that I’ve spent days, or even weeks, developing a specific feature or task. And every time I solve a problem I couldn’t find how to online I think: “What if I had a blog and published a small tutorial on how to solve this?”. You’re reading that blog.

Now, English is not my native language, so please be constructive if you’re commenting just to tell me that I wrote something wrong.

If you wish yo contact me, check the About Me page.

 

Hello World!