Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Replacement for sizeWithFont:ForWidth:lineBreakMode:

Many of the methods I had been using to determine layout of printed strings for creation of complex pdf documents have been deprecated in iOS7. Documentation calls out the same method to use as replacement for all the sizeWithFont methods that are deprecated:

boundingRectWithSize:options:attributes:

That is fine for sizeWithFont:ConstrainedTosize:lineBreakMode but what if I want my string on one line only? I don't know what to use for max height so I do not have a rect to hand over as a value for the first parameter.

Here is what I have when limiting to a given size.

CGFloat maxHeightAllowable = _maxHeight;
CGSize issueTitleMaxSize = CGSizeMake(_issueListTitleColWidth - (kColumnMargin *2), maxHeightAllowable);
NSDictionary *issueTitleAttributes = [NSDictionary dictionaryWithObjectsAndKeys:_bodyFont, NSFontAttributeName, nil];
CGRect issueTitleRect = CGRectIntegral([issueTitleText boundingRectWithSize:issueTitleMaxSize options:(NSStringDrawingUsesLineFragmentOrigin|NSStringDrawingUsesFontLeading) attributes:issueTitleAttributes context:nil]);
CGSize issueTitleSize = issueTitleRect.size;

How would I use this same method if I don't know the maxHeight, or actually, height for one line is exactly what I am trying to find out?

I see why they are pushing towards compatibility for the NSAttributed strings and auto layout but why deprecate these? The replacement, in my case, now takes 4 or 5 steps where it used to be 1 or 2.

Using the lineHeight property of font, as suggested by Mr T, I made these methods in a category that greatly simplifies my replacement.

#import "NSString+SizingForPDF.h"

@implementation NSString (SizingForPDF)

-(CGSize)integralSizeWithFont:(UIFont *)font constrainedToSize:(CGSize)maxSize
{
    NSDictionary *attributes = [NSDictionary dictionaryWithObjectsAndKeys:font, NSFontAttributeName, nil];
    CGRect rect = CGRectIntegral([self boundingRectWithSize:maxSize options:(NSStringDrawingUsesLineFragmentOrigin|NSStringDrawingUsesFontLeading) attributes:attributes context:nil]);
    return rect.size;
}

-(CGSize)integralSizeWithFont:(UIFont *)font maxWidth:(CGFloat)maxWidth numberOfLines:(NSInteger)lines
{
    if (lines == 0) {
        lines = 1;
    }
    NSDictionary *attributes = [NSDictionary dictionaryWithObject:font forKey:NSFontAttributeName];
    CGFloat height = font.lineHeight * lines;
    CGSize maxsize = CGSizeMake(maxWidth, height);
    CGRect rect = CGRectIntegral([self boundingRectWithSize:maxsize options:NSStringDrawingUsesLineFragmentOrigin|NSStringDrawingTruncatesLastVisibleLine|NSStringDrawingUsesFontLeading attributes:attributes context:nil]);
    return rect.size;
}

@end
like image 995
Dean Davids Avatar asked Sep 23 '13 00:09

Dean Davids


1 Answers

If you were just looking for the height of one line, couldn't you just use your font's lineHeight property? I use that to set the height of my labels or properly anticipate height of elements without any issues. I'm not certain if pdf documents are different in this regard.

Additionally, I believe those functions were deprecated because that series of NSString+UIKit functions (sizeWithFont:..., etc) 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.

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 easily 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);
like image 183
Mr. T Avatar answered Nov 07 '22 16:11

Mr. T