Как установить выравнивание по левому верхнему краю для приложения UILabel для iOS?


99

Я добавил одну метку в свой файл с перьями, тогда для нее требуется выравнивание по левому верхнему краю. Поскольку я предоставляю текст во время выполнения, я не уверен, сколько там строк. Таким образом, если текст содержит только одну строку, он отображается как выровненный по вертикали. Это выравнивание не совпадает с моей этикеткой перед ним.

Например:

введите описание изображения здесь

Что выглядит странно :(

Есть ли способ установить текст метки для выравнивания по верхнему левому краю?


Ответы:



65

Сделать это довольно просто. Создайте UILabelподкласс со verticalAlignmentсвойством и переопределите, textRectForBounds:limitedToNumberOfLinesчтобы вернуть правильные границы для верхнего, среднего или нижнего вертикального выравнивания. Вот код:

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
Я также пробовал много других решений здесь, на SO, прежде чем столкнуться с этим. Это сработало отлично! Имейте в виду, что если вы делаете это в StoryBoard, убедитесь, что вы установили для атрибута CustomClass значение SOLabel (или как вы решите назвать его) вместо UILabel (в Инспекторе утилит).
TMc

Это очень полезно, спасибо. Это не работает для текста с выравниванием по центру или по правому краю, но использование bounds.size.widthвместо rect.size.widthin, textRectForBounds:limitedToNumberOfLines:похоже, исправляет это.
Джефф Хакворт,

1
Если вы столкнулись с «Поток 1: EXC_BAD_ACCESS (Код 2, адрес = 0x ...)» в iOS 9 Xcode 7, просто удалите установщик и получатель - (VerticalAlignment) verticalAlignment; и - (void) setVerticalAlignment: (VerticalAlignment) функции значения, поскольку переменная - это @property. Он синтезирован и содержит аксессуары.
felixwcf

У меня есть некоторые изменения здесь, в методе: "textRectForBounds" - result = CGRectMake (rect.origin.x, bounds.origin.y, rect.size.width, rect.size.height); Чтобы мои работы для rightAlignment UILable.
g212gs

50

Я нашел решение, используя AutoLayout в StoryBoard.

1) Установите количество строк на 0 и выравнивание текста по левому краю.

введите описание изображения здесь

2) Установите ограничение по высоте.

введите описание изображения здесь

3) Ограничение высоты должно быть в соотношении - меньше или равно

введите описание изображения здесь

4)

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

Я получил следующий результат:

введите описание изображения здесь


2
Прекрасно работает даже в UITableViewCell с повторным использованием.
alex.bour

Вы помещаете viewWillLayoutSubviewsв контроллер или файл ячейки? Если контроллер, как он получает доступ к UILabel из ячейки?
Craig.Pearce

Где поставить шаг 4? Как новый пользователь, я был рад получить чисто пользовательское решение, тогда этот код появляется из ниоткуда, и нам не говорят, куда его поместить
velkoon

Либо на SampleClass.swift, либо на SampleTableViewCell.swift
AG

Это должно быть Решение. Работает отлично, не требует взлома или создания подклассов.
Curious101

45

У меня работает SOLabel.

Swift 3 и 5:

Эта версия была обновлена ​​по сравнению с исходной, чтобы обеспечить поддержку языков 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)
    }
}

Если я попытаюсь создать метку, используя этот код: var myLabel = VerticalAlignLabel (), я получаю сообщение «Отсутствует аргумент для параметра 'coder' при вызове». Как я могу создать метку с помощью этого подкласса VerticalAlignLabel?
RanLearns

1
Попробуйте Swift версии 3 сейчас - у меня был необходимый init, который не нужен.
totiG

14

В моем случае это была bottom spaceпроблема ограничения. Я установил его = 16.

Когда я установил его bottom to >= 16, проблема была решена.

Кроме того, если у вас есть ограничение по высоте на этикетке, вам необходимо его удалить.

Вот вид ограничений моей метки в инспекторе размеров:

ограничение


Когда я выбираю ярлык, у меня нет параметров ограничения.
velkoon 06

Самое простое исправление - пусть об этом позаботятся ограничения и автоматический макет. Спасибо!
Джейсон

13

В вашем коде

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

Помните, что если вы используете это в ячейках таблицы или других представлениях, которые обрабатываются с другими данными, вам нужно будет где-то сохранить исходный фрейм и сбросить его перед вызовом sizeToFit.


Я бы рекомендовал оставить все это на данный момент Autolayout. Это больше не нужно.
n13

9

Я нашел другое решение той же проблемы. Я использовал UITextViewвместо UILabelи переключил editable()функцию на false.


@geekyaleks Почему это тупой хак? Похоже на достойный обходной путь, есть ли другие проблемы с ним, кроме того, что он не является прямым ответом на вопрос?
Кристофер Ларсен,

Это не подходит, потому что вы не используете соответствующий компонент пользовательского интерфейса для работы. Это НЕ должно быть компромиссом для такой простой вещи, как вертикальное выравнивание. Необходимо использовать правильный компонент для работы. Все остальное - это взлом ...
geekyaleks

7

У меня тоже была эта проблема, но я обнаружил, что порядок, в котором вы устанавливаете свойства и методы UILabel, имеет значение!

Если вы позвоните [label sizeToFit]раньше, label.font = [UIFont fontWithName:@"Helvetica" size:14];текст не выровняется по верхнему краю, но если вы поменяете их местами, то это произойдет!

Я также заметил, что установка текста сначала тоже имеет значение.

Надеюсь это поможет.


Отлично. sizeToFit () следует вызывать в самом конце.
MKatleast3 02

4

Поскольку вы используете построитель интерфейса, установите ограничения для вашей метки (не забудьте также установить высоту и ширину). Затем в инспекторе размеров проверьте высоту метки. Там вы захотите, чтобы было написано> = вместо =. Затем в реализации для этого контроллера представления установите количество строк равным 0 (это также можно сделать в IB) и установите метку [label sizeToFit]; и по мере увеличения длины текста метка будет увеличиваться в высоту и сохранять текст в верхнем левом углу.


4

Если вам нужен нередактируемый текст, который по умолчанию начинается в верхнем левом углу, вы можете просто использовать текстовое представление вместо метки, а затем установить его состояние как нередактируемое, например:

textview.isEditable = false

Это проще, чем возиться с этикетками ...

Ура!


3

Решение с SoLabel работает, спасибо.

Ниже я добавил монотачную версию:

    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

Самый простой и легкий способ - встроить метку в StackView и установить для StackView Axis значение Horizontal, Alignment to Top в Attribute Inspector из Storyboard, как показано здесь .


2

Основываясь на замечательном ответе totiG, я создал класс IBDesignable, который упрощает настройку вертикального выравнивания UILabel прямо из StoryBoard. Просто убедитесь, что вы установили для своего класса UILabel значение «VerticalAlignLabel» из инспектора идентичности StoryBoard. Если вертикальное выравнивание не сработает, перейдите в Editor-> Refresh All Views, что должно помочь.

Как это работает: после того, как вы правильно настроили свой класс UILabel, раскадровка должна показать вам поле ввода, которое принимает целое число (код выравнивания).

Обновление: я добавил поддержку центрированных меток ~ Sev


Введите 0 для выравнивания по верхнему краю

Введите 1 для среднего выравнивания

Введите 2 для выравнивания по нижнему краю

    @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

вы также можете просто изменить свой UILabel на UITextView, потому что они в основном делают то же самое, за исключением того, что преимущество UITextView заключается в том, что текст автоматически выравнивается по левому верхнему краю


1

У меня есть эта проблема, но моя метка была в UITableViewCell, а в фонде самый простой способ решить проблему - создать пустой UIView и установить метку внутри него с ограничениями только вверху и слева, на проклятии установите количество строк на 0


0

Для iOS 7 это то, что я сделал и работал на меня

@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 :: Использование расширения UILabel

Сделайте постоянные значения перечисления в пустом файле Swift.

//  AppRef.swift

import UIKit
import Foundation

enum UILabelTextPositions : String {

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

}

Использование расширения UILabel:

Создайте пустой класс Swift и назовите его. Добавьте следующее.

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

     }
    }

Использование :

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

Не могли бы вы объяснить, что нужно sampleLabel: UILabel??
Craig.Pearce

В этой функции makeLabelTextPosition (sampleLabel: UILabel ?, positionIdentifier: String) {} вы должны передать объект UILabel.
AG

0

Swift 3 версия ответа @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

Ответ @totiG правильный и решил мою проблему. Но я обнаружил проблему при реализации этого метода, в небольших устройствах, таких как 5s, SE, у меня это не работает. Я должен установить label.sizeToFit()вoverride func layoutSubViews()

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

0

Swift 5

Все просто, порядок свойств решает все.

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

Как установить выравнивание по левому верхнему краю для приложения UILabel для iOS? Ярлык Установите для режима содержимого значение "Вверху слева" работает для меня, большое спасибо:
Как установить выравнивание по левому верхнему краю для приложения UILabel для iOS?  Ярлык Установите для режима содержимого значение "Вверху слева" работает для меня, большое спасибо


1
Для меня абсолютно ничего не делает. Интуитивно кажется, что это должно быть решение, поэтому я обратился к Google, когда это не сработало (или, похоже, взломало все, если на то пошло).
velkoon 06
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.