blob: dee9ac44434c64255625baba08ca2d6b2e632fa1 [file] [log] [blame]
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "ios/chrome/browser/ui/util/CRUILabel+AttributeUtils.h"
#import <objc/runtime.h>
#import "base/ios/weak_nsobject.h"
#include "base/mac/scoped_nsobject.h"
#import "ios/chrome/browser/ui/util/label_observer.h"
namespace {
// They key under which to associate the line height with the label.
const void* const kLineHeightKey = &kLineHeightKey;
// Creates an NSNumber from |line_height| and associates it with |label|.
void SetAssociatedLineHeight(CGFloat line_height, UILabel* label) {
objc_setAssociatedObject(label, kLineHeightKey, @(line_height),
OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
// Returns the line height associated with |label|.
CGFloat GetAssociatedLineHeight(UILabel* label) {
return [objc_getAssociatedObject(label, kLineHeightKey) floatValue];
}
}
@implementation UILabel (CRUILabelAttributeUtils)
// If there's no text in the label, the line height is whatever is stored in the
// associated value.
- (CGFloat)cr_lineHeight {
if (!self.text.length || !self.attributedText.string.length)
return GetAssociatedLineHeight(self);
NSParagraphStyle* style =
[self.attributedText attribute:NSParagraphStyleAttributeName
atIndex:0
effectiveRange:nullptr];
return style.maximumLineHeight;
}
- (void)cr_setLineHeight:(CGFloat)lineHeight {
DCHECK_GT(lineHeight, 0.0);
// If this is the first time the line height is being set, register the
// LabelObserverAction.
if (!GetAssociatedLineHeight(self)) {
LabelObserver* observer = [LabelObserver observerForLabel:self];
[observer addTextChangedAction:^(UILabel* label) {
label.cr_lineHeight = GetAssociatedLineHeight(label);
}];
}
// Store the new line height as an associated object.
SetAssociatedLineHeight(lineHeight, self);
// If there's no text yet, there's nothing that can be done. The
// LabelObserverAction will call this selector again upon changes to the text.
if (!self.text.length || !self.attributedText.string.length)
return;
base::scoped_nsobject<NSMutableAttributedString> newString(
[self.attributedText mutableCopy]);
DCHECK([newString length]);
NSParagraphStyle* style = [newString attribute:NSParagraphStyleAttributeName
atIndex:0
effectiveRange:nullptr];
if (!style)
style = [NSParagraphStyle defaultParagraphStyle];
base::scoped_nsobject<NSMutableParagraphStyle> newStyle([style mutableCopy]);
[newStyle setMinimumLineHeight:lineHeight];
[newStyle setMaximumLineHeight:lineHeight];
[newString addAttribute:NSParagraphStyleAttributeName
value:newStyle
range:NSMakeRange(0, [newString length])];
self.attributedText = newString;
}
- (void)cr_adjustLineHeightForMaximimumLines:(NSUInteger)maximumLines {
CGSize labelSize = self.bounds.size;
CGFloat lineHeight = self.cr_lineHeight;
CGFloat numberOfLines = floorf(labelSize.height / lineHeight);
CGSize maxSize = CGSizeMake(labelSize.width, CGFLOAT_MAX);
CGSize textSize = [self sizeThatFits:maxSize];
// |textSize.height| should be a multiple of |lineHeight|. If this is not the
// case, then it is safer to fit one more line to ensure that the text of the
// label is not cropped.
CGFloat requiredNumberOfLines = ceilf(textSize.height / lineHeight);
if (requiredNumberOfLines > numberOfLines) {
requiredNumberOfLines = MIN(requiredNumberOfLines, maximumLines);
self.cr_lineHeight = floorf(labelSize.height / requiredNumberOfLines);
}
}
@end