Search code examples
objective-ccocoaitunesnstextfieldnsscrollview

iTunes Song Title Scrolling in Cocoa


I have searched extensively and cannot for the life of me find any information about how to achieve a similar effect to that of the iTunes song title scrolling if the text is too large in Cocoa. I have tried setting the bounds on a NSTextField to no avail. I have tried using NSTextView as well as various attempts at using NSScrollView. I am sure I am missing something simple but any help would be greatly appreciated. I am also hoping to not have to use CoreGraphics if at all possible.

Example, notice the "Base.FM http://www." text has been scrolled. If you need a better example open iTunes with a song with a rather large title and watch it scroll back and forth.

I would think surely there is a simple way to just create a marquee type effect with an NSTextField and an NSTimer, but alas.


Solution

  • I can see how this would be difficult if you're trying to shoehorn the functionality into an exist control. However, if you just start with a plain NSView, it's not that bad. I whipped this up in about 10 minutes...

    //ScrollingTextView.h:
    #import <Cocoa/Cocoa.h>
    @interface ScrollingTextView : NSView {
        NSTimer * scroller;
        NSPoint point;
        NSString * text;
        NSTimeInterval speed;
        CGFloat stringWidth;
    }
    
    @property (nonatomic, copy) NSString * text;
    @property (nonatomic) NSTimeInterval speed;
    
    @end
    
    
    //ScrollingTextView.m
    
    #import "ScrollingTextView.h"
    
    @implementation ScrollingTextView
    
    @synthesize text;
    @synthesize speed;
    
    - (void) dealloc {
        [text release];
        [scroller invalidate];
        [super dealloc];
    }
    
    - (void) setText:(NSString *)newText {
        [text release];
        text = [newText copy];
        point = NSZeroPoint;
    
        stringWidth = [newText sizeWithAttributes:nil].width;
    
        if (scroller == nil && speed > 0 && text != nil) {
            scroller = [NSTimer scheduledTimerWithTimeInterval:speed target:self selector:@selector(moveText:) userInfo:nil repeats:YES];
        }
    }
    
    - (void) setSpeed:(NSTimeInterval)newSpeed {
        if (newSpeed != speed) {
            speed = newSpeed;
    
            [scroller invalidate];
            scroller == nil;
            if (speed > 0 && text != nil) {
                scroller = [NSTimer scheduledTimerWithTimeInterval:speed target:self selector:@selector(moveText:) userInfo:nil repeats:YES];
            }
        }
    }
    
    - (void) moveText:(NSTimer *)timer {
        point.x = point.x - 1.0f;
        [self setNeedsDisplay:YES];
    }
    
    - (void)drawRect:(NSRect)dirtyRect {
        // Drawing code here.
    
        if (point.x + stringWidth < 0) {
            point.x += dirtyRect.size.width;
        }
    
        [text drawAtPoint:point withAttributes:nil];
    
        if (point.x < 0) {
            NSPoint otherPoint = point;
            otherPoint.x += dirtyRect.size.width;
            [text drawAtPoint:otherPoint withAttributes:nil];
        }
    }
    
    @end
    

    Just drag an NSView onto your window in Interface Builder and change its class to "ScrollingTextView". Then (in code), you do:

    [myScrollingTextView setText:@"This is the text I want to scroll"];
    [myScrollingTextView setSpeed:0.01]; //redraws every 1/100th of a second
    

    This is obviously pretty rudimentary, but it does the wrap around stuff that you're looking for and is a decent place to start.