Como definir o alinhamento superior esquerdo para o aplicativo UILabel para iOS?


99

Eu adicionei uma etiqueta em meu arquivo de ponta, então é necessário ter alinhamento superior esquerdo para essa etiqueta. Como estou fornecendo texto em tempo de execução, não tenho certeza de quantas linhas existem. Portanto, se o texto contém apenas uma linha, ele aparece alinhado com o centro vertical. Esse alinhamento não coincide com a minha respectiva etiqueta na frente dele.

Por exemplo:

insira a descrição da imagem aqui

Que parece estranho :(

Existe alguma maneira de definir o texto do rótulo adequado ao alinhamento superior esquerdo?


Respostas:



64

É bastante fácil de fazer. Crie uma UILabelsubcasse com uma verticalAlignmentpropriedade e substitua textRectForBounds:limitedToNumberOfLinespara retornar os limites corretos para um alinhamento vertical superior, médio ou inferior. Aqui está o código:

SOLabel.h

#import <UIKit/UIKit.h>

typedef enum
{
    VerticalAlignmentTop = 0, // default
    VerticalAlignmentMiddle,
    VerticalAlignmentBottom,
} VerticalAlignment;

@interface SOLabel : UILabel

   @property (nonatomic, readwrite) VerticalAlignment verticalAlignment;

@end

SOLabel.m

@implementation SOLabel

-(id)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (!self) return nil;

    // set inital value via IVAR so the setter isn't called
    _verticalAlignment = VerticalAlignmentTop;

    return self;
}

-(VerticalAlignment) verticalAlignment
{
    return _verticalAlignment;
}

-(void) setVerticalAlignment:(VerticalAlignment)value
{
    _verticalAlignment = value;
    [self setNeedsDisplay];
}

// align text block according to vertical alignment settings
-(CGRect)textRectForBounds:(CGRect)bounds 
    limitedToNumberOfLines:(NSInteger)numberOfLines
{
   CGRect rect = [super textRectForBounds:bounds 
                   limitedToNumberOfLines:numberOfLines];
    CGRect result;
    switch (_verticalAlignment)
    {
       case VerticalAlignmentTop:
          result = CGRectMake(bounds.origin.x, bounds.origin.y, 
                              rect.size.width, rect.size.height);
           break;

       case VerticalAlignmentMiddle:
          result = CGRectMake(bounds.origin.x, 
                    bounds.origin.y + (bounds.size.height - rect.size.height) / 2,
                    rect.size.width, rect.size.height);
          break;

       case VerticalAlignmentBottom:
          result = CGRectMake(bounds.origin.x, 
                    bounds.origin.y + (bounds.size.height - rect.size.height),
                    rect.size.width, rect.size.height);
          break;

       default:
          result = bounds;
          break;
    }
    return result;
}

-(void)drawTextInRect:(CGRect)rect
{
    CGRect r = [self textRectForBounds:rect 
                limitedToNumberOfLines:self.numberOfLines];
    [super drawTextInRect:r];
}

@end

3
Também tentei muitas outras soluções aqui no SO antes de encontrar esta. Funcionou perfeitamente! Lembre-se de que, se estiver fazendo isso no StoryBoard, certifique-se de definir o atributo CustomClass como SOLabel (ou qualquer outro nome que você decida dar) em vez de UILabel (no Utilities Inspector).
TMc

Isso é muito útil, obrigado. Não funciona para texto alinhado ao centro ou à direita, mas usar em bounds.size.widthvez de rect.size.widthem textRectForBounds:limitedToNumberOfLines:parece consertar isso.
Geoff Hackworth

1
Se você encontrou 'Thread 1: EXC_BAD_ACCESS (Code 2, address = 0x ...)' no iOS 9 Xcode 7, simplesmente remova o setter e getter - (VerticalAlignment) verticalAlignment; e - (void) setVerticalAlignment: (VerticalAlignment) funções de valor, uma vez que a variável é @property. É sintetizado e contém acessores.
felixwcf

eu fiz algumas alterações aqui no método: "textRectForBounds" - result = CGRectMake (rect.origin.x, bounds.origin.y, rect.size.width, rect.size.height); Para fazer meus trabalhos para UILable rightAlignment.
g212gs

50

Eu encontrei uma solução usando AutoLayout no StoryBoard.

1) Defina o número de linhas como 0 e o alinhamento do texto como Esquerda.

insira a descrição da imagem aqui

2) Defina a restrição de altura.

insira a descrição da imagem aqui

3) A restrição de altura deve estar em relação - menor ou igual

insira a descrição da imagem aqui

4)

   override func viewWillLayoutSubviews() {
        sampleLabel.sizeToFit()
    }

Obtive o resultado da seguinte forma:

insira a descrição da imagem aqui


2
Funciona perfeitamente, mesmo em um UITableViewCell com reutilização.
alex.bour

Você coloca o viewWillLayoutSubviewsno controlador ou no arquivo da célula? Se for o controlador, como ele acessa o UILabel da célula?
Craig.Pearce

Onde você coloca a etapa 4? Como um novo usuário, fiquei animado por ter uma solução puramente de IU, então esse código veio do nada e não nos disseram onde colocá-lo
velkoon

Tanto em SampleClass.swift ou SampleTableViewCell.swift
AG

Esta deve ser a solução. Funciona perfeitamente, sem necessidade de hack ou subclasse.
Curious101

44

O SOLabel funciona para mim.

Swift 3 e 5:

Esta versão foi atualizada a partir do original para permitir suporte para idiomas RTL:

public class VerticalAlignLabel: UILabel {
    enum VerticalAlignment {
        case top
        case middle
        case bottom
    }

    var verticalAlignment : VerticalAlignment = .top {
        didSet {
            setNeedsDisplay()
        }
    }

    override public func textRect(forBounds bounds: CGRect, limitedToNumberOfLines: Int) -> CGRect {
        let rect = super.textRect(forBounds: bounds, limitedToNumberOfLines: limitedToNumberOfLines)

        if UIView.userInterfaceLayoutDirection(for: .unspecified) == .rightToLeft {
            switch verticalAlignment {
            case .top:
                return CGRect(x: self.bounds.size.width - rect.size.width, y: bounds.origin.y, width: rect.size.width, height: rect.size.height)
            case .middle:
                return CGRect(x: self.bounds.size.width - rect.size.width, y: bounds.origin.y + (bounds.size.height - rect.size.height) / 2, width: rect.size.width, height: rect.size.height)
            case .bottom:
                return CGRect(x: self.bounds.size.width - rect.size.width, y: bounds.origin.y + (bounds.size.height - rect.size.height), width: rect.size.width, height: rect.size.height)
            }
        } else {
            switch verticalAlignment {
            case .top:
                return CGRect(x: bounds.origin.x, y: bounds.origin.y, width: rect.size.width, height: rect.size.height)
            case .middle:
                return CGRect(x: bounds.origin.x, y: bounds.origin.y + (bounds.size.height - rect.size.height) / 2, width: rect.size.width, height: rect.size.height)
            case .bottom:
                return CGRect(x: bounds.origin.x, y: bounds.origin.y + (bounds.size.height - rect.size.height), width: rect.size.width, height: rect.size.height)
            }
        }
    }

    override public func drawText(in rect: CGRect) {
        let r = self.textRect(forBounds: rect, limitedToNumberOfLines: self.numberOfLines)
        super.drawText(in: r)
    }
}

Swift 1:

class UIVerticalAlignLabel: UILabel {

enum VerticalAlignment : Int {
    case VerticalAlignmentTop = 0
    case VerticalAlignmentMiddle = 1
    case VerticalAlignmentBottom = 2
}

var verticalAlignment : VerticalAlignment = .VerticalAlignmentTop {
    didSet {
        setNeedsDisplay()
    }
}

required init(coder aDecoder: NSCoder){
    super.init(coder: aDecoder)
}

override func textRectForBounds(bounds: CGRect, limitedToNumberOfLines: Int) -> CGRect {
    let rect = super.textRectForBounds(bounds, limitedToNumberOfLines: limitedToNumberOfLines)

    switch(verticalAlignment) {
        case .VerticalAlignmentTop:
            return CGRectMake(bounds.origin.x, bounds.origin.y, rect.size.width, rect.size.height)
        case .VerticalAlignmentMiddle:
            return CGRectMake(bounds.origin.x, bounds.origin.y + (bounds.size.height - rect.size.height) / 2, rect.size.width, rect.size.height)
        case .VerticalAlignmentBottom:
            return CGRectMake(bounds.origin.x, bounds.origin.y + (bounds.size.height - rect.size.height), rect.size.width, rect.size.height)
        default:
            return bounds
    }
}

override func drawTextInRect(rect: CGRect) {
    let r = self.textRectForBounds(rect, limitedToNumberOfLines: self.numberOfLines)
    super.drawTextInRect(r)
    }
}

Se eu tentar criar um rótulo usando este código: var myLabel = VerticalAlignLabel (), obtenho um "Argumento ausente para o parâmetro 'coder' na chamada". Como posso criar um rótulo usando esta subclasse VerticalAlignLabel?
RanLearns de

1
Experimente o Swift versão 3 agora - eu tinha um init requerido que não é necessário.
totiG

14

No meu caso, foi uma bottom spacequestão de restrição. Eu tinha definido como = 16.

Quando o configurei como bottom to >= 16, esse problema foi resolvido.

Além disso, se houver alguma restrição de altura no rótulo, será necessário removê-la.

Esta é a visualização de restrição do meu rótulo no inspetor de tamanho:

limitação


Não tenho opções de restrição quando seleciono um rótulo.
velkoon

Correção mais simples - deixe que as restrições e o layout automático cuidem disso. Obrigado!
Jason

13

Em seu código

label.text = @"some text";
[label sizeToFit];

Esteja ciente de que, se você usar isso em células de tabela ou outras visualizações que são recicladas com dados diferentes, você precisará armazenar o quadro original em algum lugar e redefini-lo antes de chamar sizeToFit.


Eu recomendo deixar tudo para o Autolayout neste momento. Isso não é mais necessário.
n13

9

Encontrei outra solução para o mesmo problema. Usei em UITextViewvez de UILabele mudei a editable()função para false.


@geekyaleks Por que isso é um hack idiota? Parece uma solução decente, há algum outro problema além de não ser uma resposta direta à pergunta?
Christopher Larsen

Não é apropriado, porque você não está usando o componente de IU apropriado para o trabalho. NÃO deve ser um compromisso para algo tão simples como o alinhamento vertical. Precisa usar o componente certo para o trabalho. Qualquer outra coisa é um hack ...
geekyaleks

7

Eu também estava tendo esse problema, mas o que descobri foi que a ordem em que você define as propriedades e métodos do UILabel é importante!

Se você chamar [label sizeToFit]antes label.font = [UIFont fontWithName:@"Helvetica" size:14];, o texto não se alinha ao topo, mas se você trocá-los, então ele se alinha!

Também percebi que definir o texto primeiro também faz diferença.

Espero que isto ajude.


Ótimo. sizeToFit () deve ser chamado no final.
MKatleast3

4

Como você está usando o construtor de interface, defina as restrições para sua etiqueta (certifique-se de definir a altura e a largura também). Em seguida, no Inspetor de tamanho, verifique a altura da etiqueta. Lá você vai querer ler> = em vez de =. Então, na implementação desse controlador de visualização, defina o número de linhas como 0 (também pode ser feito em IB) e defina o rótulo [label sizeToFit]; e à medida que o texto ganha comprimento, o rótulo aumenta de altura e mantém o texto no canto superior esquerdo.


4

Se você precisa de um texto não editável que, por padrão, começa no canto superior esquerdo, você pode simplesmente usar uma Visualização de Texto em vez de um rótulo e definir seu estado como não editável, assim:

textview.isEditable = false

Muito mais fácil do que mexer com os rótulos ...

Felicidades!


3

Solução com SoLabel funciona, obrigado.

Abaixo, adicionei a versão monotouch:

    public class UICustomLabel : UILabel
{
    private UITextVerticalAlignment _textVerticalAlignment;

    public UICustomLabel()
    {
        TextVerticalAlignment = UITextVerticalAlignment.Top;
    }

    public UITextVerticalAlignment TextVerticalAlignment
    {
        get
        {
            return _textVerticalAlignment;
        }
        set
        {
            _textVerticalAlignment = value;
            SetNeedsDisplay();
        }
    }

    public override void DrawText(RectangleF rect)
    {
        var bound = TextRectForBounds(rect, Lines);
        base.DrawText(bound);
    }

    public override RectangleF TextRectForBounds(RectangleF bounds, int numberOfLines)
    {
        var rect = base.TextRectForBounds(bounds, numberOfLines);
        RectangleF resultRect;
        switch (TextVerticalAlignment)
        {
            case UITextVerticalAlignment.Top:
                resultRect = new RectangleF(bounds.X, bounds.Y, rect.Size.Width, rect.Size.Height);
                break;
            case UITextVerticalAlignment.Middle:
                resultRect = new RectangleF(bounds.X,
                                            bounds.Y + (bounds.Size.Height - rect.Size.Height)/2,
                                            rect.Size.Width, rect.Size.Height);
                break;
            case UITextVerticalAlignment.Bottom:
                resultRect = new RectangleF(bounds.X,
                                            bounds.Y + (bounds.Size.Height - rect.Size.Height),
                                            rect.Size.Width, rect.Size.Height);
                break;

            default:
                resultRect = bounds;
                break;
        }

        return resultRect;
    }
}

public enum UITextVerticalAlignment
{
    Top = 0, // default
    Middle,
    Bottom
}

3

A maneira mais simples e fácil é incorporar Label no StackView e definir o eixo do StackView como Horizontal, Alignment to Top no Attribute Inspector do Storyboard, como mostrado aqui .


2

Com base na resposta incrível do totiG, criei uma classe IBDesignable que torna extremamente fácil personalizar o alinhamento vertical de um UILabel direto do StoryBoard. Apenas certifique-se de definir sua classe UILabel como 'VerticalAlignLabel' no inspetor de identidade do StoryBoard. Se o alinhamento vertical não tiver efeito, vá para Editor-> Atualizar todas as visualizações que deve resolver o problema.

Como funciona: depois de definir a classe do UILabel corretamente, o storyboard deve mostrar um campo de entrada que leva um inteiro (código de alinhamento).

Atualização: adicionei suporte para rótulos centralizados ~ Sev


Insira 0 para Alinhamento Superior

Insira 1 para Alinhamento do Meio

Digite 2 para alinhamento inferior

    @IBDesignable class VerticalAlignLabel: UILabel {
    
    @IBInspectable var alignmentCode: Int = 0 {
        didSet {
            applyAlignmentCode()
        }
    }
    
    func applyAlignmentCode() {
        switch alignmentCode {
        case 0:
            verticalAlignment = .top
        case 1:
            verticalAlignment = .topcenter
        case 2:
            verticalAlignment = .middle
        case 3:
            verticalAlignment = .bottom
        default:
            break
        }
    }
    
    override func awakeFromNib() {
        super.awakeFromNib()
        self.applyAlignmentCode()
    }
    
    override func prepareForInterfaceBuilder() {
        super.prepareForInterfaceBuilder()
        
        self.applyAlignmentCode()
    }
    
    enum VerticalAlignment {
        case top
        case topcenter
        case middle
        case bottom
    }
    
    var verticalAlignment : VerticalAlignment = .top {
        didSet {
            setNeedsDisplay()
        }
    }
    
    override public func textRect(forBounds bounds: CGRect, limitedToNumberOfLines: Int) -> CGRect {
        let rect = super.textRect(forBounds: bounds, limitedToNumberOfLines: limitedToNumberOfLines)
        
        if #available(iOS 9.0, *) {
            if UIView.userInterfaceLayoutDirection(for: .unspecified) == .rightToLeft {
                switch verticalAlignment {
                case .top:
                    return CGRect(x: self.bounds.size.width - rect.size.width, y: bounds.origin.y, width: rect.size.width, height: rect.size.height)
                case .topcenter:
                    return CGRect(x: self.bounds.size.width - (rect.size.width / 2), y: bounds.origin.y, width: rect.size.width, height: rect.size.height)
                case .middle:
                    return CGRect(x: self.bounds.size.width - rect.size.width, y: bounds.origin.y + (bounds.size.height - rect.size.height) / 2, width: rect.size.width, height: rect.size.height)
                case .bottom:
                    return CGRect(x: self.bounds.size.width - rect.size.width, y: bounds.origin.y + (bounds.size.height - rect.size.height), width: rect.size.width, height: rect.size.height)
                }
            } else {
                switch verticalAlignment {
                case .top:
                    return CGRect(x: bounds.origin.x, y: bounds.origin.y, width: rect.size.width, height: rect.size.height)
                case .topcenter:
                    return CGRect(x: (self.bounds.size.width / 2 ) - (rect.size.width / 2), y: bounds.origin.y, width: rect.size.width, height: rect.size.height)
                case .middle:
                    return CGRect(x: bounds.origin.x, y: bounds.origin.y + (bounds.size.height - rect.size.height) / 2, width: rect.size.width, height: rect.size.height)
                case .bottom:
                    return CGRect(x: bounds.origin.x, y: bounds.origin.y + (bounds.size.height - rect.size.height), width: rect.size.width, height: rect.size.height)
                }
            }
        } else {
            // Fallback on earlier versions
            return rect
        }
    }
    
    override public func drawText(in rect: CGRect) {
        let r = self.textRect(forBounds: rect, limitedToNumberOfLines: self.numberOfLines)
        super.drawText(in: r)
    }
}


2

você também pode apenas alterar seu UILabel para UITextView, porque eles basicamente fazem a mesma coisa, exceto que a vantagem de UITextView é que o texto é alinhado automaticamente no canto superior esquerdo


1

Eu tenho esse problema, mas meu rótulo estava em UITableViewCell, e no fundo que a maneira mais fácil de resolver o problema era criar um UIView vazio e definir o rótulo dentro dele com restrições para cima e apenas para o lado esquerdo, em off maldito defina o número de linhas para 0


0

Para iOS 7, foi isso que fiz e trabalhei para mim

@implementation UILabel (VerticalAlign)
- (void)alignTop
{
    CGSize boundingRectSize = CGSizeMake(self.frame.size.width, CGFLOAT_MAX);
    NSDictionary *attributes = @{NSFontAttributeName : self.font};
    CGRect labelSize = [self.text boundingRectWithSize:boundingRectSize options:NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading
                                              attributes:attributes
                                                 context:nil];
    int numberOfLines= ceil(labelSize.size.height / self.font.lineHeight);

    CGRect newFrame = self.frame;
    newFrame.size.height = numberOfLines * self.font.lineHeight;
    self.frame = newFrame;
}

- (void)alignBottom
{
    CGSize boundingRectSize = CGSizeMake(self.frame.size.width, CGFLOAT_MAX);
    NSDictionary *attributes = @{NSFontAttributeName : self.font};
    CGRect labelSize = [self.text boundingRectWithSize:boundingRectSize options:NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading
                                            attributes:attributes
                                               context:nil];
    int numberOfLines= ceil(labelSize.size.height / self.font.lineHeight);

    int numberOfNewLined = (self.frame.size.height/self.font.lineHeight) - numberOfLines;

    NSMutableString *newLines = [NSMutableString string];
    for(int i=0; i< numberOfNewLined; i++){
        [newLines appendString:@"\n"];
    }
    [newLines appendString:self.text];
    self.text = [newLines mutableCopy];
}

0

Swift 2.0:: Usando a extensão UILabel

Crie valores enum constantes em um arquivo Swift vazio.

//  AppRef.swift

import UIKit
import Foundation

enum UILabelTextPositions : String {

 case VERTICAL_ALIGNMENT_TOP = "VerticalAlignmentTop"
 case VERTICAL_ALIGNMENT_MIDDLE = "VerticalAlignmentMiddle"
 case VERTICAL_ALIGNMENT_BOTTOM = "VerticalAlignmentBottom"

}

Usando a extensão UILabel:

Faça uma classe Swift vazia e nomeie-a. Adicione o seguinte.

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

     }
    }

Uso:

myMessageLabel.makeLabelTextPosition(messageLabel, positionIdentifier: UILabelTextPositions.VERTICAL_ALIGNMENT_TOP.rawValue)

Você poderia explicar para que é necessário sampleLabel: UILabel??
Craig.Pearce

Nesta função makeLabelTextPosition (sampleLabel: UILabel ?, positionIdentifier: String) {}, deve-se passar o objeto UILabel.
AG

0

Versão Swift 3 da resposta de @totiG

class UIVerticalAlignLabel: UILabel {
    enum VerticalAlignment : Int {
        case VerticalAlignmentTop = 0
        case VerticalAlignmentMiddle = 1
        case VerticalAlignmentBottom = 2
    }

    @IBInspectable var verticalAlignment : VerticalAlignment = .VerticalAlignmentTop {
        didSet {
            setNeedsDisplay()
        }
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
    }

    override func textRect(forBounds bounds: CGRect, limitedToNumberOfLines: Int) -> CGRect {
        let rect = super.textRect(forBounds: bounds, limitedToNumberOfLines: limitedToNumberOfLines)

        switch(verticalAlignment) {
        case .VerticalAlignmentTop:
            return CGRect(x: bounds.origin.x, y: bounds.origin.y, width: rect.size.width, height: rect.size.height)
        case .VerticalAlignmentMiddle:
            return CGRect(x: bounds.origin.x, y: bounds.origin.y + (bounds.size.height - rect.size.height) / 2, width: rect.size.width, height: rect.size.height)
        case .VerticalAlignmentBottom:
            return CGRect(x: bounds.origin.x, y: bounds.origin.y + (bounds.size.height - rect.size.height), width: rect.size.width, height: rect.size.height)
        }
    }

    override func drawText(in rect: CGRect) {
        let r = self.textRect(forBounds: rect, limitedToNumberOfLines: self.numberOfLines)
        super.drawText(in: r)
    }
}

0

A resposta do @totiG está correta e resolveu meu problema. Mas eu encontrei um problema ao implementar este método, em dispositivos menores como 5s, SE, isso não funciona para mim. Eu tenho que definir label.sizeToFit()emoverride func layoutSubViews()

override func layoutSubViews() {
    super.layoutSubViews()
    // Do other works if needed
    label.sizeToFit()
}

0

Swift 5

É simples, a ordem dos imóveis é tudo.

titleLabel.frame = CGRect(x: 20, y: 20, width: 374, height: 291.2)
titleLabel.backgroundColor = UIColor.clear //set a light color to see the frame
titleLabel.textAlignment = .left
titleLabel.lineBreakMode = .byTruncatingTail
titleLabel.numberOfLines = 4
titleLabel.font = UIFont(name: "HelveticaNeue-Bold", size: 35)
titleLabel.text = "Example"
titleLabel.sizeToFit()
self.view.addSubview(titleLabel)

-2

Como definir o alinhamento superior esquerdo para o aplicativo UILabel para iOS? Etiqueta Definir modo de conteúdo para "Superior esquerdo" é um trabalho para mim, muito obrigado:
Como definir o alinhamento superior esquerdo para o aplicativo UILabel para iOS?  Etiqueta Definir o modo de conteúdo para "Superior esquerdo" funciona para mim, muito obrigado


1
Não faz absolutamente nada por mim. Esta parece intuitivamente que deveria ser a solução, e é por isso que eu recorri ao Google quando ele não funcionou (ou aparentemente fez jack all, para esse assunto).
velkoon
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.