29

Converting a project from iOS5.0 to iOS7 / iOS6 on Xcode 5. The code below is giving a compile time warning:

'sizeWithFont:constrainedToSize:lineBreakMode:'is deprecated: first deprecated in ios 7.0 - Use - boundingRectWithSize:options:attribiutes:context

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
    if (indexPath.section == 0)
    {
        self.lblHidden.frame = CGRectMake(58, 228, 945, 9999);
        self.lblHidden.text = detailShareObj.pDesc;
        CGSize size = [detailShareObj.pDesc sizeWithFont:self.lblHidden.font constrainedToSize:self.lblHidden.frame.size lineBreakMode:NSLineBreakByWordWrapping];
        return 228.0+size.height+20;

    }
    else if (indexPath.section == 1)
    {
        NSString *tempPointStr = (self.shortDescArray)[indexPath.row];

        self.lblHidden.frame = CGRectMake(58, 0, 945, 9999);
        self.lblHidden.text = tempPointStr;
        CGSize size = [tempPointStr sizeWithFont:self.lblHidden.font
                               constrainedToSize:self.lblHidden.frame.size
                                   lineBreakMode:NSLineBreakByWordWrapping];

            return 50.0f;
    }

I tried some of the suggestion give elsewhere but nothing is up to rescue if some one can help by giving the corrections required in the code will be greatly appreciated.

iSrini
  • 905
  • 2
  • 9
  • 18

6 Answers6

64

I wouldn't just mask the deprecated function warning. They deprecated it for a reason. I believe the function was deprecated because that series of NSString+UIKit functions were based on the UIStringDrawing library, which wasn't thread safe. If you tried to run them not on the main thread (like any other UIKit functionality), you'll get unpredictable behaviors. In particular, if you ran the function on multiple threads simultaneously, it'll probably crash your app. This is why in iOS 6, they introduced a the boundingRectWithSize:... method for NSAttributedStrings. This was built on top of the NSStringDrawing libraries and is thread safe.

If you look at the new NSString boundingRectWithSize:... function, it asks for an attributes array in the same manner as a NSAttributeString. If I had to guess, this new NSString function in iOS 7 is merely a wrapper for the NSAttributeString function from iOS 6.

On that note, if you were only supporting iOS 6 and iOS 7, then I would definitely change all of your NSString's sizeWithFont:... to the NSAttributeString's boundingRectWithSize. It'll save you a lot of headache if you happen to have a weird multi-threading corner case! 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);
Mr. T
  • 12,795
  • 5
  • 39
  • 47
  • Yes ! I too think that masking the issue is not a solution. Let me try implementing your suggestion and post the modified code if possible. – iSrini Sep 25 '13 at 14:30
  • 11
    Slightly cleaner to use `CGRectIntegral(rect)` to ceiling the size rather than ceiling the height and width individually. – memmons Dec 04 '13 at 22:49
  • Definitely remember to implement either rounding function, as I was getting slightly truncated labels until doing so! – Evan R Jan 21 '14 at 00:09
  • new `boundingRectWitSize` always use line break rule as `NSLineBreakByClipping` when with `NSStringDrawingUsesLineFragmentOrigin`. So this returns incorrect size when we use other break rule. ex: `NSLineBreakByCharWrapping` – jeeeyul Aug 01 '14 at 06:45
44

If you want it compatible with both iOS7 and the versions below it, try this one (with ARC):

CGSize size;

if ([tempPointStr respondsToSelector:
     @selector(boundingRectWithSize:options:attributes:context:)])
{
  NSMutableParagraphStyle * paragraphStyle = [[NSMutableParagraphStyle alloc] init];
  paragraphStyle.lineBreakMode = NSLineBreakByWordWrapping;
  paragraphStyle.alignment = NSTextAlignmentLeft;

  NSDictionary * attributes = @{NSFontAttributeName : self.lblHidden.font,
                      NSParagraphStyleAttributeName : paragraphStyle};

  size = [tempPointStr boundingRectWithSize:self.lblHidden.frame.size
                                    options:NSStringDrawingUsesFontLeading
                                           |NSStringDrawingUsesLineFragmentOrigin
                                 attributes:attributes
                                    context:nil].size;
} else {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
  size = [tempPointStr sizeWithFont:self.lblHidden.font
                  constrainedToSize:self.lblHidden.frame.size
                      lineBreakMode:NSLineBreakByWordWrapping];
#pragma clang diagnostic pop
}

Note: It's just an example for your else-if case, maybe you need to do some modification depend on what you want it be. ;)

Kjuly
  • 34,476
  • 22
  • 104
  • 118
10

For iOS7, replace:

CGSize size = [tempPointStr sizeWithFont:self.lblHidden.font
                       constrainedToSize:self.lblHidden.frame.size
                           lineBreakMode:NSLineBreakByWordWrapping];

With:

NSMutableParagraphStyle *paragraphStyle = [[NSMutableParagraphStyle alloc]init];
paragraphStyle.lineBreakMode = NSLineBreakByWordWrapping; //set the line break mode
NSDictionary *attrDict = [NSDictionary dictionaryWithObjectsAndKeys:self.lblHidden.font, NSFontAttributeName, paragraphStyle, NSParagraphStyleAttributeName, nil];
CGSize size = [tempPointStr boundingRectWithSize:self.lblHidden.frame.size
                                         options:NSStringDrawingTruncatesLastVisibleLine|NSStringDrawingUsesLineFragmentOrigin
                                      attributes:attrDict context:nil].size;
Kevin
  • 16,696
  • 7
  • 51
  • 68
Dave Chambers
  • 2,483
  • 2
  • 32
  • 55
2

You can use:

UIFont *font = [UIFont boldSystemFontOfSize:16];

CGRect new = [string boundingRectWithSize:CGSizeMake(200, 300)
    options:NSStringDrawingUsesFontLeading 
    attributes:@{NSFontAttributeName: font} 
    context:nil];

CGSize stringSize= new.size;
Andrew Stubbs
  • 4,322
  • 3
  • 29
  • 48
user3575114
  • 993
  • 7
  • 13
1

If you're targeting iOS 6.0+, you can still use sizeWithFont:constrainedToSize:lineBreakMode:. Just make sure that your project's iOS Deployment Target is set for 6.0, and the compiler won't give you these warnings.

(You can find this by clicking on the blue project tab (usually at the top of the left, project navigator pane) within the "info" section).

If you're only targeting iOS 7.0+, you should use the new method boundingRectWithSize:options:attributes:context.

You can find the Apple docs on this new method here.

JRG-Developer
  • 12,454
  • 8
  • 55
  • 81
  • yeah sure the issue arrises only when we are doing it for iOS 7 and your suggestion is already suggested by the compiler itself. i need help on actually how to use boundingRectWithSize:options:attributes:context ! thanks any way for the suggestion. – iSrini Sep 25 '13 at 14:29
1

The boundingRectWithSize:options:attributes:context has the problem, that it does not calculates the height correctly if the String contains "\n" (line breaks). Therefore this code calculates the size for each line separately for a given width (inWidth):

NSArray *brokenByLines=[string componentsSeparatedByString:@"\n"];
CGFloat height=0.0;
CGFloat maxWidth=0.0;
for (NSString* actString in brokenByLines) {
    CGRect tSize=[actString boundingRectWithSize:CGSizeMake(inWidth, 600) options:(NSStringDrawingUsesLineFragmentOrigin | NSLineBreakByWordWrapping) attributes:@{NSFontAttributeName: inFont} context:nil];
    if (maxWidth<tSize.size.width) {
        maxWidth=tSize.size.width;
    }
    height+=tSize.size.height;
}
CGSize size= CGSizeMake(ceil(maxWidth), ceil(height));
EckhardN
  • 469
  • 5
  • 11
  • If you have a double line break (e.g. "\n\n"), then you will run into trouble because `boundingRectWithSize` does not properly size empty strings. I added `if ([actString isEqual: @""]) {height += fontSize) else{ }`. Note that this requires you to pass the font size as well. – BFar Feb 18 '15 at 02:58
  • I can't edit my previous comment, but I should mention that I multiply the fontSize by 1.125 for blank strings. I've observed that `boundingRectWithSize` does this to calculate the height of each line. – BFar Feb 18 '15 at 03:06