使用多行自动收缩UILabel

时间:2012-01-30 05:12:14

标签: ios ios5 uilabel

是否可以在UILabel的多行上结合使用autoshrink属性?例如,2个可用行上可能有大文本大小。

17 个答案:

答案 0 :(得分:46)

我稍微修改了上面的代码,使其成为UILabel上的一个类别:

标题文件:

#import <UIKit/UIKit.h>
@interface UILabel (MultiLineAutoSize)
    - (void)adjustFontSizeToFit;
@end

执行文件:

@implementation UILabel (MultiLineAutoSize)

- (void)adjustFontSizeToFit
{
    UIFont *font = self.font;
    CGSize size = self.frame.size;

    for (CGFloat maxSize = self.font.pointSize; maxSize >= self.minimumFontSize; maxSize -= 1.f)
    {
        font = [font fontWithSize:maxSize];
        CGSize constraintSize = CGSizeMake(size.width, MAXFLOAT);
        CGSize labelSize = [self.text sizeWithFont:font constrainedToSize:constraintSize lineBreakMode:UILineBreakModeWordWrap];
        if(labelSize.height <= size.height)
        {
            self.font = font;
            [self setNeedsLayout];
            break;
        }
    }
    // set the font to the minimum size anyway
    self.font = font;
    [self setNeedsLayout];
}

@end

答案 1 :(得分:39)

这些人找到了解决方案:

http://www.11pixel.com/blog/28/resize-multi-line-text-to-fit-uilabel-on-iphone/

他们的解决方案如下:

int maxDesiredFontSize = 28;
int minFontSize = 10;
CGFloat labelWidth = 260.0f;
CGFloat labelRequiredHeight = 180.0f;
//Create a string with the text we want to display.
self.ourText = @"This is your variable-length string. Assign it any way you want!";

/* This is where we define the ideal font that the Label wants to use.
   Use the font you want to use and the largest font size you want to use. */
UIFont *font = [UIFont fontWithName:@"Marker Felt" size:maxDesiredFontSize];

int i;
/* Time to calculate the needed font size.
   This for loop starts at the largest font size, and decreases by two point sizes (i=i-2)
   Until it either hits a size that will fit or hits the minimum size we want to allow (i > 10) */
for(i = maxDesiredFontSize; i > minFontSize; i=i-2)
{
    // Set the new font size.
    font = [font fontWithSize:i];
    // You can log the size you're trying: NSLog(@"Trying size: %u", i);

    /* This step is important: We make a constraint box 
       using only the fixed WIDTH of the UILabel. The height will
       be checked later. */ 
    CGSize constraintSize = CGSizeMake(labelWidth, MAXFLOAT);

    // This step checks how tall the label would be with the desired font.
    CGSize labelSize = [self.ourText sizeWithFont:font constrainedToSize:constraintSize lineBreakMode:UILineBreakModeWordWrap];

    /* Here is where you use the height requirement!
       Set the value in the if statement to the height of your UILabel
       If the label fits into your required height, it will break the loop
       and use that font size. */
    if(labelSize.height <= labelRequiredHeight)
        break;
}
// You can see what size the function is using by outputting: NSLog(@"Best size is: %u", i);

// Set the UILabel's font to the newly adjusted font.
msg.font = font;

// Put the text into the UILabel outlet variable.
msg.text = self.ourText;

为了使其正常工作,必须在界面构建器中为UILabel分配IBOutlet。

“IBOutlet UILabel * msg;”

所有的优点都是11像素的人。

答案 2 :(得分:38)

我找到了这个链接http://beckyhansmeyer.com/2015/04/09/autoshrinking-text-in-a-multiline-uilabel/

使用Interface Builder可以通过3个简单步骤解决问题:

  1. 将“自动收缩”设置为“最小字体大小。”
  2. 将字体设置为您的字体 最大的理想字体大小(20)并将行设置为10,在我的例子中为 在该字体大小的标签中适合的许多行。
  3. 然后,改变“Line 打破“从”自动换行“到”截断尾巴。“
  4. 希望它有所帮助!

答案 3 :(得分:24)

这是基于针对iOS 6的itecedor更新而更新到iOS 7的类别解决方案。

标题文件:

#import <UIKit/UIKit.h>
@interface UILabel (MultiLineAutoSize)
    - (void)adjustFontSizeToFit;
@end

执行文件:

@implementation UILabel (MultiLineAutoSize)


- (void)adjustFontSizeToFit {
    UIFont *font = self.font;
    CGSize size = self.frame.size;

    for (CGFloat maxSize = self.font.pointSize; maxSize >= self.minimumScaleFactor * self.font.pointSize; maxSize -= 1.f)
    {
        font = [font fontWithSize:maxSize];
        CGSize constraintSize = CGSizeMake(size.width, MAXFLOAT);

        CGRect textRect = [self.text boundingRectWithSize:constraintSize
                                             options:NSStringDrawingUsesLineFragmentOrigin
                                          attributes:@{NSFontAttributeName:font}
                                             context:nil];

        CGSize labelSize = textRect.size;


        if(labelSize.height <= size.height)
        {
            self.font = font;
            [self setNeedsLayout];
            break;
        }
    }
    // set the font to the minimum size anyway
    self.font = font;
    [self setNeedsLayout]; }


@end

答案 4 :(得分:11)

标记为解决方案的答案是hacky和不精确。如果您正确设置以下属性,UILabel将自动处理它:

numberOfLines必须为非零

adjustsFontSizeToFitWidth必须为YES

lineBreakMode必须NSLineBreakByCharWrappingNSLineBreakByWordWrapping

答案 5 :(得分:8)

改编自@DaGaMs的漂亮版本。

SWIFT 2:

extension UILabel {
    func adjustFontSizeToFit(minimumFontSize: CGFloat, maximumFontSize: CGFloat? = nil) {
        let maxFontSize = maximumFontSize ?? font.pointSize
        for size in stride(from: maxFontSize, to: minimumFontSize, by: -CGFloat(0.1)) {
            let proposedFont = font.fontWithSize(size)
            let constraintSize = CGSizeMake(bounds.size.width, CGFloat(MAXFLOAT))
            let labelSize = ((text ?? "") as NSString).boundingRectWithSize(constraintSize,
                options: .UsesLineFragmentOrigin,
                attributes: [NSFontAttributeName: proposedFont],
                context: nil)
            if labelSize.height <= bounds.size.height {
                font = proposedFont
                setNeedsLayout()
                break;
            }
        }
    }
}

SWIFT 3:

extension UILabel {
    func adjustFontSizeToFit(minimumFontSize: CGFloat, maximumFontSize: CGFloat? = nil) {
        let maxFontSize = maximumFontSize ?? font.pointSize
        for size in stride(from: maxFontSize, to: minimumFontSize, by: -CGFloat(0.1)) {
            let proposedFont = font.withSize(size)
            let constraintSize = CGSize(width: bounds.size.width, height: CGFloat(MAXFLOAT))
            let labelSize = ((text ?? "") as NSString).boundingRect(with: constraintSize,
                                                                            options: .usesLineFragmentOrigin,
                                                                            attributes: [NSFontAttributeName: proposedFont],
                                                                            context: nil)
            if labelSize.height <= bounds.size.height {
                font = proposedFont
                setNeedsLayout()
                break;
            }
        }
    }
}

答案 6 :(得分:7)

由于声誉不足,我无法对MontiRabbit的帖子发表评论,所以我会做出新的答案。他(和她的推荐人)提出的解决方案不适用于Xcode 7.3或更好,它是不精确的。 为了使它工作,在故事板中,我不得不:

  1. 设置宽度约束(纯宽度或尾部和前导)
  2. 设置一个HEIGHT CONSTRAINT(这非常重要,通常使用自动调整大小不会设置标签高度)
  3. 将“自动收缩”属性设置为“最小字体比例”或“最小字体大小”(适用于两种情况)
  4. 将“Line Breaks”属性设置为“Truncate Tail”
  5. 将“Lines”属性设置为非零值
  6. 希望它有所帮助! ;)

答案 7 :(得分:6)

itedcedor的答案有一个问题,普维特曼指出。此外,不需要修剪空格。这是修改后的版本:

- (void)adjustFontSizeToFit {
    UIFont *font = self.font;
    CGSize size = self.frame.size;

    for (CGFloat maxSize = self.font.pointSize; maxSize >= self.minimumScaleFactor * self.font.pointSize; maxSize -= 1.f) {
        font = [font fontWithSize:maxSize];
        CGSize constraintSize = CGSizeMake(size.width, MAXFLOAT);
        CGSize labelSize = [self.text sizeWithFont:font constrainedToSize:constraintSize lineBreakMode:NSLineBreakByWordWrapping];

        if(labelSize.height <= size.height) {
            self.font = font;
            [self setNeedsLayout];
            break;
        }
    }

    // set the font to the minimum size anyway
    self.font = font;
    [self setNeedsLayout];
}

答案 8 :(得分:6)

我喜欢DaGaMs的答案,但是在使用可以返回dequeueReusableCell:的UITableViewCells中的标签时,常规字体大小会继续缩小,即使原始字体大小仍然需要一些文本较少且可能的tableView单元格利用原始标签的原始字体大小。

所以,我从DaGaMs的类别开始作为跳跃点,我创建了一个单独的类而不是一个单独的类,我确保我的故事板中的UILabel使用这个新类:

#import "MultiLineAutoShrinkLabel.h"

@interface MultiLineAutoShrinkLabel ()
@property (readonly, nonatomic) UIFont* originalFont;
@end

@implementation MultiLineAutoShrinkLabel

@synthesize originalFont = _originalFont;

- (UIFont*)originalFont { return _originalFont ? _originalFont : (_originalFont = self.font); }

- (void)quoteAutoshrinkUnquote
{
    UIFont* font = self.originalFont;
    CGSize frameSize = self.frame.size;

    CGFloat testFontSize = _originalFont.pointSize;
    for (; testFontSize >= self.minimumFontSize; testFontSize -= 0.5)
    {
        CGSize constraintSize = CGSizeMake(frameSize.width, MAXFLOAT);
        CGSize testFrameSize = [self.text sizeWithFont:(font = [font fontWithSize:testFontSize])
                                     constrainedToSize:constraintSize
                                         lineBreakMode:self.lineBreakMode];
        // the ratio of testFontSize to original font-size sort of accounts for number of lines
        if (testFrameSize.height <= frameSize.height * (testFontSize/_originalFont.pointSize))
            break;
    }

    self.font = font;
    [self setNeedsLayout];
}

@end

答案 9 :(得分:4)

感谢DaGaMs提供此解决方案。

我已将其更新如下:

1 - 使用iOS 6(因为不推荐使用minimumFontSize和UILineBreakModeWordWrap) 2 - 从标签的文本中删除空格,因为它会导致调整大小失败(你不想知道我找到那个bug需要多长时间)

-(void)adjustFontSizeToFit 
{
    self.text = [self.text stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];

    UIFont *font = self.font;
    CGSize size = self.frame.size;

    for (CGFloat maxSize = self.font.pointSize; maxSize >= self.minimumScaleFactor; maxSize -= 1.f)
    {
        font = [font fontWithSize:maxSize];
        CGSize constraintSize = CGSizeMake(size.width, MAXFLOAT);
        CGSize labelSize = [self.text sizeWithFont:font constrainedToSize:constraintSize lineBreakMode:NSLineBreakByWordWrapping];
        if(labelSize.height <= size.height)
        {
            self.font = font;
            [self setNeedsLayout];
            break;
        }
    }
    // set the font to the minimum size anyway
    self.font = font;
    [self setNeedsLayout];
}

答案 10 :(得分:2)

对于UIButton,这些行正在为我工​​作:

self.centerBtn.titleLabel.numberOfLines = 2;
self.centerBtn.titleLabel.textAlignment = NSTextAlignmentCenter;
self.centerBtn.titleLabel.adjustsFontSizeToFitWidth = YES;

答案 11 :(得分:2)

我使用了@ wbarksdale的Swift 3解决方案,但发现长字在中间被截断。为了保持单词的完整性,我必须修改如下所示:

extension UILabel {
    func adjustFontSizeToFit(minimumFontSize: CGFloat, maximumFontSize: CGFloat? = nil) {
        let maxFontSize = maximumFontSize ?? font.pointSize
        let words = self.text?.components(separatedBy: " ")
        var longestWord: String?
        if let max = words?.max(by: {$1.characters.count > $0.characters.count}) {
            longestWord = max
        }
        for size in stride(from: maxFontSize, to: minimumFontSize, by: -CGFloat(0.1)) {
            let proposedFont = font.withSize(size)
            let constraintSize = CGSize(width: bounds.size.width, height: CGFloat(MAXFLOAT))
            let labelSize = ((text ?? "") as NSString).boundingRect(with: constraintSize,
                                                                    options: .usesLineFragmentOrigin,
                                                                    attributes: [NSFontAttributeName: proposedFont],
                                                                    context: nil)

            let wordConstraintSize = CGSize(width: CGFloat(MAXFLOAT), height: CGFloat(MAXFLOAT))
            let longestWordSize = ((longestWord ?? "") as NSString).boundingRect(with: wordConstraintSize,
                                                                    options: .usesFontLeading,
                                                                    attributes: [NSFontAttributeName: proposedFont],
                                                                    context: nil)

            if labelSize.height <= bounds.size.height && longestWordSize.width < constraintSize.width {
                font = proposedFont
                setNeedsLayout()
                break
            }
        }
    }
}

答案 12 :(得分:1)

我已经根据&#34; The Dude&#34;在UILabel上写了一个小类别。回答以上问题以实现此功能。

https://gist.github.com/ayushn21/d87b835b2efc756e859f

答案 13 :(得分:0)

NSString-sizeWithFont:minFontSize:actualFontSize:forWidth:lineBreakMode:上有一个方法,自iOS 2.0以来显然存在,但遗憾的是在iOS 7中已弃用,没有建议的替代方法,因为不鼓励自动缩小字体大小。我不太了解Apple对此的立场,因为他们在主题演讲等中使用它,我认为如果字体大小在一个小范围内,那就没关系。这是使用此方法在Swift中的实现。

var newFontSize: CGFloat = 30
    let font = UIFont.systemFontOfSize(newFontSize)
    (self.label.text as NSString).sizeWithFont(font, minFontSize: 20, actualFontSize: &newFontSize, forWidth: self.label.frame.size.width, lineBreakMode: NSLineBreakMode.ByWordWrapping)
    self.label.font = font.fontWithSize(newFontSize)

我不知道如果不使用弃用方法就可以实现这一目标。

答案 14 :(得分:0)

试试这个:

子类UILabel或在标签上设置text属性后调用adjustFontSize方法

override var text : String? { didSet { self.adjustFontSize() } }

func adjustFontSize()
{
    var lineCount = self.string.components(separatedBy: "\n").count - 1
    var textArray = self.string.components(separatedBy: " ")
    var wordsToCompare = 1
    while(textArray.count > 0)
    {
        let words = textArray.first(n: wordsToCompare).joined(separator: " ")
        let wordsWidth = words.widthForHeight(0, font: self.font)
        if(wordsWidth > self.frame.width)
        {
            textArray.removeFirst(wordsToCompare)
            lineCount += 1
            wordsToCompare = 1
        }
        else if(wordsToCompare > textArray.count)
        {
            break
        }
        else
        {
            wordsToCompare += 1
        }
    }
    self.numberOfLines = lineCount + 1
}

答案 15 :(得分:0)

extension UILabel{

func adjustFont(minSize:Int, maxSize:Int){
    var newFont = self.font
    for index in stride(from: maxSize, to: minSize, by: -1) {
        newFont = UIFont.systemFont(ofSize: CGFloat(index))
        let size = CGSize(width: self.frame.width, height: CGFloat(Int.max))
        let size2 = (self.text! as NSString).boundingRect(with: size, options: [.usesLineFragmentOrigin, .usesFontLeading], attributes: [NSAttributedStringKey.font:newFont!], context: nil).size
        if size2.height < self.frame.size.height{
            break
        }
    }
    self.font = newFont
}

}

您还需要为UILabel的numberOfLines属性赋值。

答案 16 :(得分:0)

是的,尽管一开始很难弄清楚,但这是可能的。我将更进一步,向您展示如何甚至可以单击文本中的任何区域。

使用此方法,您可以使UI标签为:

  • 多行友好
  • 自动收缩友好
  • 可点击的友好内容(是,甚至是单个字符)
  • 雨燕5

步骤1:

使UILabel具有“ 截尾”的换行属性,并设置最小字体比例

如果您不熟悉字体比例,请记住以下规则:

minimumFontSize / defaultFontSize = fontscale

在我的情况下,我希望7.2为最小字体,而我的起始字体为36。因此,7.2 / 36 = 0.2

enter image description here

第2步:

如果您不关心标签是否可单击,而只想要一个有效的多行标签,就可以了!

但是,如果希望标签可点击,请继续阅读...

添加我创建的以下扩展名

extension UILabel {

    func setOptimalFontSize(maxFontSize:CGFloat,text:String){
        let width = self.bounds.size.width

        var font_size:CGFloat = maxFontSize //Set the maximum font size.
        var stringSize = NSString(string: text).size(withAttributes: [.font : self.font.withSize(font_size)])
        while(stringSize.width > width){
            font_size = font_size - 1
            stringSize = NSString(string: text).size(withAttributes: [.font : self.font.withSize(font_size)])
        }

        self.font = self.font.withSize(font_size)//Forcefully change font to match what it would be graphically.
    }
}

它的用法如下(只需将<Label>替换为您的实际标签名称)即可:

<Label>.setOptimalFontSize(maxFontSize: 36.0, text: formula)

此扩展名是必需的,因为自动收缩在自动收缩后不会不更改标签的'font'属性,因此您必须通过与使用相同的方式来计算得出。 size(withAttributes)函数,用于模拟特定字体的大小。

这是必需的,因为用于检测在何处单击标签的解决方案要求确切的字体大小

第3步:

添加以下扩展名:

extension UITapGestureRecognizer {

    func didTapAttributedTextInLabel(label: UILabel, inRange targetRange: NSRange) -> Bool {
        // Create instances of NSLayoutManager, NSTextContainer and NSTextStorage
        let layoutManager = NSLayoutManager()
        let textContainer = NSTextContainer(size: CGSize.zero)

        let mutableAttribString = NSMutableAttributedString(attributedString: label.attributedText!)
        mutableAttribString.addAttributes([NSAttributedString.Key.font: label.font!], range: NSRange(location: 0, length: label.attributedText!.length))

        let paragraphStyle = NSMutableParagraphStyle()
        paragraphStyle.lineSpacing = 6
        paragraphStyle.lineBreakMode = .byTruncatingTail
        paragraphStyle.alignment = .center
        mutableAttribString.addAttributes([.paragraphStyle: paragraphStyle], range: NSMakeRange(0, mutableAttribString.string.count))

        let textStorage = NSTextStorage(attributedString: mutableAttribString)

        // Configure textContainer
        textContainer.lineFragmentPadding = 0.0
        textContainer.lineBreakMode = label.lineBreakMode
        textContainer.maximumNumberOfLines = label.numberOfLines

        // Configure layoutManager and textStorage
        layoutManager.addTextContainer(textContainer)

        textStorage.addLayoutManager(layoutManager)

        let labelSize = label.bounds.size

        textContainer.size = labelSize

        // Find the tapped character location and compare it to the specified range
        let locationOfTouchInLabel = self.location(in: label)

        let textBoundingBox = layoutManager.usedRect(for: textContainer)
        //let textContainerOffset = CGPointMake((labelSize.width - textBoundingBox.size.width) * 0.5 - textBoundingBox.origin.x,
                                              //(labelSize.height - textBoundingBox.size.height) * 0.5 - textBoundingBox.origin.y);
        let textContainerOffset = CGPoint(x: (labelSize.width - textBoundingBox.size.width) * 0.5 - textBoundingBox.origin.x, y: (labelSize.height - textBoundingBox.size.height) * 0.5 - textBoundingBox.origin.y)

        //let locationOfTouchInTextContainer = CGPointMake(locationOfTouchInLabel.x - textContainerOffset.x,
                                                        // locationOfTouchInLabel.y - textContainerOffset.y);
        let locationOfTouchInTextContainer = CGPoint(x: locationOfTouchInLabel.x - textContainerOffset.x, y: locationOfTouchInLabel.y - textContainerOffset.y)

        let indexOfCharacter = layoutManager.characterIndex(for: locationOfTouchInTextContainer, in: textContainer, fractionOfDistanceBetweenInsertionPoints: nil)
        print("IndexOfCharacter=",indexOfCharacter)

        print("TargetRange=",targetRange)
        return NSLocationInRange(indexOfCharacter, targetRange)
    }

}

您将需要针对特定​​的多行情况修改此扩展名。就我而言,您会注意到我使用的是段落样式。

let paragraphStyle = NSMutableParagraphStyle()
        paragraphStyle.lineSpacing = 6
        paragraphStyle.lineBreakMode = .byTruncatingTail
        paragraphStyle.alignment = .center
        mutableAttribString.addAttributes([.paragraphStyle: paragraphStyle], range: NSMakeRange(0, mutableAttribString.string.count))

请确保将扩展名中的更改为您实际使用的行距,以便正确计算所有内容。

第4步:

viewDidLoad或您认为合适的地方,将gestureRecognizer添加到标签中(只需将<Label>替换为标签名:

<Label>.addGestureRecognizer(UITapGestureRecognizer(target:self, action: #selector(tapLabel(gesture:))))

这是我的tapLabel函数的简化示例(只需将<Label>替换为您的UILabel名称):

@IBAction func tapLabel(gesture: UITapGestureRecognizer) {
        guard let text = <Label>.attributedText?.string else {
            return
        }

        let click_range = text.range(of: "(α/β)")

        if gesture.didTapAttributedTextInLabel(label: <Label>, inRange: NSRange(click_range!, in: text)) {
           print("Tapped a/b")
        }else {
           print("Tapped none")
        }
    }

在我的示例中仅需注意,我的字符串是BED = N * d * [ RBE + ( d / (α/β) ) ],因此在这种情况下,我只是获得了α/β的范围。您可以在字符串中添加“ \ n”,以添加换行符和所需的任何文本,然后进行测试以在下一行中找到字符串,它仍然会找到并正确检测到点击!

就是这样!大功告成享受多行 可点击标签。