Using any UIStringDrawing methods on two threads simultaneously causes a crash. My understanding was that all UIStringDrawing methods were thread safe from iOS 4.0.
While trying to find a work around I noticed that iOS 6 introduces much more widespread integration of NSAttributedString and Core Text so I tried swapping all UIStringDrawing methods with the equivalent NSStringDrawing methods using NSAttributedString in place of NSString and it seems the crashes have stopped.
For example, I'm now using:
NSAttributedString *attribStr = [[NSAttributedString alloc] initWithString:@"My String"];
CGSize size = [attribStr size];
instead of:
NSString *str = @"My String";
CGSize size = [str sizeWithFont:font];
Adam is correct. UIStringDrawing methods are only safe to use from the main queue on iOS 6. You can either use NSStringDrawing methods or CoreText directly to perform rendering from background queues. This is a known issue, but feel free to file more bugs.
Adam Swinden's solution worked for me. Here's how I converted NSString's sizeWithFont:constrainedToSize:
:
What used to be:
NSString *text = ...;
CGFloat width = ...;
UIFont *font = ...;
CGSize size = [text sizeWithFont:font
constrainedToSize:(CGSize){width, CGFLOAT_MAX}];
Can be replaced with:
NSString *text = ...;
CGFloat width = ...;
UIFont *font = ...;
NSAttributedString *attributedText =
[[NSAttributedString alloc]
initWithString:text
attributes:@
{
NSFontAttributeName: font
}];
CGRect rect = [attributedText boundingRectWithSize:(CGSize){width, CGFLOAT_MAX}
options:NSStringDrawingUsesLineFragmentOrigin
context:nil];
CGSize size = rect.size;
Please note the documentation mentions:
In iOS 7 and later, this method returns fractional sizes (in the size component of the returned CGRect); to use a returned size to size views, you must use raise its value to the nearest higher integer using the ceil function.
So to pull out the calculated height or width to be used for sizing views, I would use:
CGFloat height = ceilf(size.height);
CGFloat width = ceilf(size.width);
Based on the Adam Swinden's and Mr T's answers I wrote 2 drop-in methods:
@implementation NSString (Extensions)
- (CGSize)threadSafeSizeWithFont:(UIFont *)font {
return [self threadSafeSizeWithFont:font constrainedToSize:CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX)];
}
- (CGSize)threadSafeSizeWithFont:(UIFont *)font constrainedToSize:(CGSize)size {
// http://stackoverflow.com/questions/12744558/uistringdrawing-methods-dont-seem-to-be-thread-safe-in-ios-6
NSAttributedString *attributedText =
[[NSAttributedString alloc]
initWithString:self
attributes:@
{
NSFontAttributeName: font
}];
CGRect rect = [attributedText boundingRectWithSize:size
options:NSStringDrawingUsesLineFragmentOrigin
context:nil];
return rect.size;
}
@end