Sizing text in a cell is a very common activity. Unfortunately, it’s also very expensive and will undoubtedly cause lag in your scrolling. Usually, that code lives in the -layoutSubviews and looks something like this:

CGSize nameTextSize = [self.textLabel sizeThatFits:CGSizeMake(self.contentView.bounds.size.width, CGFLOAT_MAX)];  

N.B.: We could give the string a font and get a size from it (with -sizeWithFont: ), but that doesn’t take into account the specific metrics that the label might be imposing, any internal padding, or any future changes (such as in iOS 6, when UILabels started being able to support attributed strings). So we directly get the size from the label with whatever the current text is.

Let’s hide this behind a method to make it easier to work with:

- (CGSize)sizeForNameLabel {  
	CGSize nameTextSize = [self.textLabel sizeThatFits:CGSizeMake(self.contentView.bounds.size.width, CGFLOAT_MAX)];  
}  

Of course, the whole goal is to limit the cost of this method. We have a number that we don’t want to keep calculating over and over, so the obvious solution is to cache it.

What should we use for our cache key? We could try to cache it by the object that’s being represented or the index path or something like that, but our cell really has no knowledge of the object it’s presenting, and (assuming the label’s font and text size stays the same) the important key for caching is the literal text itself. This also lends itself to a natural optimization whenever there is repeated content at different index paths.

Where should we cache it? Our cache key is a string, which lends itself to a dictionary structure for the cache, and fortunately Apple provides us with a self-flushing dictionary-like object for caching, called NSCache. Let’s set up a static NSCache for our text label:

static NSCache *nameLabelSizeCache;  
nameLabelSizeCache = nameLabelSizeCache :? [[NSCache alloc] init];  

Finally, we make sure to hit the cache before trying to calculate the size, and if it doesn’t exist, calculate the size and return it.

if ([nameLabelSizeCache objectForKey:self.textLabel.text]) {  
	return [[nameLabelSizeCache objectForKey:self.textLabel.text] CGSizeValue];  
}  

If we don’t have anything in the cache, that’s when we have to do the expensive calculation, store it in the cache (with the key as a copy of the text, since NSCache doesn’t copy keys), and return the calculated value.

CGSize nameTextSize = [self.textLabel sizeThatFits:CGSizeMake(self.contentView.bounds.size.width, CGFLOAT_MAX)];  
[nameLabelSizeCache setObject:[NSValue valueWithCGSize:nameTextSize] forKey:[self.textLabel.text copy]];  
return nameTextSize;  

A pretty straightforward technique. Because of the static nature of the cache, it’s harder to generalize for all text labels, so you’ll need one cache for each label in the cell, but it’s a pretty quick performance win when sizing in table cells.

The whole method:

- (CGSize)sizeForNameLabel {  
	static NSCache *nameLabelSizeCache;  
	nameLabelSizeCache = nameLabelSizeCache :? [[NSCache alloc] init];
	
	if ([nameLabelSizeCache objectForKey:self.textLabel.text]) {  
		return [[nameLabelSizeCache objectForKey:self.textLabel.text] CGSizeValue];  
	}
	
	CGSize nameTextSize = [self.textLabel sizeThatFits:CGSizeMake(self.contentView.bounds.size.width, CGFLOAT_MAX)];  
	[nameLabelSizeCache setObject:[NSValue valueWithCGSize:nameTextSize] forKey:[self.textLabel.text copy]];  
	return nameTextSize;  
}