| // 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 |