J'ai un UILabel
avec un espace pour deux lignes de texte. Parfois, lorsque le texte est trop court, ce texte est affiché au centre vertical de l'étiquette.
Comment aligner verticalement le texte pour toujours figurer en haut de la UILabel
?
Il n'y a aucun moyen de définir l'alignement vertical sur une UILabel
, mais vous pouvez obtenir le même effet en modifiant le cadre de l'étiquette. J'ai fait mes étiquettes en orange afin que vous puissiez voir clairement ce qui se passe.
Voici le moyen rapide et facile de le faire:
[myLabel sizeToFit];
Si vous avez une étiquette avec un texte plus long qui fera plus d’une ligne, définissez numberOfLines
sur 0
(zéro signifie ici un nombre illimité de lignes).
myLabel.numberOfLines = 0;
[myLabel sizeToFit];
Version plus longue
Je vais faire mon étiquette en code afin que vous puissiez voir ce qui se passe. Vous pouvez également configurer la plupart de ces éléments dans Interface Builder. Ma configuration est une application basée sur une vue avec une image d'arrière-plan que j'ai créée dans Photoshop pour afficher les marges (20 points). L'étiquette est de couleur orange attrayante pour que vous puissiez voir ce qui se passe avec les dimensions.
- (void)viewDidLoad
{
[super viewDidLoad];
// 20 point top and left margin. Sized to leave 20 pt at right.
CGRect labelFrame = CGRectMake(20, 20, 280, 150);
UILabel *myLabel = [[UILabel alloc] initWithFrame:labelFrame];
[myLabel setBackgroundColor:[UIColor orangeColor]];
NSString *labelText = @"I am the very model of a modern Major-General, I've information vegetable, animal, and mineral";
[myLabel setText:labelText];
// Tell the label to use an unlimited number of lines
[myLabel setNumberOfLines:0];
[myLabel sizeToFit];
[self.view addSubview:myLabel];
}
Certaines limitations de sizeToFit
entrent en jeu avec le texte aligné au centre ou à droite. Voici ce qui se passe:
// myLabel.textAlignment = NSTextAlignmentRight;
myLabel.textAlignment = NSTextAlignmentCenter;
[myLabel setNumberOfLines:0];
[myLabel sizeToFit];
L'étiquette est toujours de taille avec un coin fixe en haut à gauche. Vous pouvez enregistrer la largeur de l'étiquette d'origine dans une variable et la définir après sizeToFit
, ou lui attribuer une largeur fixe pour contrer ces problèmes:
myLabel.textAlignment = NSTextAlignmentCenter;
[myLabel setNumberOfLines:0];
[myLabel sizeToFit];
CGRect myFrame = myLabel.frame;
// Resize the frame's width to 280 (320 - margins)
// width could also be myOriginalLabelFrame.size.width
myFrame = CGRectMake(myFrame.Origin.x, myFrame.Origin.y, 280, myFrame.size.height);
myLabel.frame = myFrame;
Notez que sizeToFit
respectera la largeur minimale de votre étiquette initiale. Si vous commencez avec une étiquette de largeur 100 et que vous appelez sizeToFit
dessus, vous obtiendrez une étiquette (éventuellement très grande) de largeur 100 (ou un peu moins). Vous voudrez peut-être définir votre étiquette à la largeur minimale souhaitée avant le redimensionnement.
Quelques autres choses à noter:
Le respect de lineBreakMode
dépend de la manière dont elle est définie. NSLineBreakByTruncatingTail
(la valeur par défaut) est ignorée après sizeToFit
, de même que les deux autres modes de troncature (tête et milieu). NSLineBreakByClipping
est également ignoré. NSLineBreakByCharWrapping
fonctionne comme d'habitude. La largeur du cadre est toujours réduite pour correspondre à la lettre la plus à droite.
Mark Amery a corrigé les problèmes de NIB et de Storyboards en utilisant la disposition automatique dans les commentaires:
Si votre étiquette est incluse dans une plume ou un storyboard en tant que sous-vue de la
view
d'un ViewController qui utilise autolayout, le fait de placer votre appelsizeToFit
dansviewDidLoad
ne fonctionnera pas, car la taille et le positionnement des sous-vues aprèsviewDidLoad
seront appelés et seront immédiatement annulés les effets de votre appelsizeToFit
. Cependant, appelersizeToFit
à partir deviewDidLayoutSubviews
sera travail.
Ceci utilise la méthode NSString
sizeWithFont:constrainedToSize:lineBreakMode:
pour calculer la hauteur de trame nécessaire pour adapter une chaîne, puis définit l'origine et la largeur.
Redimensionnez le cadre de l'étiquette à l'aide du texte que vous souhaitez insérer. De cette façon, vous pouvez accueillir un nombre quelconque de lignes.
CGSize maximumSize = CGSizeMake(300, 9999);
NSString *dateString = @"The date today is January 1st, 1999";
UIFont *dateFont = [UIFont fontWithName:@"Helvetica" size:14];
CGSize dateStringSize = [dateString sizeWithFont:dateFont
constrainedToSize:maximumSize
lineBreakMode:self.dateLabel.lineBreakMode];
CGRect dateFrame = CGRectMake(10, 10, 300, dateStringSize.height);
self.dateLabel.frame = dateFrame;
Définissez le nouveau texte:
myLabel.text = @"Some Text"
Définissez le maximum number
des lignes sur 0 (automatique):
myLabel.numberOfLines = 0
Définissez le cadre de l'étiquette sur la taille maximale:
myLabel.frame = CGRectMake(20,20,200,800)
Appelez sizeToFit
pour réduire la taille du cadre afin que le contenu s'adapte parfaitement:
[myLabel sizeToFit]
Le cadre des étiquettes est maintenant juste assez haut et large pour s’adapter à votre texte. Le coin supérieur gauche devrait rester inchangé. Je l'ai testé uniquement avec du texte aligné en haut à gauche. Pour d'autres alignements, vous devrez peut-être modifier le cadre ultérieurement.
De plus, l'habillage de Word est activé sur mon étiquette.
En référence à la solution d'extension:
for(int i=1; i< newLinesToPad; i++)
self.text = [self.text stringByAppendingString:@"\n"];
devrait être remplacé par
for(int i=0; i<newLinesToPad; i++)
self.text = [self.text stringByAppendingString:@"\n "];
Un espace supplémentaire est nécessaire dans chaque nouvelle ligne ajoutée, car l'iPhone UILabels
'renvoie le retour en fin de chariot semble être ignoré :(
De même, alignBottom doit également être mis à jour avec un @" \n@%"
à la place de "\n@%"
(pour l'initialisation du cycle, il doit être remplacé par "for (int i = 0 ..." également).
L'extension suivante fonctionne pour moi:
// -- file: UILabel+VerticalAlign.h
#pragma mark VerticalAlign
@interface UILabel (VerticalAlign)
- (void)alignTop;
- (void)alignBottom;
@end
// -- file: UILabel+VerticalAlign.m
@implementation UILabel (VerticalAlign)
- (void)alignTop {
CGSize fontSize = [self.text sizeWithFont:self.font];
double finalHeight = fontSize.height * self.numberOfLines;
double finalWidth = self.frame.size.width; //expected width of label
CGSize theStringSize = [self.text sizeWithFont:self.font constrainedToSize:CGSizeMake(finalWidth, finalHeight) lineBreakMode:self.lineBreakMode];
int newLinesToPad = (finalHeight - theStringSize.height) / fontSize.height;
for(int i=0; i<newLinesToPad; i++)
self.text = [self.text stringByAppendingString:@"\n "];
}
- (void)alignBottom {
CGSize fontSize = [self.text sizeWithFont:self.font];
double finalHeight = fontSize.height * self.numberOfLines;
double finalWidth = self.frame.size.width; //expected width of label
CGSize theStringSize = [self.text sizeWithFont:self.font constrainedToSize:CGSizeMake(finalWidth, finalHeight) lineBreakMode:self.lineBreakMode];
int newLinesToPad = (finalHeight - theStringSize.height) / fontSize.height;
for(int i=0; i<newLinesToPad; i++)
self.text = [NSString stringWithFormat:@" \n%@",self.text];
}
@end
Ensuite, appelez [yourLabel alignTop];
ou [yourLabel alignBottom];
après chaque assignation de texte yourLabel.
Juste au cas où cela aiderait quelqu'un, j’avais le même problème, mais j’ai pu le résoudre simplement en passant de UILabel
à UITextView
. J'apprécie que ce ne soit pas pour tout le monde car la fonctionnalité est un peu différente.
Si vous passez à utiliser UITextView
, vous pouvez désactiver toutes les propriétés de défilement de la vue ainsi que l'interaction utilisateur activée ... Cela l'obligera à se comporter davantage comme une étiquette.
Pas de muss, pas de chichi
@interface MFTopAlignedLabel : UILabel
@end
@implementation MFTopAlignedLabel
- (void)drawTextInRect:(CGRect) rect
{
NSAttributedString *attributedText = [[NSAttributedString alloc] initWithString:self.text attributes:@{NSFontAttributeName:self.font}];
rect.size.height = [attributedText boundingRectWithSize:rect.size
options:NSStringDrawingUsesLineFragmentOrigin
context:nil].size.height;
if (self.numberOfLines != 0) {
rect.size.height = MIN(rect.size.height, self.numberOfLines * self.font.lineHeight);
}
[super drawTextInRect:rect];
}
@end
Pas de muss, pas d'objectif-c, pas de chichi mais Swift 3:
class VerticalTopAlignLabel: UILabel {
override func drawText(in rect:CGRect) {
guard let labelText = text else { return super.drawText(in: rect) }
let attributedText = NSAttributedString(string: labelText, attributes: [NSFontAttributeName: font])
var newRect = rect
newRect.size.height = attributedText.boundingRect(with: rect.size, options: .usesLineFragmentOrigin, context: nil).size.height
if numberOfLines != 0 {
newRect.size.height = min(newRect.size.height, CGFloat(numberOfLines) * font.lineHeight)
}
super.drawText(in: newRect)
}
}
Comme la réponse ci-dessus, mais ce n'était pas tout à fait correct, ou facile à insérer dans le code alors je l'ai nettoyé un peu. Ajoutez cette extension à ses propres fichiers .h et .m ou collez-la juste au-dessus de l'implémentation que vous souhaitez utiliser:
#pragma mark VerticalAlign
@interface UILabel (VerticalAlign)
- (void)alignTop;
- (void)alignBottom;
@end
@implementation UILabel (VerticalAlign)
- (void)alignTop
{
CGSize fontSize = [self.text sizeWithFont:self.font];
double finalHeight = fontSize.height * self.numberOfLines;
double finalWidth = self.frame.size.width; //expected width of label
CGSize theStringSize = [self.text sizeWithFont:self.font constrainedToSize:CGSizeMake(finalWidth, finalHeight) lineBreakMode:self.lineBreakMode];
int newLinesToPad = (finalHeight - theStringSize.height) / fontSize.height;
for(int i=0; i<= newLinesToPad; i++)
{
self.text = [self.text stringByAppendingString:@" \n"];
}
}
- (void)alignBottom
{
CGSize fontSize = [self.text sizeWithFont:self.font];
double finalHeight = fontSize.height * self.numberOfLines;
double finalWidth = self.frame.size.width; //expected width of label
CGSize theStringSize = [self.text sizeWithFont:self.font constrainedToSize:CGSizeMake(finalWidth, finalHeight) lineBreakMode:self.lineBreakMode];
int newLinesToPad = (finalHeight - theStringSize.height) / fontSize.height;
for(int i=0; i< newLinesToPad; i++)
{
self.text = [NSString stringWithFormat:@" \n%@",self.text];
}
}
@end
Et puis, à utiliser, placez votre texte dans l'étiquette, puis appelez la méthode appropriée pour l'aligner:
[myLabel alignTop];
ou
[myLabel alignBottom];
Un moyen encore plus rapide (et plus salissant) d'y parvenir est de définir le mode de saut de ligne de UILabel sur "Clip" et d'ajouter un nombre fixe de nouvelles lignes.
myLabel.lineBreakMode = UILineBreakModeClip;
myLabel.text = [displayString stringByAppendingString:"\n\n\n\n"];
Cette solution ne fonctionnera pas pour tout le monde - en particulier, si vous souhaitez toujours afficher "..." à la fin de votre chaîne, si celle-ci dépasse le nombre de lignes que vous affichez, vous devez utiliser l'une des options suivantes: les plus longs morceaux de code - mais dans de nombreux cas, cela vous donnera ce dont vous avez besoin.
Au lieu de UILabel
, vous pouvez utiliser UITextField
avec l’option d’alignement vertical suivante:
textField.contentVerticalAlignment = UIControlContentVerticalAlignmentCenter;
textField.userInteractionEnabled = NO; // Don't allow interaction
Je lutte avec celui-ci depuis longtemps et je voulais partager ma solution.
Cela vous donnera une UILabel
qui réduira automatiquement le texte à 0,5 échelle et le centrera verticalement. Ces options sont également disponibles dans Storyboard/IB.
[labelObject setMinimumScaleFactor:0.5];
[labelObject setBaselineAdjustment:UIBaselineAdjustmentAlignCenters];
Créer une nouvelle classe
LabelTopAlign
fichier .h
#import <UIKit/UIKit.h>
@interface KwLabelTopAlign : UILabel {
}
@end
fichier .m
#import "KwLabelTopAlign.h"
@implementation KwLabelTopAlign
- (void)drawTextInRect:(CGRect)rect {
int lineHeight = [@"IglL" sizeWithFont:self.font constrainedToSize:CGSizeMake(rect.size.width, 9999.0f)].height;
if(rect.size.height >= lineHeight) {
int textHeight = [self.text sizeWithFont:self.font constrainedToSize:CGSizeMake(rect.size.width, rect.size.height)].height;
int yMax = textHeight;
if (self.numberOfLines > 0) {
yMax = MIN(lineHeight*self.numberOfLines, yMax);
}
[super drawTextInRect:CGRectMake(rect.Origin.x, rect.Origin.y, rect.size.width, yMax)];
}
}
@end
Voici une implémentation plus simple qui fait la même chose:
#import "KwLabelTopAlign.h"
@implementation KwLabelTopAlign
- (void)drawTextInRect:(CGRect)rect
{
CGFloat height = [self.text sizeWithFont:self.font
constrainedToSize:rect.size
lineBreakMode:self.lineBreakMode].height;
if (self.numberOfLines != 0) {
height = MIN(height, self.font.lineHeight * self.numberOfLines);
}
rect.size.height = MIN(rect.size.height, height);
[super drawTextInRect:rect];
}
@end
Dans Interface Builder
UILabel
à la taille du plus grand texte possibleLines
sur '0' dans l'inspecteur d'attributsDans votre code
sizeToFit
sur votre étiquetteExtrait de code:
self.myLabel.text = @"Short Title";
[self.myLabel sizeToFit];
Créez une sous-classe de UILabel. Fonctionne comme un charme:
// TopLeftLabel.h
#import <Foundation/Foundation.h>
@interface TopLeftLabel : UILabel
{
}
@end
// TopLeftLabel.m
#import "TopLeftLabel.h"
@implementation TopLeftLabel
- (id)initWithFrame:(CGRect)frame
{
return [super initWithFrame:frame];
}
- (CGRect)textRectForBounds:(CGRect)bounds limitedToNumberOfLines:(NSInteger)numberOfLines
{
CGRect textRect = [super textRectForBounds:bounds limitedToNumberOfLines:numberOfLines];
textRect.Origin.y = bounds.Origin.y;
return textRect;
}
-(void)drawTextInRect:(CGRect)requestedRect
{
CGRect actualRect = [self textRectForBounds:requestedRect limitedToNumberOfLines:self.numberOfLines];
[super drawTextInRect:actualRect];
}
@end
Comme discuté ici .
Pour l'interface utilisateur adaptative (iOS8 ou ultérieure), l'alignement vertical d'UILabel doit être défini à partir de StoryBoard en modifiant les propriétés noOfLines
= 0` et
Contraintes
Réglage des contraintes UILabel LefMargin, RightMargin et Top Margin.
Modifiez
Content Compression Resistance Priority For Vertical
= 1000` de manière à ce que Vertical> Horizontal.
Édité:
noOfLines=0
et les contraintes suivantes suffisent pour atteindre les résultats souhaités.
J'ai écrit une fonction util pour atteindre cet objectif. Vous pouvez jeter un oeil:
// ajuste la hauteur d'une étiquette multiligne pour l'aligner verticalement avec le haut + (void) alignLabelWithTop: (UILabel *) label { CGSize maxSize = CGSizeMake (label.frame.size.width, 999); label.adjustsFontSizeToFitWidth = NO; // obtenir la hauteur réelle CGSize actualSize = [label.text sizeWithFont: label.font constrainedToSize: maxSize lineBreakMode: label.lineBreakMode]. CGRect rect = label.frame; rect.size.height = actualSize.height; label.frame = rect; }
.Comment utiliser? (Si lblHello est créé par le générateur Interface, je saute quelques détails sur les attributs UILabel)
lblHello.text = @ "Bonjour tout le monde! Bonjour tout le monde! Bonjour tout le monde! Bonjour tout le monde! Bonjour tout le monde! Bonjour tout le monde!"; : lblHello];
Je l'ai également écrit sur mon blog sous la forme d'un article: http://fstoke.me/blog/?p=2819
J'ai mis un certain temps à lire le code, ainsi que le code de la page d'introduction, et j'ai constaté qu'ils essayaient tous de modifier la taille du cadre de l'étiquette, de sorte que l'alignement vertical central par défaut n'apparaisse pas.
Toutefois, dans certains cas, nous souhaitons que l’étiquette occupe tous ces espaces, même si elle contient beaucoup de texte (par exemple, plusieurs lignes de même hauteur).
Ici, j’ai utilisé un autre moyen de le résoudre, simplement en remplissant les nouvelles lignes jusqu’à la fin du libellé (notez que j’ai hérité de la UILabel
, mais ce n’est pas nécessaire):
CGSize fontSize = [self.text sizeWithFont:self.font];
finalHeight = fontSize.height * self.numberOfLines;
finalWidth = size.width; //expected width of label
CGSize theStringSize = [self.text sizeWithFont:self.font constrainedToSize:CGSizeMake(finalWidth, finalHeight) lineBreakMode:self.lineBreakMode];
int newLinesToPad = (finalHeight - theStringSize.height) / fontSize.height;
for(int i = 0; i < newLinesToPad; i++)
{
self.text = [self.text stringByAppendingString:@"\n "];
}
J'ai pris les suggestions ici et créé une vue qui peut envelopper un UILabel et le dimensionner et définir le nombre de lignes de sorte qu'il soit aligné en haut. Il suffit de mettre un UILabel en tant que sous-vue:
@interface TopAlignedLabelContainer : UIView
{
}
@end
@implementation TopAlignedLabelContainer
- (void)layoutSubviews
{
CGRect bounds = self.bounds;
for (UILabel *label in [self subviews])
{
if ([label isKindOfClass:[UILabel class]])
{
CGSize fontSize = [label.text sizeWithFont:label.font];
CGSize textSize = [label.text sizeWithFont:label.font
constrainedToSize:bounds.size
lineBreakMode:label.lineBreakMode];
label.numberOfLines = textSize.height / fontSize.height;
label.frame = CGRectMake(0, 0, textSize.width,
fontSize.height * label.numberOfLines);
}
}
}
@end
Vous pouvez utiliser TTTAttributedLabel , il prend en charge l’alignement vertical.
@property (nonatomic) TTTAttributedLabel* label;
<...>
//view's or viewController's init method
_label.verticalAlignment = TTTAttributedLabelVerticalAlignmentTop;
J'ai trouvé que les réponses à cette question étaient maintenant un peu obsolètes, alors ajoutez ceci pour les fans de la mise en page automatique.
La mise en page automatique rend ce problème plutôt trivial. En supposant que nous ajoutions l'étiquette à UIView *view
, le code suivant accomplira ceci:
UILabel *label = [[UILabel alloc] initWithFrame:CGRectZero];
[label setText:@"Some text here"];
[label setTranslatesAutoresizingMaskIntoConstraints:NO];
[view addSubview:label];
[view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[label]|" options:0 metrics:nil views:@{@"label": label}]];
[view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[label]" options:0 metrics:nil views:@{@"label": label}]];
La hauteur de l'étiquette sera calculée automatiquement (en utilisant sa intrinsicContentSize
) et l'étiquette sera positionnée bord à bord horizontalement, en haut de la view
.
Je souhaitais avoir une étiquette pouvant comporter plusieurs lignes, une taille de police minimale et centrée à la fois horizontalement et verticalement dans la vue parent. J'ai ajouté mon label par programmation à mon point de vue:
- (void) customInit {
// Setup label
self.label = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, self.frame.size.width, self.frame.size.height)];
self.label.numberOfLines = 0;
self.label.lineBreakMode = UILineBreakModeWordWrap;
self.label.textAlignment = UITextAlignmentCenter;
// Add the label as a subview
self.autoresizesSubviews = YES;
[self addSubview:self.label];
}
Et puis quand j'ai voulu changer le texte de mon étiquette ...
- (void) updateDisplay:(NSString *)text {
if (![text isEqualToString:self.label.text]) {
// Calculate the font size to use (save to label's font)
CGSize textConstrainedSize = CGSizeMake(self.frame.size.width, INT_MAX);
self.label.font = [UIFont systemFontOfSize:TICKER_FONT_SIZE];
CGSize textSize = [text sizeWithFont:self.label.font constrainedToSize:textConstrainedSize];
while (textSize.height > self.frame.size.height && self.label.font.pointSize > TICKER_MINIMUM_FONT_SIZE) {
self.label.font = [UIFont systemFontOfSize:self.label.font.pointSize-1];
textSize = [ticker.blurb sizeWithFont:self.label.font constrainedToSize:textConstrainedSize];
}
// In cases where the frame is still too large (when we're exceeding minimum font size),
// use the views size
if (textSize.height > self.frame.size.height) {
textSize = [text sizeWithFont:self.label.font constrainedToSize:self.frame.size];
}
// Draw
self.label.frame = CGRectMake(0, self.frame.size.height/2 - textSize.height/2, self.frame.size.width, textSize.height);
self.label.text = text;
}
[self setNeedsDisplay];
}
J'espère que ça aide quelqu'un!
J'ai utilisé beaucoup des méthodes ci-dessus et je veux juste ajouter une approche rapide et sale que j'ai utilisée:
myLabel.text = [NSString stringWithFormat:@"%@\n\n\n\n\n\n\n\n\n",@"My label text string"];
Assurez-vous que le nombre de nouvelles lignes dans la chaîne fera en sorte que le texte remplisse l'espace vertical disponible, et définissez le paramètre UILabel pour tronquer tout texte en débordement.
Parce que parfois assez bon est assez bon.
FXLabel (sur github) fait ceci en sortant de la boîte en définissant label.contentMode
à UIViewContentModeTop
. Ce composant n’est pas fabriqué par moi, mais c’est un composant que j’utilise fréquemment et qui possède une multitude de fonctionnalités et qui semble bien fonctionner.
si vous lisez ceci car le texte à l'intérieur de votre étiquette n'est pas centré verticalement, n'oubliez pas que certains types de police ne sont pas conçus de la même manière. Par exemple, si vous créez une étiquette avec une taille de zapfino 16, vous verrez que le texte n'est pas parfaitement centré verticalement.
cependant, travailler avec helvetica va centrer votre texte verticalement.
Si vous utilisez autolayout, définissez la valeur verticale contentHuggingPriority sur 1000, dans le code ou dans IB. Dans IB, vous devrez peut-être ensuite supprimer une contrainte de hauteur en définissant sa priorité sur 1, puis en la supprimant.
Sous-classe UILabel et contraignez le rectangle de dessin, comme ceci:
- (void)drawTextInRect:(CGRect)rect
{
CGSize sizeThatFits = [self sizeThatFits:rect.size];
rect.size.height = MIN(rect.size.height, sizeThatFits.height);
[super drawTextInRect:rect];
}
J'ai essayé la solution impliquant un remplissage avec une nouvelle ligne et j'ai rencontré un comportement incorrect dans certains cas. D'après mon expérience, il est plus facile de contraindre le rectangle de dessin comme ci-dessus que de manipuler numberOfLines
.
P.S. Vous pouvez imaginer facilement supporter UIViewContentMode de cette façon:
- (void)drawTextInRect:(CGRect)rect
{
CGSize sizeThatFits = [self sizeThatFits:rect.size];
if (self.contentMode == UIViewContentModeTop) {
rect.size.height = MIN(rect.size.height, sizeThatFits.height);
}
else if (self.contentMode == UIViewContentModeBottom) {
rect.Origin.y = MAX(0, rect.size.height - sizeThatFits.height);
rect.size.height = MIN(rect.size.height, sizeThatFits.height);
}
[super drawTextInRect:rect];
}
Tant que vous n'effectuez aucune tâche complexe, vous pouvez utiliser UITextView
au lieu de UILabels
.
Désactiver le défilement.
Si vous voulez que le texte soit affiché complètement, utilisez uniquement les méthodes sizeToFit
et sizeThatFits:
de l'utilisateur
Utilisez textRect(forBounds:limitedToNumberOfLines:)
.
class TopAlignedLabel: UILabel {
override func drawText(in rect: CGRect) {
let textRect = super.textRect(forBounds: bounds, limitedToNumberOfLines: numberOfLines)
super.drawText(in: textRect)
}
}
Ceci est une ancienne solution, utilisez autolayout sur iOS> = 6
Ma solution:
1/Fractionner des lignes par moi-même (en ignorant les paramètres d’emballage des étiquettes)
2/Dessiner des lignes par moi-même (en ignorant l'alignement de l'étiquette)
@interface UITopAlignedLabel : UILabel
@end
@implementation UITopAlignedLabel
#pragma mark Instance methods
- (NSArray*)splitTextToLines:(NSUInteger)maxLines {
float width = self.frame.size.width;
NSArray* words = [self.text componentsSeparatedByCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
NSMutableArray* lines = [NSMutableArray array];
NSMutableString* buffer = [NSMutableString string];
NSMutableString* currentLine = [NSMutableString string];
for (NSString* Word in words) {
if ([buffer length] > 0) {
[buffer appendString:@" "];
}
[buffer appendString:Word];
if (maxLines > 0 && [lines count] == maxLines - 1) {
[currentLine setString:buffer];
continue;
}
float bufferWidth = [buffer sizeWithFont:self.font].width;
if (bufferWidth < width) {
[currentLine setString:buffer];
}
else {
[lines addObject:[NSString stringWithString:currentLine]];
[buffer setString:Word];
[currentLine setString:buffer];
}
}
if ([currentLine length] > 0) {
[lines addObject:[NSString stringWithString:currentLine]];
}
return lines;
}
- (void)drawRect:(CGRect)rect {
if ([self.text length] == 0) {
return;
}
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetFillColorWithColor(context, self.textColor.CGColor);
CGContextSetShadowWithColor(context, self.shadowOffset, 0.0f, self.shadowColor.CGColor);
NSArray* lines = [self splitTextToLines:self.numberOfLines];
NSUInteger numLines = [lines count];
CGSize size = self.frame.size;
CGPoint Origin = CGPointMake(0.0f, 0.0f);
for (NSUInteger i = 0; i < numLines; i++) {
NSString* line = [lines objectAtIndex:i];
if (i == numLines - 1) {
[line drawAtPoint:Origin forWidth:size.width withFont:self.font lineBreakMode:UILineBreakModeTailTruncation];
}
else {
[line drawAtPoint:Origin forWidth:size.width withFont:self.font lineBreakMode:UILineBreakModeClip];
}
Origin.y += self.font.lineHeight;
if (Origin.y >= size.height) {
return;
}
}
}
@end
Dans UILabel verticalement, l'alignement du texte n'est pas possible. Cependant, vous pouvez modifier dynamiquement la hauteur de l'étiquette à l'aide de la méthode sizeWithFont:
de NSString
et définir ses x et y comme vous le souhaitez.
Vous pouvez utiliser UITextField
. Il prend en charge le contentVerticalAlignment peoperty car il s’agit d’une sous-classe de UIControl
. Vous devez définir sa userInteractionEnabled
sur NO pour empêcher l'utilisateur de saisir du texte dessus.
Si la création de votre propre vue personnalisée est une option, vous pouvez procéder comme suit:
- (void)drawRect:(CGRect)rect
{
CGRect bounds = self.bounds;
[self.textColor set];
[self.text drawInRect:bounds
withFont:self.font
lineBreakMode:UILineBreakModeTailTruncation
alignment:self.textAlignment];
}
Je sais que c’est un vieux message, mais l’alignement vertical du texte est un problème ÉNORME (du moins pour moi) et j’ai pensé que je devrais partager cette solution car je n’arrivais pas à en trouver un.
L'utilisation de drawRect est un peu cher à mon avis. La bonne façon d’aligner verticalement un UILabel est de ne pas utiliser un UILabel. Utilisez une UITextView (UITextField multiligne) et observez la propriété content de la manière suivante:
- (UITextView*)textView{
if(!_textView){
UIEdgeInsets insets = UIEdgeInsetsMake(0, 50, 0, 5);
CGRect frame = CGRectMake(0.0f, 0.0f, 100.0f, 100.0f);
_textView = [[UITextView alloc]initWithFrame:UIEdgeInsetsInsetRect(frame, insets)];
_textView.delegate = self;
_textView.scrollEnabled = YES;
_textView.bounces = YES;
_textView.backgroundColor = [UIColor whiteColor];
[_textView setUserInteractionEnabled:NO];
[_textView addObserver:self forKeyPath:@"contentSize" options:(NSKeyValueObservingOptionNew) context:NULL];
}
return _textView;
}
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change: (NSDictionary *)change context:(void *)context {
UITextView *tv = object;
CGFloat height = [tv bounds].size.height;
CGFloat contentheight;
#ifdef __IPHONE_7_0
contentheight = [tv sizeThatFits:CGSizeMake(tv.frame.size.width, FLT_MAX)].height;
#else
contentheight = [tv contentSize].height;
#endif
switch(self.verticalAlignment) {
case VerticalAlignmentBottom:{
CGFloat topCorrect = ([tv bounds].size.height - contentheight);
topCorrect = (topCorrect <0.0 ? 0.0 : topCorrect);
tv.contentOffset = (CGPoint){.x = 0, .y = -topCorrect};
}
break;
case VerticalAlignmentMiddle:{
CGFloat topCorrect = (height - contentheight * [tv zoomScale])/2.0;
topCorrect = ( topCorrect < 0.0 ? 0.0 : topCorrect );
tv.contentOffset = (CGPoint){.x = 0, .y = -topCorrect};
}
break;
case VerticalAlignmentTop:{
tv.contentOffset = (CGPoint){.x = 0, .y = 0 };
}
break;
default:
break;
}
}
Ce qui se passe ici, c’est que nous définissons la classe dans laquelle nous sommes en tant qu’observateur, en examinant la propriété contentSize avec une option de NSKeyValueObservingOptionNew afin que chaque fois que le contenu change, -(void)observeValueForKeyPath:ofObject:change:context:
soit appelé, puis vous pouvez calculer une taille de décalage pour aligner le texte de manière appropriée.
Je ne peux pas en prendre le crédit, l'idée originale est venue de ici . Mais cette solution ne fonctionne pas sur iOS7. Après avoir traîné autour de SO pendant quelques heures, j'ai trouvé ceci: Correction d'alignement vertical iOS 7 . La ligne de clé à cet endroit est contentheight = [tv sizeThatFits:CGSizeMake(tv.frame.size.width, FLT_MAX)].height;
. Pour une raison quelconque dans iOS 7, obtenir la hauteur contentSize ne fonctionne pas, mais cela résout le problème. Aucune des deux solutions ne fonctionnait seule, mais après un peu de bricolage, j'ai été capable de synthétiser ensemble la solution ci-dessus.
yourLabel.baselineAdjustment = UIBaselineAdjustmentAlignCenters;
Swift 2.0:
Définissez des valeurs d'énumération constantes dans un fichier Swift vide.
// AppRef.Swift
import UIKit
import Foundation
enum UILabelTextPositions : String {
case VERTICAL_ALIGNMENT_TOP = "VerticalAlignmentTop"
case VERTICAL_ALIGNMENT_MIDDLE = "VerticalAlignmentMiddle"
case VERTICAL_ALIGNMENT_BOTTOM = "VerticalAlignmentBottom"
}
Utiliser l'extension UILabel:
Créez une classe Swift vide et nommez-la. Ajouter ce qui suit
// AppExtensions.Swift
import Foundation
import UIKit
extension UILabel{
func makeLabelTextPosition (sampleLabel :UILabel?, positionIdentifier : String) -> UILabel
{
let rect = sampleLabel!.textRectForBounds(bounds, limitedToNumberOfLines: 0)
switch positionIdentifier
{
case "VerticalAlignmentTop":
sampleLabel!.frame = CGRectMake(bounds.Origin.x+5, bounds.Origin.y, rect.size.width, rect.size.height)
break;
case "VerticalAlignmentMiddle":
sampleLabel!.frame = CGRectMake(bounds.Origin.x+5,bounds.Origin.y + (bounds.size.height - rect.size.height) / 2,
rect.size.width, rect.size.height);
break;
case "VerticalAlignmentBottom":
sampleLabel!.frame = CGRectMake(bounds.Origin.x+5, bounds.Origin.y + (bounds.size.height - rect.size.height),rect.size.width, rect.size.height);
break;
default:
sampleLabel!.frame = bounds;
break;
}
return sampleLabel!
}
}
Utilisation:
myMessageLabel.makeLabelTextPosition(messageLabel, positionIdentifier: UILabelTextPositions.VERTICAL_ALIGNMENT_TOP.rawValue)
Je travaillais aussi sur ce problème particulier, alors j’ai repris les idées de DS et de nevan king et les a fondamentalement combinées dans une sous-classe qui implémente une propriété d'alignement vertical, ce qui vous permet également de modifier l'alignement plusieurs fois. Il emprunte le type UIControlContentVerticalAlignment
et prend également en charge UIControlContentVerticalAlignmentFill
.
D'après ce que j'ai vu, numberOfLines
semble inutile en ce qui concerne l'alignement vertical. Par conséquent, dans cette sous-classe, il est toujours mis à 0 lors de l'application d'un alignement vertical. En outre, vous devez toujours définir vous-même lineBreakMode
si vous souhaitez une étiquette de texte multiligne.
Le voici: QALabel sur GitHub
J'ai suivi la suggestion de dalewking et ajouté un UIEdgeInset pour permettre une marge ajustable. Beau travail autour.
- (id)init
{
if (self = [super init]) {
contentEdgeInsets = UIEdgeInsetsZero;
}
return self;
}
- (void)layoutSubviews
{
CGRect localBounds = self.bounds;
localBounds = CGRectMake(MAX(0, localBounds.Origin.x + contentEdgeInsets.left),
MAX(0, localBounds.Origin.y + contentEdgeInsets.top),
MIN(localBounds.size.width, localBounds.size.width - (contentEdgeInsets.left + contentEdgeInsets.right)),
MIN(localBounds.size.height, localBounds.size.height - (contentEdgeInsets.top + contentEdgeInsets.bottom)));
for (UIView *subview in self.subviews) {
if ([subview isKindOfClass:[UILabel class]]) {
UILabel *label = (UILabel*)subview;
CGSize lineSize = [label.text sizeWithFont:label.font];
CGSize sizeForText = [label.text sizeWithFont:label.font constrainedToSize:localBounds.size lineBreakMode:label.lineBreakMode];
NSInteger numberOfLines = ceilf(sizeForText.height/lineSize.height);
label.numberOfLines = numberOfLines;
label.frame = CGRectMake(MAX(0, contentEdgeInsets.left), MAX(0, contentEdgeInsets.top), localBounds.size.width, MIN(localBounds.size.height, lineSize.height * numberOfLines));
}
}
}
Pour Swift 3 ...
@IBDesignable class TopAlignedLabel: UILabel {
override func drawText(in rect: CGRect) {
if let stringText = text {
let stringTextAsNSString = stringText as NSString
let labelStringSize = stringTextAsNSString.boundingRect(with: CGSize(width: self.frame.width,height: CGFloat.greatestFiniteMagnitude),
options: NSStringDrawingOptions.usesLineFragmentOrigin,
attributes: [NSFontAttributeName: font],
context: nil).size
super.drawText(in: CGRect(x:0,y: 0,width: self.frame.width, height:ceil(labelStringSize.height)))
} else {
super.drawText(in: rect)
}
}
override func prepareForInterfaceBuilder() {
super.prepareForInterfaceBuilder()
layer.borderWidth = 1
layer.borderColor = UIColor.black.cgColor
}
}
Il y a deux façons de résoudre ce problème.
[mylabel setNumberOfLines:0];
[mylabel sizeToFit];
Mais le deuxième moyen est plus fiable pour cette approche, c’est-à-dire
CGSize sizeToFit = [label.text sizeWithFont:label.font constrainedToSize:maxSize lineBreakMode:label.lineBreakMode];
[mylabel setFrame:CGRectMake(mylabel.frame.Origin.x, mylabel.frame.Origin.y, sizeToFit.width, sizeToFit.height)];
entrer "\ n" n’est pas une bonne chose, mais oui, si vous connaissez les contraintes et la taille des données à afficher, cela peut fonctionner mais vous ne pouvez pas le développer si le texte est plus long que la taille de l’étiquette. La deuxième façon de définir le cadre en fonction de la taille du texte à afficher.
Ce code vous aide à aligner le texte en haut et à fixer la hauteur de l’étiquette au contenu du texte.
instruction d'utiliser le code ci-dessous
isHeightToChange
par défaut, il est vrai que la hauteur de l'étiquette est identique à celle du contenu du texte automatiquement .isHeightToChange
si elle est fausse, le texte est aligné en haut sans diminution de hauteur.
#import "DynamicHeightLable.h"
@implementation DynamicHeightLable
@synthesize isHeightToChange;
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self)
{
// Initialization code.
self.isHeightToChange=FALSE;//default
}
return self;
}
- (void)drawTextInRect:(CGRect)rect
{
if(!isHeightToChange){
CGSize maximumLabelSize = CGSizeMake(self.frame.size.width,2500);
CGFloat height = [self.text sizeWithFont:self.font
constrainedToSize:maximumLabelSize
lineBreakMode:self.lineBreakMode].height;
if (self.numberOfLines != 0) {
height = MIN(height, self.font.lineHeight * self.numberOfLines);
}
rect.size.height = MIN(rect.size.height, height);
}
[super drawTextInRect:rect];
}
- (void) layoutSubviews
{
[super layoutSubviews];
if(isHeightToChange){
CGRect ltempFrame = self.frame;
CGSize maximumLabelSize = CGSizeMake(self.frame.size.width,2500);
CGFloat height = [self.text sizeWithFont:self.font
constrainedToSize:maximumLabelSize
lineBreakMode:self.lineBreakMode].height;
if (self.numberOfLines != 0) {
height = MIN(height, self.font.lineHeight * self.numberOfLines);
}
ltempFrame.size.height = MIN(ltempFrame.size.height, height);
ltempFrame.size.height=ltempFrame.size.height;
self.frame=ltempFrame;
}
}
@end
Pour ceux d'entre vous dont les cellules de tableau personnalisées tentent de résoudre ce problème, ajoutez ceci à votre classe de cellules de tableau personnalisée:
Swift 2.2:
override func layoutSubviews() {
labelName.sizeToFit()
}
Cela a résolu mon problème.
(Au 7 mars 2018)
Swift 4
let maxFrameHeight = 75
myLabel = UILabel()
myLabel.frame = CGRect(x: 9, y: 9, width: 126, height: maxFrameHeight)
myLabel.text = "my labels text displayed"
myLabel.numberOfLines = 0
myLabel.sizeToFit()
let newFrameHeight = myLabel.frame.size.height
let safeNewHeight = min(newFrameHeight, maxFrameHeight)
myLabel.frame = CGRect(x: 9, y: 9, width: 126, height: safeNewHeight)
Cela donnera le résultat souhaité et garantira que la nouvelle hauteur de UILabel ne dépasse pas la hauteur maximale souhaitée pour cette étiquette.
Au lieu d'utiliser sizeToFit
, vous pouvez modifier manuellement la hauteur de l'étiquette.
CGSize size = [descLabel.text sizeWithFont:descLabel.font constrainedToSize:CGSizeMake(labelWidth, labelHeight)];
CGRect frame = descLabel.frame;
frame.size.height = size.height;
[yourLabel setFrame:frame];
La taille retournée correspondra le mieux au contenu de votre étiquette. Si la hauteur de l'étiquette est adaptée à son contenu, vous ne rencontrerez aucun problème de positionnement du contenu au centre de l'étiquette.
En me basant sur toutes les autres solutions publiées, j'ai créé une petite sous-classe UILabel simple qui gérera l'alignement vertical pour vous lors de la définition de sa propriété d'alignement. Cela mettra également à jour l'étiquette lors des changements d'orientation, contraindra la hauteur au texte et maintiendra la largeur de l'étiquette à sa taille d'origine.
.h
@interface VAlignLabel : UILabel
@property (nonatomic, assign) WBZVerticalAlignment alignment;
@end
.m
-(void)setAlignment:(WBZVerticalAlignment)alignment{
_alignment = alignment;
CGSize s = [self.text sizeWithFont:self.font constrainedToSize:CGSizeMake(self.frame.size.width, 9999) lineBreakMode:NSLineBreakByWordWrapping];
switch (_alignment)
{
case wbzLabelAlignmentVerticallyTop:
self.frame = CGRectMake(self.frame.Origin.x, self.frame.Origin.y, self.frame.size.width, s.height);
break;
case wbzLabelAlignmentVerticallyMiddle:
self.frame = CGRectMake(self.frame.Origin.x, self.frame.Origin.y + (self.frame.size.height - s.height)/2, self.frame.size.width, s.height);
break;
case wbzLabelAlignmentVerticallyBottom:
self.frame = CGRectMake(self.frame.Origin.x, self.frame.Origin.y + (self.frame.size.height - s.height), self.frame.size.width, s.height);
break;
default:
break;
}
}
-(void)layoutSubviews{
[self setAlignment:self.alignment];
}
Solution simple à ce problème ...
Placez une UIStackView
au-dessous de l'étiquette.
Utilisez ensuite AutoLayout pour définir un espacement vertical de zéro sur l'étiquette et contraindre le haut à ce qui était précédemment le bas de l'étiquette. Le label ne va pas grandir, le stackView va le faire. Ce qui me plaît dans cette situation est que la vue de la pile n’est pas un rendu. Donc, ce n'est pas vraiment perdre du temps à rendre. Même s'il effectue des calculs AutoLayout.
Vous aurez probablement besoin de jouer avec ContentHugging and Resistance, mais c’est aussi simple.
De temps en temps, je recherche ce problème sur Google et je reviens ici. Cette fois, j'ai eu une idée que je pense être la plus facile, et je ne pense pas que ce soit une mauvaise performance. Je dois dire que je viens d'utiliser AutoLayout et que je ne veux pas vraiment me soucier de calculer des images. C'est juste ... beurk.
Cela peut être fait avec plus de flexibilité en définissant un height constraint
sur l'étiquette dans Interface Builder, en le liant au code avec un IBOutlet et en modifiant cette hauteur pour afficher le texte dans une position verticale concrète. Exemple d'alignement central et inférieur:
labelHeightConstraint.constant = centerAlignment ? 30 : 15
layoutIfNeeded()
Voici une solution dans Swift 4.0:
func viewDidLoad() {
super.viewDidLoad()
// 20 point top and left margin. Sized to leave 20 pt at right.
let labelFrame = CGRect(x: 20, y: 20, width: 280, height: 150)
let myLabel = UILabel(frame: labelFrame)
myLabel.backgroundColor = UIColor.orange
let labelText = "I am the very model of a modern Major-General,
I've information vegetable, animal, and mineral"
myLabel.text = labelText
// Tell the label to use an unlimited number of lines
myLabel.numberOfLines = 0
myLabel.sizeToFit()
view.addSubview(myLabel) // add label
}