Friday, October 30, 2009

A simple scrolling UILabel for iPhone devs

Here's a simple class which mimics the most common features of a UILabel, yet will act like a horizontally scrolling marquee. It is actually a subclass of UIScrollView, so to use it with Interface Builder, you will need to create a UIScrollView item and then change its class to this class - which I called AutoScrollLabel.

If you don't care about the technical details of how it works and just want to use it, you can download the class files here:

http://stormyprods.com/sampleCode/AutoScrollLabel.zip

To use the class, simply set the text and if the text is wider than the allocated space in the view, it will automatically scroll. Here's a very simple example:


autoScrollLabel.text = @"Hi Mom! How are you? I really ought to write more often.";
autoScrollLabel.textColor = [UIColor yellowColor];


The class also supports the font property, so you can set the font as you normally would for a UILabel.

In addition, there are a few properties related to the scrolling:

scrollDirection: set to AUTO_SCROLL_LEFT (default) or AUTO_SCROLL_RIGHT
scrollSpeed: set to the number of pixels per second (default is 30)
pauseInterval: set to the number of seconds to pause when the end of the text is reached. (default is 0.5)
bufferSpaceBetweenLabels: how much blank space between the end of the text and the beginning of the next instance

It turns out this autoscrolling feature was fairly easy to implement. I'm surprised there isn't already something like this in the SDK. The scrolling is implemented using two UILabels contained in a UIScrollView. Each label contains an identical copy of the text and the labels are resized to be just large enough to hold the text without any truncation.

If the text in the UILabel is wider than the containing UIScrollView, then the two UILabels are laid out side-by-side, and the scroll view is animated to scroll from the beginning of one label to the beginning of the next label. The second label is there just to give the illusion of the text looping forever.

The class registers itself as the delegate for the scrolling animation block, so when the animation block ends, it waits the defined pause time, and then just performs another animated scroll, in which case the scroll view is reset back to the beginning of the first label and the process is repeated.

And that's it! Pretty simple. The animation block feature of a UIView does all the hard work.

I hope others find this useful. If you notice any problems in the code, please let me know.

44 comments:

Keyvisuals said...

Very cool and so easy to implement! I can't wait to use this in a project. Thanks for sharing.

G. Crisp said...

I'm attempting to use this in a project, but it doesn't seem to be working. The class gets instantiated just fine, and the methods are being called, but I see no text.

Can you post some sample code for how to use this class? Thanks!

Mostly Torn said...

The default text color in the class is WHITE, so if you are doing this with a white background , you'll need to change the text color to something else, otherwise you won't see it.

If this is not the problem you are encountering, please let me know and I'll post a sample project demonstrating it's use. Probably won't have time to do it today, though.

Rahul Vyas said...

outstanding work.i will use it in my future apps.thanks for creating this.

Nick Dalton said...

Worked right out of the box. Thank you!

Simon said...

Hi there, for me it's working great, but the text won't change the font color. I already did myLabel.textColor = [UIColor blackColor]; but it isn't working. Also is there a way to do a dropshadow, like you can do with the UILabel?

Nick Dalton said...

I just noticed something "interesting" on 2.x devices: The AutoScrollLabel fails to display anything on screen if the text string is too long.

The limit seems to be somewhere around 2000 pixels width for each UILabel. I'm not sure if the size limitation is in UILabel or UIScrollView. In any case it's a OS 2.x limitation and nothing is wrong with the AutoScrollLabel component.

Leaving this comment here to hopefully save anybody else a few hours of debugging time...

Anonymous said...

Super! Was looking for exactly this! Thanks for this!

necronomicon said...

there's an issue with the pause; using sleepForTimeInterval does freeze the whole application. (That's what happened to me at least). Here's a solution: change
- (void)animationDidStop:(NSString *)animationID finished:(NSNumber *)finished context:(void *)context
{

[NSThread sleepForTimeInterval:pauseInterval];
[NSTimer

if ([finished intValue] == 1 && label[0].frame.size.width > self.frame.size.width){
[self scroll];
}
}

with
- (void)}animationDidStop:(NSString *)animationID finished:(NSNumber *)finished context:(void *)context
{
[NSTimer scheduledTimerWithTimeInterval:pauseInterval target:self selector:@selector(scroll) userInfo:nil repeats:NO];
}

Mostly Torn said...

Thanks for the tip Necronomicon.

I've updated the code so it no longer blocks in the foreground thread.

Giovambattista Fazioli said...

Hi, with xcode 3.2.2, using IB crash:
*** Terminating app due to uncaught exception 'NSUnknownKeyException', reason: '[ setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key autoScrollLabel.'

Anonymous said...

'm trying to use this, but it doesn't seem to be working. The class gets instantiated just fine, and the methods are being called, but I see no text.

Can you post some sample code for how to use this class? Thanks!

Pau said...

If I has an AutoScrollLabel, and I push a modalViewController, like the Camera picker or any view with presentModalViewController. When I dismiss the modalView, the AutoScrollLabel is stopped instead of rolling.

Tried calling the scroll method, but that way always rolls, short labels too.

Any clue how to avoid that?

Anonymous said...

If anyone has the same problem, I resolve it calling setText method again once I dismiss the modalView.

adi said...

great program ..Thank you very much..your code help me lot..

adi said...

Hi..
i want to use this code into my application where i am already using run time scroll view.as i tried to integrate both scroll view together then UILabel scroll view which you created geting shrink from both side whenever i run this code.
help me out from this situation.
thanks in advance.

Anonymous said...

Thanks a lot, so easy to integrate !

dongduong said...

Very useful, thank a lot :))

Anonymous said...

This works pretty good, except when I change the orientation of the iPad. WHen I rotate to landscape, the text scrolls off of what should be the new uiscrollview boundaries. So, on a split view the text scrolls over and onto the root controller. Also, when the device is rotated, the scrolling stops. One feature that would be cool would be to give the scroller an NSArray of strings. Scroll one string.. pause... scroll #2.. pause, scroll 3.. pause, then back to #1.

DadGuy said...

Hey, thanks a bunch for posting this. I might have to add a "bounce" marquee scroll where it goes to the end and back, but other than that I think this will work perfectly for what I need. Thanks much!

e.dolecki said...

This thing was to auto-center the text if it's not wide enough to cause the scroll.

e.dolecki said...

If you want left-alignment for text that fits:

// Hide the other labels out of view
for (int i = 1; i < NUM_LABELS; ++i){
label[i].hidden = YES;
}
// Center this label
//CGPoint center;
//center = label[0].center;
//center.x = self.center.x - self.frame.origin.x;
//label[0].center = center;

Elias said...

Found a leakage in CGGLYPHBITMAPCREATE frame when I ran this code on my device.

The solution was to synthesize and release the textColor and font variables. Also, I had to change the attribute of the setFont method.

Specifically:

Added the following code:

@synthesize textColor, font;

- (void)dealloc {
for (int i=0; i<NUM_LABELS; ++i){
[label[i] release];
}

[textColor release];
[font release];
[super dealloc];
}

Modified the font attribute of this method:

- (void) setFont:(UIFont *)font_
{
for (int i=0; i<NUM_LABELS; ++i){
label[i].font = font_;
}
[self readjustLabels];
}

hamjoon said...

thank you very much! It helps me lot.

priya said...

awesome code ..thanks :)

priya said...

Awesome code... thanks a lot :)

Amit said...

AutoScrollLabel is really awesome and very easy to use.
But if we use AutoScrollLabel in a project, will it be acceptable by Apple in App Store??

Kriviq said...

@Amit There is no problem in using this class within your project, as it will be accepted by App Store.

To everyone else I just want to add that if you want to change the frame of the label you need to add this method to the class:

- (void)setFrame:(CGRect)frame{
[super setFrame:frame];
[self readjustLabels];
}

Alex Muller said...

I have noticed that when using this AutoScrollLabel, in a box that is 230 by 15, and using a white font of size 14, the font begins to look blury..any fix for this?

A. Lomas said...

One problem I had with this code was that upon returning from multitasking, the AutoScrollLabel had frozen. After many hours of tinkering, I finally got it working by using this simple code.

In the viewDidLoad of the .m file where your AutoScrollLabel is defined put the following:

[[NSNotificationCenter defaultCenter]addObserver:self selector:@selector(didEnterForeground:)
name:@"UIApplicationWillEnterForegroundNotification" object:nil];

The code above calls the selector when returning from multitasking. You now need to add the void for that selector in the same .m file so we can unfreeze the AutoScrollLabel. This is done as follows:

- (void) didEnterForeground: (NSNotification*) sender;
{
NSLog(@"Entered foreground");

autoScrollLabel.text = autoScrollLabel.text;
}

And that's it. I hope this helps anyone who stumbles across this problem. Seeming this post is very old, it's highly unlikely, but hey, there you go. :)

DG said...

So cool! And easy to implement. Still works on iOS 5. Impressive :). Only thing is, when switching from landscape to portrait, it stops moving, even if it's only halfway through. My app will actually be portrait only, but it might be useful if you're still developing it to try to fix it. Thanks so much! I really appreciate easy, free, and great code like this. Any idea from anyone how to implement a fade on either side of the label? Images?

Dimitris said...

if you hate blurry text just found the .x values at the readjustLabels and make them round...

for example —

// Recenter label vertically within the scroll view
CGPoint center;
center = label[i].center;
center.y = roundf ( self.center.y - self.frame.origin.y );
label[i].center = center;

Don said...

So great!! This is what i'm looking for. Is there anyway that I can put an event when the user taps the scrolling label? I tried touchesBegan but it wont trigger. I little help will be greatly appreciated.

Cheers!

Mostly Torn said...

Don,

A simple way to detect taps would be to overlay the scrolling label with and invisible button. It's not nicely integrated as one object but it does work.


Also, you may be able to change the attributes of the label view so "user interaction" is enabled which may then allow you to catch the touch events.

Cheers, Brian

Don said...

Mostly Torn,

Thanks for the help. I did exactly what you said with the invisible button and it works like a charm.

Thanks again!

Don said...

I don't know if I'm doing the right thing but I tried to contain the scrolling label inside a UIView but the text still show outside the UIView's bounds. I also tried to set the frame of the scrolling label but still no luck. Anybody had the dilemma?

Cheers!

Rohit Dhawan said...

i solved my earlier problem by reading a earlier comment ,my new question is that how we can start or stop scrolling.

Anonymous said...

Great tutorial, very helpful. Thank you for taking the time to create this blog and sharing your code. I am having a problem using it in my app though. Its probably the same problem Pau had, because everything seems working good until I push a new modal view. When i go back, the label isn't moving anymore. I don't understand how to call the setText method again, as Pau is suggesting. Any help or example would be greatly appreciated.

C. Bess said...

I grabbed the code and updated it:
https://github.com/cbess/AutoScrollLabel

It has been modernized for iOS 4+

thomas_itbox said...

hy guye, first i want to say thanks for the code. its really simple to use. here is my issue:

when i run the "analyze" tool from xcode i get multiple memory management warnings like:
Returning "self" while it is not set to the result of "[(super or self) init...]" this message shows up at the methods:
init
initWithCoder
and initWithFrame:

can i just ignore them or will apple reject my app for this.

with kind regards,

thomas from austria

CraigW said...

Thanks for this. Very useful. Test still looks a bit blurry though on the non retina screen. I have had a look to see if I can use the round function that Dimitris mentioned, but as it's now using UIView animations I'm not sure if anything can be done about this.

Any suggestions?

Carl S said...

Well, it's a year after the last person left a comment, but this code is still ROCKIN'!!!

SIMPLE steps to make it work:

1. Added the .h and .m to my project
2. Replaced my UILabel with an AutoScrollLabel in Interface Builder
3. Commented out the dealloc function in the .m, since I'm using ARC
4. Used the setFont call to change the font to what I wanted
5. DONE!

This class filled a need for a current (music player) project, and I'm confident I'll find more uses for it in the future.

Many Thanks!

Anonymous said...

Thanks - this looks quite neat. I want to use it in a Navigation Bar - but when I add it as a subview ([self.navigationController.navigationBar addSubview:self.autoScrollLabel];), I notice it doesn't animate as part of push segue. Does anyone know why that is or if there is a way around that? I'm currently hacking viewWillDisappear to get quickly fade it out there.

David DelMonte said...

Still great after 5+ years.. FYO, I'm using this as the second line in a label thats embedded in a UIPicker view - that is itself embedded in a UIScrollView.. I didn't expect it to work... But it does. Amazing.. well done..


All content copyright © 2009  Brian Stormont, unless otherwise noted.   All rights reserved.