Как сделать сжатие текста NSStringDrawingContext?

Я пытаюсь использовать атрибутный API-интерфейс IOS 6 для вычисления размера текста и при необходимости уменьшить размер шрифта. Однако я не могу заставить его работать, как говорится в документации.

NSString *string = @"This is a long text that doesn't shrink as it should";

NSStringDrawingContext *context = [NSStringDrawingContext new];
context.minimumScaleFactor = 0.5;

UIFont *font = [UIFont fontWithName:@"SourceSansPro-Bold" size:32.f];

NSMutableParagraphStyle *paragraphStyle = [NSMutableParagraphStyle new];
paragraphStyle.lineBreakMode = NSLineBreakByClipping;

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

NSAttributedString *attributedString = [[NSAttributedString alloc] initWithString:self.title attributes:attributes];

CGRect rect = [attributedString boundingRectWithSize:CGSizeMake(512.f, 512.f) options:NSStringDrawingUsesLineFragmentOrigin context:context];

NSLog(@"rect: %@, context: %@", NSStringFromCGRect(rect), context.debugDescription);

Но текст не сжимается и усекается. actualScaleFactor всегда 1. Результаты журнала:

rect:{{0, 0}, {431.64801, 80.447998}}, context:<NSStringDrawingContext: 0x14e85770> minimumScaleFactor:0.500000 minimumTrackingAdjustment:0.000000 actualScaleFactor:1.000000 actualTrackingAdjustment:0.000000 totalBounds:{{0, 0}, {431.64801, 80.447998}}

Результат будет таким же, если я использую фактический метод рисования, а не метод измерения. Если я удаляю стиль абзаца, он обертывает текст и не сокращает его. Если я удаляю стиль абзаца, и я выбираю размер, который допускает только одну строку текста, текст также усекается, а не сокращается. Что не так? Существует очень мало документации или онлайн-ресурсов, посвященных NSStringDrawingContext. И я стараюсь избегать использования sizeWithFont:minFontSize:actualFontSize:forWidth:lineBreakMode:, который устарел в iOS 7.

Ответ 1

NSStringDrawingContext minimumScaleFactor кажется, сломан в iOS 7.

Насколько я понимаю, даже в iOS 6 он не работал для рисования; он работал для измерения, так что вы можете понять, что произойдет в контексте, где он работает для рисования, как UILabel. Таким образом, вы знаете правильную минимальную высоту для этикетки.

Или вы можете использовать полученный масштабный коэффициент, чтобы уменьшить текст самостоятельно, зная, что теперь он будет соответствовать.

Пример:

- (void)drawRect:(CGRect)rect
{
    // rect is 0,0,210,31

    NSMutableAttributedString* s = 
        [[NSMutableAttributedString alloc] initWithString: @"This is the army Mister Jones."];
    [s addAttributes:@{NSFontAttributeName:[UIFont fontWithName:@"GillSans" size:20]} 
        range:NSMakeRange(0,s.length)];

    NSMutableParagraphStyle* para = [[NSMutableParagraphStyle alloc] init];
    para.lineBreakMode = NSLineBreakByTruncatingTail;
    [s addAttributes:@{NSParagraphStyleAttributeName:para} 
        range:NSMakeRange(0,s.length)];

    NSStringDrawingContext* con = [[NSStringDrawingContext alloc] init];
    con.minimumScaleFactor = 0.5;

    CGRect result = 
        [s boundingRectWithSize:rect.size 
            options:NSStringDrawingUsesLineFragmentOrigin context:con];
    CGFloat scale = con.actualScaleFactor;
    // ...(could have a check here to see if result fits in target rect)...

    // fix font to use scale factor, and draw
    [s addAttributes:@{NSFontAttributeName:[UIFont fontWithName:@"GillSans" size:20*scale]} 
        range:NSMakeRange(0,s.length)];
    [s drawWithRect:rect options:NSStringDrawingUsesLineFragmentOrigin context:nil];

}

В iOS 6 scale составляет около 0,85, и вы можете использовать его, как показано, чтобы уменьшить текст. Но в iOS 7 scale остается равным 1, что позволяет предположить, что сжатия не происходит, и эта функция NSStringDrawingContext теперь бесполезна. Я не могу сказать, является ли это ошибкой, или эта функция была намеренно оставлена.

Ответ 2

После долгого поиска в Google я не нашел решения, работающего под iOS7. Сейчас я использую следующее обходное решение, зная, что он очень уродлив. Я визуализирую UILabel в памяти, сделаю скриншот и нарисую это. UILabel может правильно сжимать текст.

Но, возможно, кто-то сочтет это полезным.

    UILabel *myLabel = [[UILabel alloc] initWithFrame:myLabelFrame];
    myLabel.font = [UIFont fontWithName:@"HelveticaNeue-BoldItalic" size:16];
    myLabel.text = @"Some text that is too long";
    myLabel.minimumScaleFactor = 0.5;
    myLabel.adjustsFontSizeToFitWidth = YES;
    myLabel.backgroundColor = [UIColor clearColor];

    UIGraphicsBeginImageContextWithOptions(myLabelFrame.size, NO, 0.0f);
    [[myLabel layer] renderInContext:UIGraphicsGetCurrentContext()];
    UIImage *screenshot = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();

    [screenshot drawInRect:myLabel.frame];

Ответ 3

Просто хотел опубликовать это решение, поскольку я боролся с этим в течение нескольких часов. Я никогда не смог бы получить область текста, чтобы уменьшить масштаб, и всегда будут отключены последние строки текста.

Решение для меня состояло в том, чтобы не добавлять контекст и просто устанавливать его на нуль. Я получил это, глядя на пример на этом сайте. https://littlebitesofcocoa.com/144-drawing-multiline-strings

Обратите внимание, что после получения размера, который будет рисоваться при использовании

pageStringSize = [myString boundingRectWithSize:maxSize options:NSStringDrawingUsesLineFragmentOrigin attributes:attrsDictionary context:nil];

Мне все еще нужно было вручную прокручивать шрифт вручную:

while (pageStringSize.size.height > 140 && scale > 0.5) {
            scale = scale - 0.1;
            font = [UIFont fontWithName:@"Chalkboard SE" size:24.0 *scale];
            attrsDictionary = [NSDictionary dictionaryWithObjectsAndKeys:font, NSFontAttributeName,[NSNumber numberWithFloat:1.0], NSBaselineOffsetAttributeName, nil];

            pageStringSize = [myString boundingRectWithSize:maxSize options:NSStringDrawingUsesLineFragmentOrigin attributes:attrsDictionary context:nil];

        }