Interactive Rich Text UILabel

4 min read

I see lots of posts asking about the existence of a clickable, rich text UILabel and the answer is often the same…. it’s not possible so use a UIWebView…. NOOOOOOO! The idea of contemplating using such a heavyweight control for a purpose for which is was not designed is a tad crazy. So I decided to write a component to solve the issue and make it flexible to boot. Let’s dive in!

 

The Basics

This component consists of two parts, the label and the sty le. The label is a relatively straightforward UILabel descendent and the style object is a simple class holding the relative properties for a certain style, of which we can have any number. So let’s start with the style.

The style contains the following properties:

[objc] UIFont *font;
UIColor *color;
id target;
SEL action;
[/objc]

 

It contains the font to use, the colour to use and an optional target and actio n. If these optional parameters are specified then you will be able to tap the text and call the relevant action, otherwise the text will merely be styled.

The second part is the label. This is a bit more complicated, but luckily the full source code is in the link above. The label maintains a list of style objects for a given prefix, so we can style ‘#’ elements, ‘@” elements, ‘http://’ elements etc. It contains a method to associate a style to a prefix as follows….

[objc] – (void)addStyle:(LORichTextLabelStyle *)aStyle forPrefix:(NSString *)aPrefix {
if((aPrefix == nil) || (aPrefix.length == 0)) {
[NSException raise:NSInternalInconsistencyException format:@"Prefix must be specified in %@", NSStringFromSelector(_cmd)];
}

[highlightStyles setObject:aS tyl e fo rKey:aPrefix];
}
[/objc]

 

Nice and simple. There are overridden methods for the setFont, setColour and setText which set the base font and colour and notify the OS that the label needs to be redrawn. The setText method also splits the text into elements which are used in the layout code later on.

[objc] – (void)setFont:(UIFont *)value {
if([font isEqual:value]) {
return;
}

[font release];
font = value;
[font retain];

[self setNeedsDisplay];
}

– (void)setTextColor:(UIColor *)value {
if([textColor isEqual:value]) {
return;
}

[textColor release];
textColor = value;
[textColor retain];

[self setNeedsLayout];
}

– (void)setText:(NSString *)value {
[elements release];
elements = [value componentsSeparatedByString:@" &quo t;];
[elements retain];

[self setNeedsLayout]; < br> }
[/ obj c] < p> 

Amaz ing Layout

The only other method in the label is the layout method, and this is where the magic happens. First we remove all subview as we will be creating them all again in a second. We then iterate our elements and process each one, maintaining a layout position as we go. We look for a style matching the prefix of the current element, or the base style if none is specified. We size the element and render it in position with the specified style. If the element has a target we use a UIButton, if not then a UILabel. The full code is as follows…

[objc]

– (void)layoutSubviews {
[self removeSubviews];

NSUInteger maxHeight = 999999;
CGPoint position = CGPoint Zero;
CGSize measureSize = CGSizeM ake(self.size.width, maxHeight);

for(NSString *element in elements) {
LORichTextLabelSt yle *style = nil;

// Find suitable style
for(NSString *prefix in [highl ightStyles allKeys]) {
if([element hasPrefix:prefix]) {
style = [highlightStyles objectForKey:prefix];
break;
}
}

UIFont *styleFont = style.font == nil ? fon t : style.font;
UIColor *styleColor = style.color == nil ? textColor : sty le.color;

// Get size of content (check current line before sta rting new one)
CGSize remainingSize = CGSizeMake(measureSize.width – position.x, maxHeight);
CGSize singleLineSize = CGSizeMake(remainingSize.width, 0.0);

CGSize controlSize = [element sizeWithFont:styleFon t constrainedToSize:singleLineSize lineBreakMode:UILineBreakModeTailTruncation];
CGSize elementSize = [element sizeWithFont:styleFont c onstrainedToSize:remainingSize];

if(elementSize.height &amp;gt; controlSize.he ight) {
position.y += controlSi ze. height;
position.x = 0.0;
}

elementSize = [ele ment sizeWithFont:sty leFont constrainedToSize:measureSize];
CGRect elementFrame = CGRectMake(position.x, pos ition.y, elementSize.width, elementSize.height);

// Add button or label depending on whether we have a target
if(style.target != nil) {
UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
Button Text;
Button Text;
Button Text;
Button Text;
Button Text;
[self addSubview:button];
} else {
UILabel *label = [[UILabel alloc] initWithFrame:elementFrame];
[label setBackgroundColor:[UIColor clea rColor]];
[label setNumberOfLines:maxHeight];
[label setFont:styleFont];
[label setTextColor:styleColor];
[label setText:element];
[self addSubview:label];
}

CGSize spaceSize = [@&a mp;amp;quot; &amp;quot; sizeWithFont :st yleFont];< br> position.x += elementSize.wi dth + spaceSize.width;

if([element isEqual:[el ements lastObject]]) {
position.y += controlSize.height;
}
}

[self setSize: CGSizeMake(self.size.width, position.y)];
}
[/objc]

 

In Use

To u se the label we need to create some style, add them to the label and set the text accordingly. We can create any number of styles and associate them with specific prefixes. Let’s look at how we would add styles for a hashtag…

[objc] // Create a hashtag style
LORichTextLabelStyle *hashStyle = [LORichTextLabelStyle styleWithFont:[UIFont fontWithName:@&amp;quot;Helvetica-Bold&a mp; quot ; size:14.0] color :[U IColor magentaColor]];
[hashStyle addTarget:self action:@selector(hashSelected:)];

// Create the label with the default font and colour for the specified width
LORichTextLabel *label = [[LORichTextLabel alloc] initWithWidth:300.0];
[label setFont:[UIF ont fontWithName:@&amp;quot;Helvetica& amp ;quo t; size:14.0] ];< br> [label setTex tColor:[UIColor blackColor]];
[label setBa ckgroundColor:[UIColor clearColor]];

// Add the style to the label for the ‘#’ prefix
[label addStyle:hashStyle forPrefix:@&amp;quot;#& amp ;quo t;];< /p>

// Set the text of the label
[label setText:@&amp;quot ;Th is i s an example of a rich text UILabel class highlighting #hashtags pretty easily!&amp;q uot ;];< b r> [/objc]

 < /p>

This renders the text as per the style and fires the hashSelected: selector when the tag is clicked. But what does that return? Well the tag is a UIButton so it follows the standard signature, and the title of the button is the tag itself!

[objc] – (void)hashSelected:(id)sender
NSString *tag = ((UIButton *)sender).titleLabel.text;
}
[/objc]

 

There you have it. I will leave it to you to look at how to improve this to use the minimum number of UILabel components as it gets a bit more complex but this should be enough to get you started. UIWebviews?! Pah.

Subscribe To Our Blog