wake-up-neo.net

Mehrzeiliges UILabel mit adjustsFontSizeToFitWidth

Ich habe ein mehrzeiliges UILabel, dessen Schriftgröße ich je nach Textlänge anpassen möchte. Der gesamte Text sollte in den Rahmen des Labels passen, ohne es abzuschneiden.

Leider ist die adjustsFontSizeToFitWidth-Eigenschaft laut Dokumentation "nur wirksam, wenn die numberOfLines-Eigenschaft auf 1 gesetzt ist".

Ich habe versucht, die angepasste Schriftgröße mit zu ermitteln

-[NSString (CGSize)sizeWithFont:(UIFont *)font constrainedToSize:(CGSize)size lineBreakMode:(UILineBreakMode)lineBreakMode]

und dekrementieren Sie dann die Schriftgröße, bis sie passt. Leider schneidet diese Methode den Text intern auf die angegebene Größe ab und gibt die Größe der resultierenden abgeschnittenen Zeichenfolge zurück.

41
Ortwin Gentz

In diese Frage , 0x90 bietet eine Lösung, die - wenn auch etwas hässlich - tut, was ich will. Insbesondere wird richtig mit der Situation umgegangen, dass ein einzelnes Word die Breite bei der ursprünglichen Schriftgröße nicht passt. Ich habe den Code leicht modifiziert, damit er als NSString-Kategorie funktioniert:

- (CGFloat)fontSizeWithFont:(UIFont *)font constrainedToSize:(CGSize)size {
    CGFloat fontSize = [font pointSize];
    CGFloat height = [self sizeWithFont:font constrainedToSize:CGSizeMake(size.width,FLT_MAX) lineBreakMode:UILineBreakModeWordWrap].height;
    UIFont *newFont = font;

    //Reduce font size while too large, break if no height (empty string)
    while (height > size.height && height != 0) {   
        fontSize--;  
        newFont = [UIFont fontWithName:font.fontName size:fontSize];   
        height = [self sizeWithFont:newFont constrainedToSize:CGSizeMake(size.width,FLT_MAX) lineBreakMode:UILineBreakModeWordWrap].height;
    };

    // Loop through words in string and resize to fit
    for (NSString *Word in [self componentsSeparatedByCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]) {
        CGFloat width = [Word sizeWithFont:newFont].width;
        while (width > size.width && width != 0) {
            fontSize--;
            newFont = [UIFont fontWithName:font.fontName size:fontSize];   
            width = [Word sizeWithFont:newFont].width;
        }
    }
    return fontSize;
}

So verwenden Sie es mit einer UILabel:

    CGFloat fontSize = [label.text fontSizeWithFont:[UIFont boldSystemFontOfSize:15] constrainedToSize:label.frame.size];
    label.font = [UIFont boldSystemFontOfSize:fontSize];

EDIT: Korrigiert den Code zum Initialisieren von newFont mit font. Behebt unter bestimmten Umständen einen Absturz.

50
Ortwin Gentz

In einigen Fällen reicht es aus, "Zeilenumbrüche" von "Zeilenumbruch" in "Trunkate Tail" zu ändern, wenn Sie wissen, wie viele Zeilen Sie wünschen (z. B. "2"): Credit: Becky Hansmeyer

5
weienw

Danke, mit diesem und etwas mehr von jemandem, den ich mit diesem benutzerdefinierten UILabel gemacht habe, wird die minimale Schriftgröße beachtet und es gibt eine Bonusoption, um den Text nach oben auszurichten.

h:

@interface EPCLabel : UILabel {
    float originalPointSize;
    CGSize originalSize;
}

@property (nonatomic, readwrite) BOOL alignTextOnTop;
@end

m:

#import "EPCLabel.h"

@implementation EPCLabel
@synthesize alignTextOnTop;

-(void)verticalAlignTop {
    CGSize maximumSize = originalSize;
    NSString *dateString = self.text;
    UIFont *dateFont = self.font;
    CGSize dateStringSize = [dateString sizeWithFont:dateFont 
                                   constrainedToSize:CGSizeMake(self.frame.size.width, maximumSize.height)
                                       lineBreakMode:self.lineBreakMode];

    CGRect dateFrame = CGRectMake(self.frame.Origin.x, self.frame.Origin.y, self.frame.size.width, dateStringSize.height);

    self.frame = dateFrame;
}

- (CGFloat)fontSizeWithFont:(UIFont *)font constrainedToSize:(CGSize)size {
    CGFloat fontSize = [font pointSize];
    CGFloat height = [self.text sizeWithFont:font             
                           constrainedToSize:CGSizeMake(size.width,FLT_MAX)  
                               lineBreakMode:UILineBreakModeWordWrap].height;
    UIFont *newFont = font;

    //Reduce font size while too large, break if no height (empty string)
    while (height > size.height && height != 0 && fontSize > self.minimumFontSize) { 
        fontSize--;  
        newFont = [UIFont fontWithName:font.fontName size:fontSize];   
        height = [self.text sizeWithFont:newFont  
                       constrainedToSize:CGSizeMake(size.width,FLT_MAX) 
                           lineBreakMode:UILineBreakModeWordWrap].height;
    };

    // Loop through words in string and resize to fit
    if (fontSize > self.minimumFontSize) {
        for (NSString *Word in [self.text componentsSeparatedByString:@" "]) {
            CGFloat width = [Word sizeWithFont:newFont].width;
            while (width > size.width && width != 0 && fontSize > self.minimumFontSize) {
                fontSize--;
                newFont = [UIFont fontWithName:font.fontName size:fontSize];   
                width = [Word sizeWithFont:newFont].width;
            }
        }
    }
    return fontSize;
}

-(void)setText:(NSString *)text {
    [super setText:text];
    if (originalSize.height == 0) {
        originalPointSize = self.font.pointSize;
        originalSize = self.frame.size;
    }

    if (self.adjustsFontSizeToFitWidth && self.numberOfLines > 1) {
        UIFont *origFont = [UIFont fontWithName:self.font.fontName size:originalPointSize];
        self.font = [UIFont fontWithName:origFont.fontName size:[self fontSizeWithFont:origFont constrainedToSize:originalSize]];
    }

    if (self.alignTextOnTop) [self verticalAlignTop];
}

-(void)setAlignTextOnTop:(BOOL)flag {
    alignTextOnTop = YES;
    if (alignTextOnTop && self.text != nil)
        [self verticalAlignTop];
}

@end

Ich hoffe, es hilft.

3
Everton Cunha

Eine voll funktionsfähige Lösung finden Sie unten in meiner Antwort.

Um die Abmessungen der text/attributedText Ihrer UILabel manuell zu messen, um mithilfe Ihrer eigenen Strategie die geeignete Schriftgröße zu finden, haben Sie einige Möglichkeiten:

  1. Verwenden Sie NSStrings size(withAttributes:) oder NSAttributedString__ size(). Diese sind nur bedingt hilfreich, da sie davon ausgehen, dass der Text eine Zeile ist.

  2. Verwenden Sie die boundingRect()-Funktion von NSAttributedString, die einige Zeichnungsoptionen erfordert. Stellen Sie sicher, dass Sie .usesLineFragmentOrigin angeben, um mehrere Zeilen zu unterstützen:

    var textToMeasure = label.attributedText
    
    // Modify the font size in `textToMeasure` as necessary
    
    // Now measure
    let rect = textToMeasure.boundingRect(with: label.bounds, options: [. usesLineFragmentOrigin], context: nil)
    
  3. Verwenden Sie TextKit und Ihren eigenen NSLayoutManager:

    var textToMeasure = label.attributedText
    
    // Modify the font size in `textToMeasure` as necessary
    
    // Now measure
    let layoutManager = NSLayoutManager()
    let textContainer = NSTextContainer(size: CGSize(width: label.bounds.width, height: .greatestFiniteMagnitude))
    let textStorage = NSTextStorage(attributedString: string)
    textStorage.addLayoutManager(layoutManager)
    layoutManager.addTextContainer(textContainer)
    let glyphRange = layoutManager.glyphRange(for: textContainer)
    let rect = layoutManager.boundingRect(forGlyphRange: glyphRange, in: textContainer)
    
  4. Verwenden Sie CoreText, eine leistungsfähigere und einfachere API zum Erstellen von Text. Dies wäre wahrscheinlich unnötig kompliziert für diese Aufgabe.

Unabhängig davon, was Sie für die Messung von Text verwenden, müssen Sie wahrscheinlich zwei Durchgänge ausführen: Der erste Durchlauf besteht darin, lange Wörter zu berücksichtigen, die nicht über mehrere Zeilen aufgeteilt werden sollten, wobei Sie die größte Schriftgröße suchen müssen passt das größte (~ längste) Wort vollständig in die Grenzen des Labels. Im zweiten Durchlauf können Sie Ihre Suche ab dem ersten Durchlauf nach unten fortsetzen, um eine noch kleinere Schriftgröße zu finden, die diesmal für den gesamten Text erforderlich ist.

Bei der größten Word-Messung (und nicht beim gesamten Text) möchten Sie den von Ihnen angegebenen Breitenparameter nicht auf einige der oben genannten Größenfunktionen beschränken. Andernfalls hat das System keine andere Wahl, als das einzelne Wort aufzubrechen gab es und gab falsche Ergebnisse für Ihre Zwecke zurück. Sie müssen das width-Argument der obigen Methoden durch CGFloat.greatestFiniteMagnitude ersetzen:

  • der breite Teil des Größenarguments von boundingRect().
  • der breite Teil des Größenarguments von NSTextContainer().

Sie müssen auch den tatsächlichen Algorithmus berücksichtigen, den Sie für Ihre Suche verwenden, da das Messen von Text eine teure Operation ist und eine lineare Suche je nach Ihren Anforderungen zu langsam ist. Wenn Sie effizienter arbeiten möchten, können Sie stattdessen eine binäre Suche durchführen. ????

Eine robuste Arbeitslösung auf der Grundlage der obigen Informationen finden Sie in meinem Open-Source-Framework AccessibilityKit . Einige Beispiele für AKLabel in Aktion:

Example1

Example2

Hoffe das hilft!

2

In Kommentaren gibt es eine Erweiterung von ObjC, die die Schriftgröße berechnet, die erforderlich ist, um mehrzeiligen Text in UILabel ..__ zu integrieren. Er wurde in Swift umgeschrieben (seit 2016):

//
//  NSString+KBAdditions.Swift
//
//  Created by Alexander Mayatsky on 16/03/16.
//
//  Original code from http://stackoverflow.com/a/4383281/463892 & http://stackoverflow.com/a/18951386
//

import Foundation
import UIKit

protocol NSStringKBAdditions {
    func fontSizeWithFont(font: UIFont, constrainedToSize size: CGSize, minimumScaleFactor: CGFloat) -> CGFloat
}

extension NSString : NSStringKBAdditions {
    func fontSizeWithFont(font: UIFont, constrainedToSize size: CGSize, minimumScaleFactor: CGFloat) -> CGFloat {
        var fontSize = font.pointSize
        let minimumFontSize = fontSize * minimumScaleFactor


        var attributedText = NSAttributedString(string: self as String, attributes:[NSFontAttributeName: font])
        var height = attributedText.boundingRectWithSize(CGSize(width: size.width, height: CGFloat.max), options:NSStringDrawingOptions.UsesLineFragmentOrigin, context:nil).size.height

        var newFont = font
        //Reduce font size while too large, break if no height (empty string)
        while (height > size.height && height != 0 && fontSize > minimumFontSize) {
            fontSize--;
            newFont = UIFont(name: font.fontName, size: fontSize)!

            attributedText = NSAttributedString(string: self as String, attributes:[NSFontAttributeName: newFont])
            height = attributedText.boundingRectWithSize(CGSize(width: size.width, height: CGFloat.max), options:NSStringDrawingOptions.UsesLineFragmentOrigin, context:nil).size.height
        }

        // Loop through words in string and resize to fit
        for Word in self.componentsSeparatedByCharactersInSet(NSCharacterSet.whitespaceAndNewlineCharacterSet()) {
            var width = Word.sizeWithAttributes([NSFontAttributeName:newFont]).width
            while (width > size.width && width != 0 && fontSize > minimumFontSize) {
                fontSize--
                newFont = UIFont(name: font.fontName, size: fontSize)!
                width = Word.sizeWithAttributes([NSFontAttributeName:newFont]).width
            }
        }
        return fontSize;
    }
}

Link zum vollständigen Code: https://Gist.github.com/amayatsky/e6125a2288cc2e4f1bbf

2

Swift 4.2:

//
//  String+Utility.Swift
//
//  Created by Philip Engberg on 29/11/2018.
//  Original code from http://stackoverflow.com/a/4383281/463892 & http://stackoverflow.com/a/18951386
//

import Foundation
import UIKit

extension String {
    func fontSize(with font: UIFont, constrainedTo size: CGSize, minimumScaleFactor: CGFloat) -> CGFloat {
        var fontSize = font.pointSize
        let minimumFontSize = fontSize * minimumScaleFactor

        var attributedText = NSAttributedString(string: self, attributes: [.font: font])
        var height = attributedText.boundingRect(with: CGSize(width: size.width, height: CGFloat.greatestFiniteMagnitude), options: [.usesLineFragmentOrigin], context: nil).size.height

        var newFont = font
        //Reduce font size while too large, break if no height (empty string)
        while height > size.height && height != 0 && fontSize > minimumFontSize {
            fontSize -= 1
            newFont = UIFont(name: font.fontName, size: fontSize)!

            attributedText = NSAttributedString(string: self, attributes: [.font: newFont])
            height = attributedText.boundingRect(with: CGSize(width: size.width, height: CGFloat.greatestFiniteMagnitude), options: [.usesLineFragmentOrigin], context: nil).size.height
        }

        // Loop through words in string and resize to fit
        for Word in self.components(separatedBy: NSCharacterSet.whitespacesAndNewlines) {
            var width = Word.size(withAttributes: [.font: newFont]).width
            while width > size.width && width != 0 && fontSize > minimumFontSize {
                fontSize -= 1
                newFont = UIFont(name: font.fontName, size: fontSize)!
                width = Word.size(withAttributes: [.font: newFont]).width
            }
        }
        return fontSize
    }
}
0
Zappel