J'ai un UILabel
dont la longueur peut varier selon que mon application fonctionne ou non en mode portrait ou paysage sur un iPhone ou un iPad. Lorsque le texte est trop long pour apparaître sur une ligne et qu'il tronque, je veux que l'utilisateur puisse appuyer dessus et obtenir une fenêtre contextuelle du texte complet.
Comment puis-je vérifier si le UILabel
tronque le texte? Est-ce même possible? En ce moment, je vérifie simplement les différentes longueurs en fonction du mode dans lequel je suis, mais cela ne fonctionne pas très bien.
Vous pouvez calculer la largeur de la chaîne et voir si la largeur est supérieure à label.bounds.size.width
NSString UIKit Additions a plusieurs méthodes pour calculer la taille de la chaîne avec une police spécifique. Cependant, si vous avez un minimumFontSize pour votre étiquette qui permet au système de réduire le texte à cette taille. Vous pouvez utiliser sizeWithFont: minFontSize: actualFontSize: forWidth: lineBreakMode: dans ce cas.
CGSize size = [label.text sizeWithAttributes:@{NSFontAttributeName:label.font}];
if (size.width > label.bounds.size.width) {
...
}
Swift (comme extension) - fonctionne pour uilabel multi-lignes:
Swift4: (attributes
param of boundingRect
légèrement modifié)
extension UILabel {
var isTruncated: Bool {
guard let labelText = text else {
return false
}
let labelTextSize = (labelText as NSString).boundingRect(
with: CGSize(width: frame.size.width, height: .greatestFiniteMagnitude),
options: .usesLineFragmentOrigin,
attributes: [.font: font],
context: nil).size
return labelTextSize.height > bounds.size.height
}
}
Swift3:
extension UILabel {
var isTruncated: Bool {
guard let labelText = text else {
return false
}
let labelTextSize = (labelText as NSString).boundingRect(
with: CGSize(width: frame.size.width, height: .greatestFiniteMagnitude),
options: .usesLineFragmentOrigin,
attributes: [NSFontAttributeName: font],
context: nil).size
return labelTextSize.height > bounds.size.height
}
}
Swift2:
extension UILabel {
func isTruncated() -> Bool {
if let string = self.text {
let size: CGSize = (string as NSString).boundingRectWithSize(
CGSize(width: self.frame.size.width, height: CGFloat(FLT_MAX)),
options: NSStringDrawingOptions.UsesLineFragmentOrigin,
attributes: [NSFontAttributeName: self.font],
context: nil).size
if (size.height > self.bounds.size.height) {
return true
}
}
return false
}
}
EDIT: Je viens de voir que ma réponse a été votée positivement, mais l'extrait de code que j'ai donné est obsolète.
Maintenant, la meilleure façon de procéder est (ARC):
NSMutableParagraphStyle *paragraph = [[NSMutableParagraphStyle alloc] init];
paragraph.lineBreakMode = mylabel.lineBreakMode;
NSDictionary *attributes = @{NSFontAttributeName : mylabel.font,
NSParagraphStyleAttributeName : paragraph};
CGSize constrainedSize = CGSizeMake(mylabel.bounds.size.width, NSIntegerMax);
CGRect rect = [mylabel.text boundingRectWithSize:constrainedSize
options:(NSStringDrawingUsesLineFragmentOrigin|NSStringDrawingUsesFontLeading)
attributes:attributes context:nil];
if (rect.size.height > mylabel.bounds.size.height) {
NSLog(@"TOO MUCH");
}
Notez que la taille calculée n'est pas une valeur entière. Donc, si vous faites des choses comme int height = rect.size.height
, vous perdrez une précision en virgule flottante et des résultats incorrects.
Ancienne réponse (obsolète):
Si votre étiquette est multiligne, vous pouvez utiliser ce code:
CGSize perfectSize = [mylabel.text sizeWithFont:mylabel.font constrainedToSize:CGSizeMake(mylabel.bounds.size.width, NSIntegerMax) lineBreakMode:mylabel.lineBreakMode];
if (perfectSize.height > mylabel.bounds.size.height) {
NSLog(@"TOO MUCH");
}
vous pouvez créer une catégorie avec UILabel
- (BOOL)isTextTruncated
{
CGRect testBounds = self.bounds;
testBounds.size.height = NSIntegerMax;
CGRect limitActual = [self textRectForBounds:[self bounds] limitedToNumberOfLines:self.numberOfLines];
CGRect limitTest = [self textRectForBounds:testBounds limitedToNumberOfLines:self.numberOfLines + 1];
return limitTest.size.height>limitActual.size.height;
}
Utilisez cette catégorie pour savoir si une étiquette est tronquée sur iOS 7 et supérieur.
// UILabel+Truncation.h
@interface UILabel (Truncation)
@property (nonatomic, readonly) BOOL isTruncated;
@end
// UILabel+Truncation.m
@implementation UILabel (Truncation)
- (BOOL)isTruncated
{
CGSize sizeOfText =
[self.text boundingRectWithSize:CGSizeMake(self.bounds.size.width, CGFLOAT_MAX)
options:(NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading)
attributes:@{ NSFontAttributeName : label.font }
context: nil].size;
if (self.frame.size.height < ceilf(sizeOfText.height))
{
return YES;
}
return NO;
}
@end
Pour ajouter à la réponse de iDev , vous devez utiliser intrinsicContentSize
au lieu de frame
, pour le faire fonctionner pour Autolayout
- (BOOL)isTruncated:(UILabel *)label{
CGSize sizeOfText = [label.text boundingRectWithSize: CGSizeMake(label.intrinsicContentSize.width, CGFLOAT_MAX)
options: (NSStringDrawingUsesLineFragmentOrigin|NSStringDrawingUsesFontLeading)
attributes: [NSDictionary dictionaryWithObject:label.font forKey:NSFontAttributeName] context: nil].size;
if (self.intrinsicContentSize.height < ceilf(sizeOfText.height)) {
return YES;
}
return NO;
}
Swift
Vous pouvez compter le nombre de lignes après avoir attribué la chaîne et comparer au nombre maximal de lignes de l'étiquette.
import Foundation
import UIKit
extension UILabel {
func countLabelLines() -> Int {
// Call self.layoutIfNeeded() if your view is uses auto layout
let myText = self.text! as NSString
let attributes = [NSFontAttributeName : self.font]
let labelSize = myText.boundingRect(with: CGSize(width: self.bounds.width, height: CGFloat.greatestFiniteMagnitude), options: NSStringDrawingOptions.usesLineFragmentOrigin, attributes: attributes, context: nil)
return Int(ceil(CGFloat(labelSize.height) / self.font.lineHeight))
}
func isTruncated() -> Bool {
if (self.countLabelLines() > self.numberOfLines) {
return true
}
return false
}
}
Ça y est. Cela fonctionne avec attributedText
, avant de retomber sur text
, ce qui est très logique pour nous qui traitons avec plusieurs familles de polices, tailles et même NSTextAttachments!
Fonctionne très bien avec la mise en page automatique, mais de toute évidence, les contraintes doivent être définies et définies avant de vérifier isTruncated
, sinon l'étiquette elle-même ne saura même pas comment se présenter, donc elle ne saurait même pas si elle est tronquée.
Il ne fonctionne pas pour aborder ce problème avec un simple NSString
et sizeThatFits
. Je ne sais pas comment les gens obtenaient des résultats positifs comme ça. BTW, comme mentionné à plusieurs reprises, l'utilisation de sizeThatFits
n'est pas idéale du tout car elle prend en compte numberOfLines
pour la taille résultante, ce qui va à l'encontre de l'objectif de ce que nous essayons de faire, parce que isTruncated
renverrait toujours false
, qu'il soit tronqué ou non.
extension UILabel {
var isTruncated: Bool {
layoutIfNeeded()
let rectBounds = CGSize(width: bounds.width, height: .greatestFiniteMagnitude)
var fullTextHeight: CGFloat?
if attributedText != nil {
fullTextHeight = attributedText?.boundingRect(with: rectBounds, options: .usesLineFragmentOrigin, context: nil).size.height
} else {
fullTextHeight = text?.boundingRect(with: rectBounds, options: .usesLineFragmentOrigin, attributes: [NSAttributedString.Key.font: font], context: nil).size.height
}
return (fullTextHeight ?? 0) > bounds.size.height
}
}
J'ai écrit une catégorie pour travailler avec la troncature d'UILabel. Fonctionne sur iOS 7 et versions ultérieures. J'espère que ça aide ! troncature de queue uilabel
@implementation UILabel (Truncation)
- (NSRange)truncatedRange
{
NSTextStorage *textStorage = [[NSTextStorage alloc] initWithAttributedString:[self attributedText]];
NSLayoutManager *layoutManager = [[NSLayoutManager alloc] init];
[textStorage addLayoutManager:layoutManager];
NSTextContainer *textContainer = [[NSTextContainer alloc] initWithSize:[self bounds].size];
textContainer.lineFragmentPadding = 0;
[layoutManager addTextContainer:textContainer];
NSRange truncatedrange = [layoutManager truncatedGlyphRangeInLineFragmentForGlyphAtIndex:0];
return truncatedrange;
}
- (BOOL)isTruncated
{
return [self truncatedRange].location != NSNotFound;
}
- (NSString *)truncatedText
{
NSRange truncatedrange = [self truncatedRange];
if (truncatedrange.location != NSNotFound)
{
return [self.text substringWithRange:truncatedrange];
}
return nil;
}
@end
Cela fonctionne pour iOS 8:
CGSize size = [label.text boundingRectWithSize:CGSizeMake(label.bounds.size.width, NSIntegerMax) options:NSStringDrawingUsesLineFragmentOrigin attributes:@{NSFontAttributeName : label.font} context:nil].size;
if (size.height > label.frame.size.height) {
NSLog(@"truncated");
}
J'ai eu des problèmes avec boundingRect(with:options:attributes:context:)
lors de l'utilisation de la mise en page automatique (pour définir une hauteur maximale) et un texte attribué avec NSParagraph.lineSpacing
L'espacement entre les lignes a été ignoré (même lorsqu'il a été transmis dans attributes
à la méthode boundingRect
) de sorte que l'étiquette peut être considérée comme non tronquée lorsqu'elle l'était.
La solution que j'ai trouvée consiste à utiliser UIView.sizeThatFits
:
extension UILabel {
var isTruncated: Bool {
layoutIfNeeded()
let heightThatFits = sizeThatFits(bounds.size).height
return heightThatFits > bounds.size.height
}
}
extension UILabel {
public func resizeIfNeeded() -> CGFloat? {
guard let text = text, !text.isEmpty else { return nil }
if isTruncated() {
numberOfLines = 0
sizeToFit()
return frame.height
}
return nil
}
func isTruncated() -> Bool {
guard let text = text, !text.isEmpty else { return false }
let size: CGSize = text.size(withAttributes: [NSAttributedStringKey.font: font])
return size.width > self.bounds.size.width
}
}
Vous pouvez calculer la largeur de la chaîne et voir si la largeur est supérieure à la largeur de l'étiquette.
Voici la réponse sélectionnée dans Swift 3 (en tant qu'extension). L'OP demandait environ 1 étiquette de ligne. La plupart des réponses Swift que j'ai essayées ici sont spécifiques) sur les étiquettes multilignes et ne signalent pas correctement les étiquettes sur une seule ligne.
extension UILabel {
var isTruncated: Bool {
guard let labelText = text as? NSString else {
return false
}
let size = labelText.size(attributes: [NSFontAttributeName: font])
return size.width > self.bounds.width
}
}
Pour ajouter à ce que @ iDev a fait, j'ai modifié le self.frame.size.height
utiliser label.frame.size.height
et n'a pas non plus utilisé NSStringDrawingUsesLineFontLeading
. Après ces modifications, j'ai réalisé un calcul parfait du moment où la troncature se produirait (au moins pour mon cas).
- (BOOL)isTruncated:(UILabel *)label {
CGSize sizeOfText = [label.text boundingRectWithSize: CGSizeMake(label.bounds.size.width, CGFLOAT_MAX)
options: (NSStringDrawingUsesLineFragmentOrigin)
attributes: [NSDictionary dictionaryWithObject:label.font forKey:NSFontAttributeName] context: nil].size;
if (label.frame.size.height < ceilf(sizeOfText.height)) {
return YES;
}
return NO;
}
Parce que toutes les réponses ci-dessus utilisent des méthodes obsolètes, j'ai pensé que cela pourrait être utile:
- (BOOL)isLabelTruncated:(UILabel *)label
{
BOOL isTruncated = NO;
CGRect labelSize = [label.text boundingRectWithSize:CGSizeFromString(label.text) options:NSStringDrawingUsesLineFragmentOrigin attributes:@{NSFontAttributeName : label.font} context:nil];
if (labelSize.size.width / labelSize.size.height > label.numberOfLines) {
isTruncated = YES;
}
return isTruncated;
}
Pour gérer iOS 6 (oui, certains d'entre nous doivent encore le faire), voici encore une autre extension de la réponse de @ iDev. La clé à retenir est que, pour iOS 6, pour vous assurer que numberOfLines de votre UILabel est défini sur 0 avant d'appeler sizeThatFits; sinon, il vous donnera un résultat qui dit que "les points pour dessiner la valeur de numberOfLines de hauteur" sont nécessaires pour dessiner le texte de l'étiquette.
- (BOOL)isTruncated
{
CGSize sizeOfText;
// iOS 7 & 8
if([self.text respondsToSelector:@selector(boundingRectWithSize:options:attributes:context:)])
{
sizeOfText = [self.text boundingRectWithSize:CGSizeMake(self.bounds.size.width,CGFLOAT_MAX)
options:(NSStringDrawingUsesLineFragmentOrigin|NSStringDrawingUsesFontLeading)
attributes:@{NSFontAttributeName:self.font}
context:nil].size;
}
// iOS 6
else
{
// For iOS6, set numberOfLines to 0 (i.e. draw label text using as many lines as it takes)
// so that siteThatFits works correctly. If we leave it = 1 (for example), it'll come
// back telling us that we only need 1 line!
NSInteger origNumLines = self.numberOfLines;
self.numberOfLines = 0;
sizeOfText = [self sizeThatFits:CGSizeMake(self.bounds.size.width,CGFLOAT_MAX)];
self.numberOfLines = origNumLines;
}
return ((self.bounds.size.height < sizeOfText.height) ? YES : NO);
}