SMS Bubbles

bubblesTalking with Benoit Cerrina (of ShinKanji) about a related issue, I started to wonder how you could render “bubble” text, as for instance in the SMS application. It turns out to be pretty easy; all you need is a little source art, and some tuning parameters.

Prerendered

At first, I was looking at this problem in terms of the Shiny Red Button work I’d done earlier, but came to feel that this was misguided; SMS bubbles are basically stretched images, with fixed-height top and bottom caps, and fixed-width left and right caps, so they’re much better candidates for pre-rendering than the buttons we’ve seen before. (Also, their internal graphical structure looks a little more finicky.)

bubblesAccordingly, I pulled together the source image seen to the left by reverse-engineering a screenshot of the SMS application. It’s 30×42, with a 13-pixel top cap and a 24-pixel left cap. (This implies a 16-pixel bottom cap, a 17-pixel right cap, and that the 14th row and 25th column are stretched as necessary.)

Implementation

With this art stored safely in a file, we can use the following code to produce UIViews containing “bubble” text:

- (UIView*)makeBubbleWithWidth:(CGFloat)w font:(UIFont*)f text:(NSString*)s background:(NSString*)fn caps:(CGSize)caps padding:(CGFloat*)padTRBL
{
	// Create label
	UILabel* label = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, w, 1)];
	
	// Configure (for multi-line word-wrapping)
	label.font = f;
	label.numberOfLines = 0;
	label.lineBreakMode = UILineBreakModeWordWrap;
	
	// Set and size
	label.text = s;
	[label sizeToFit];
	
	// Size and create final view
	CGSize finalSize = CGSizeMake(label.frame.size.width+padTRBL[1]+padTRBL[3], label.frame.size.height+padTRBL[0]+padTRBL[2]);
	UIView* finalView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, finalSize.width, finalSize.height)];
	
	// Create stretchable BG image
	UIImage* bubble = [[UIImage imageNamed:fn] stretchableImageWithLeftCapWidth:caps.width topCapHeight:caps.height];
	UIImageView* background = [[UIImageView alloc] initWithImage:bubble];
	background.frame = finalView.frame;
	
	// Assemble composite (with padding for label)
	[finalView addSubview:background];
	[finalView addSubview:label];
	label.backgroundColor = [UIColor clearColor];
	label.frame = CGRectMake(padTRBL[3], padTRBL[0], label.frame.size.width, label.frame.size.height);
	
	// Clean and return
	[label release];
	[background release];
	return [finalView autorelease];
}

You can drive this code from a loadView such as the following, which produced the screenshot at the top of this article:

- (void)loadView
{
	// TL view
	self.view = [[UIView alloc] initWithFrame:CGRectZero];
	self.view.backgroundColor = [UIColor colorWithRed:219/255.0 green:226/255.0 blue:237/255.0 alpha:1];
	
	// Settings
	NSString*	s;
	NSString*	art		= @"bubble.png";
	CGSize		caps		= CGSizeMake(24, 13);
	UIFont*		font		= [UIFont systemFontOfSize:16];
	CGFloat		padTRBL[4]	= {4, 16, 7, 22};
	UIView*		bubble;

	// Create bubble
	s = @"Here's a little bubble";
	bubble = [self makeBubbleWithWidth:220 font:font text:s background:art caps:caps padding:padTRBL];
	bubble.frame = CGRectMake(0, 10, bubble.frame.size.width, bubble.frame.size.height);
	[self.view addSubview:bubble];

	s = @"Here's a longer one, away from the margins";
	bubble = [self makeBubbleWithWidth:80 font:font text:s background:art caps:caps padding:padTRBL];
	bubble.frame = CGRectMake(200, 20, bubble.frame.size.width, bubble.frame.size.height);
	[self.view addSubview:bubble];

	s = @"Bubbles respect\nnewlines\nin\nthe\nsource text ...";
	bubble = [self makeBubbleWithWidth:180 font:font text:s background:art caps:caps padding:padTRBL];
	bubble.frame = CGRectMake(0, 60, bubble.frame.size.width, bubble.frame.size.height);
	[self.view addSubview:bubble];

	s = @"Andnormallyaddlinebreaksonlyinbetween words, unless a really long word forces them to do otherwise";
	bubble = [self makeBubbleWithWidth:280 font:font text:s background:art caps:caps padding:padTRBL];
	bubble.frame = CGRectMake(0, 200, bubble.frame.size.width, bubble.frame.size.height);
	[self.view addSubview:bubble];
}

Cheats

The one huge cheat in this code is that the bubble art is pre-blended with a particular background color (R:219/G:226/B:237); there’s no translucency or alpha-blending here, and if these bubbles were laid over one another, or over a more interesting background, they wouldn’t look right. That problem seems pretty fixable, though.

Also, the cap and padding parameters are pretty implementation-specific: The caps depend upon the source art, and the padding depends upon almost everything (source art, UILabel settings, font, &c).

Share and Enjoy:
  • Twitter
  • Facebook
  • Digg
  • Reddit
  • HackerNews
  • del.icio.us
  • Google Bookmarks
  • Slashdot
This entry was posted in iPhone. Bookmark the permalink.