【发布时间】:2010-11-06 11:24:14
【问题描述】:
我有一个UILabel,其中有两行文本的空格。有时,当文本太短时,该文本会显示在标签的垂直中心。
如何垂直对齐文本以始终位于 UILabel 的顶部?
【问题讨论】:
标签: ios cocoa-touch uikit uilabel text-alignment
我有一个UILabel,其中有两行文本的空格。有时,当文本太短时,该文本会显示在标签的垂直中心。
如何垂直对齐文本以始终位于 UILabel 的顶部?
【问题讨论】:
标签: ios cocoa-touch uikit uilabel text-alignment
您可以通过代码或简单地在情节提要上执行以下操作来解决该问题:
UILabel 的行数设置为等于0。UILabel 嵌入到UIView 中。UIView 约束(顶部、右侧、左侧和底部)以占据UILabel 应增长到的最大空间。UILabel 设置为UIView 的顶部、右侧和左侧,并将约束设置为底部距离>= 0。【讨论】:
这里是从左上角开始渲染文本,并保持可能调整的文本大小。
final class TopAlignedLabel: UILabel {
override func drawText(in rect: CGRect) {
super.drawText(in: .init(
origin: .zero,
size: textRect(
forBounds: rect,
limitedToNumberOfLines: numberOfLines
).size
))
}
}
【讨论】:
使用textRect(forBounds:limitedToNumberOfLines:)。
class TopAlignedLabel: UILabel {
override func drawText(in rect: CGRect) {
let textRect = super.textRect(forBounds: bounds, limitedToNumberOfLines: numberOfLines)
super.drawText(in: textRect)
}
}
【讨论】:
【讨论】:
UILabel 放入一些UIView 子类(普通UIView 通常就足够了)并让标签向下增长的好主意。谢谢!
UIStackView,只需将UILabel 嵌入UIView 中即可。设置UIView 约束以占用UILabel 应该增长到的最大空间。然后将UILabel 设置为UIView 的顶部、右侧和左侧,并将约束也设置为底部距离>= 0。
SwiftUI
在 SwiftUI 中,所有视图通常都是 sizeToFit 其内容,但如果显式使用frame 修饰符,则可以使用框架的alignment 参数:
Text("Text")
.background(Color.red)
.frame(width: 100, height: 200, alignment: .top)
另外,如果你使用任何一种Stack来排列你的视图,它们都有一个类似于框架的参数,称为alignment:
ZStack(alignment: .top) {
Color.red
Text("Text")
.background(Color.red)
}
【讨论】:
这是一个旧的解决方案,在 iOS >= 6 上使用自动布局
我的解决方案:
@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
【讨论】:
设置新文本:
myLabel.text = @"Some Text"
将行的maximum number 设置为0(自动):
myLabel.numberOfLines = 0
将标签的边框设置为最大尺寸:
myLabel.frame = CGRectMake(20,20,200,800)
调用sizeToFit 以减小帧大小,使内容刚好适合:
[myLabel sizeToFit]
标签框现在的高度和宽度足以容纳您的文本。左上角应该保持不变。我只用左上对齐的文本对此进行了测试。对于其他对齐方式,您可能需要在之后修改框架。
另外,我的标签启用了自动换行。
【讨论】:
不要乱来,不要大惊小怪
@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
没有混乱,没有 Objective-c,没有大惊小怪,只有 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)
}
}
斯威夫特 4.2
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: [NSAttributedString.Key.font: 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)
}
}
【讨论】:
无法在UILabel 上设置垂直对齐,但您可以通过更改标签的框架来获得相同的效果。我已将标签设为橙色,以便您清楚地看到发生了什么。
以下是快速简便的方法:
[myLabel sizeToFit];
如果您有一个包含超过一行的较长文本的标签,请将numberOfLines 设置为0(此处的零表示行数不受限制)。
myLabel.numberOfLines = 0;
[myLabel sizeToFit];
加长版
我将在代码中制作我的标签,以便您了解发生了什么。您也可以在 Interface Builder 中设置大部分内容。我的设置是一个基于视图的应用程序,带有我在 Photoshop 中制作的背景图像以显示边距(20 点)。标签是有吸引力的橙色,因此您可以看到尺寸的变化。
- (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];
}
使用sizeToFit 的一些限制会影响居中或右对齐的文本。以下是发生的事情:
// myLabel.textAlignment = NSTextAlignmentRight;
myLabel.textAlignment = NSTextAlignmentCenter;
[myLabel setNumberOfLines:0];
[myLabel sizeToFit];
标签的大小仍然是固定的左上角。您可以将原始标签的宽度保存在变量中并在sizeToFit 之后设置它,或者给它一个固定宽度来解决这些问题:
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;
请注意,sizeToFit 将遵循您的初始标签的最小宽度。如果您从 100 宽的标签开始并在其上调用 sizeToFit,它将返回一个 100(或稍小)宽的(可能非常高)标签。您可能希望在调整大小之前将标签设置为所需的最小宽度。
其他一些注意事项:
lineBreakMode 是否受到尊重取决于它的设置方式。 NSLineBreakByTruncatingTail(默认值)在sizeToFit 之后被忽略,其他两种截断模式(头部和中间)也是如此。 NSLineBreakByClipping 也被忽略。 NSLineBreakByCharWrapping 照常工作。框架宽度仍然变窄以适合最右边的字母。
Mark Amery 修复了在 cmets 中使用自动布局的 NIB 和 Storyboard:
如果您的标签作为使用自动布局的 ViewController 的
view的子视图包含在 nib 或情节提要中,那么将您的sizeToFit调用放入viewDidLoad将不起作用,因为自动布局的大小和位置在调用viewDidLoad之后的子视图将立即撤消sizeToFit调用的效果。但是,从viewDidLayoutSubviews内部调用sizeToFit将工作。
这使用NSString方法sizeWithFont:constrainedToSize:lineBreakMode:计算适合字符串所需的帧高度,然后设置原点和宽度。
使用您要插入的文本调整标签框架的大小。这样您就可以容纳任意数量的行。
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;
【讨论】:
可以更灵活地为 Interface Builder 中的标签设置 height constraint,使用 IBOutlet 将其绑定到代码并更改该高度以在具体的垂直位置显示文本。中心和底部对齐示例:
labelHeightConstraint.constant = centerAlignment ? 30 : 15
layoutIfNeeded()
【讨论】:
(截至 2018 年 3 月 7 日)
斯威夫特 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)
这将获得所需的结果,并确保 UILabel 的新高度不会超过您希望此标签的某个最大高度。
【讨论】:
numberOfLines = 0!
这是 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
}
【讨论】:
这个问题的简单解决方案……
在标签下方放置UIStackView。
然后使用 AutoLayout 将标签的垂直间距设置为零,并将顶部约束到之前的标签底部。标签不会增长,stackView 会。我喜欢的是堆栈视图是非渲染的。所以这并不是真正浪费时间渲染。即使它进行 AutoLayout 计算。
虽然您可能需要使用 ContentHugging 和 Resistance,但这也很简单。
我时不时地在谷歌上搜索这个问题并回到这里。这次我有一个我认为最简单的想法,而且我不认为这是糟糕的表现。 我必须说我只是在使用 AutoLayout,并不想费心计算帧。这只是......糟糕。
【讨论】:
对于 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
}
}
【讨论】:
对于那些尝试解决此问题的自定义表格单元格的人,请将其添加到您的自定义表格单元格类中:
Swift 2.2:
override func layoutSubviews() {
labelName.sizeToFit()
}
这解决了我的问题。
【讨论】:
Swift 2.0:
在一个空的 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)
【讨论】:
很快,
let myLabel : UILabel!
使标签的文本适合屏幕并且位于顶部
myLabel.sizeToFit()
使您的标签字体适合屏幕宽度或特定宽度大小。
myLabel.adjustsFontSizeToFitWidth = YES
还有一些用于标签的 textAlignment :
myLabel.textAlignment = .center
myLabel.textAlignment = .left
myLabel.textAlignment = .right
myLabel.textAlignment = .Natural
myLabel.textAlignment = .Justified
【讨论】:
[myLabel sizeToFit] 旧语法的语法更改。谢谢!
上面的方法我用过很多,只是想补充一下我用过的又快又脏的方法:
myLabel.text = [NSString stringWithFormat:@"%@\n\n\n\n\n\n\n\n\n",@"My label text string"];
确保字符串中换行符的数量会导致任何文本填满可用的垂直空间,并设置 UILabel 以截断任何溢出的文本。
因为有时候足够好就是足够好。
【讨论】:
UITextView 动态加载视图时学到的一点是,它可以调用dlopen 来支持文本输入。这可能会导致 UI 线程严重滞后,因此这种方法的性能要高得多!
【讨论】:
UITextView 就可以立即获得所需的结果。
只要你不做任何复杂的任务,你可以用UITextView代替UILabels。
禁用滚动。
如果您希望文本完全显示,只需用户 sizeToFit 和 sizeThatFits: 方法
【讨论】:
FXLabel (on github) 通过将label.contentMode 设置为UIViewContentModeTop 开箱即用地执行此操作。这个组件不是我做的,但它是我经常使用的一个组件,有很多功能,而且看起来很好用。
【讨论】:
你可以使用TTTAttributedLabel,它支持垂直对齐。
@property (nonatomic) TTTAttributedLabel* label;
<...>
//view's or viewController's init method
_label.verticalAlignment = TTTAttributedLabelVerticalAlignmentTop;
【讨论】:
此代码可帮助您使文本与顶部对齐,并且使标签高度与文本内容固定。
以下代码的使用说明
isHeightToChange 默认为 true 使标签的高度自动与文本内容相同。
isHeightToChange 如果它为 false,则使文本与顶部对齐,而不降低标签的高度。
#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
【讨论】:
我发现这个问题的答案现在有点过时了,所以为那里的自动布局爱好者添加这个。
自动布局使这个问题变得非常简单。假设我们将标签添加到UIView *view,以下代码将完成此操作:
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}]];
标签的高度将被自动计算(使用它的intrinsicContentSize)并且标签将被水平边对边放置在view的顶部。
【讨论】:
我知道这是一篇旧帖子,但垂直对齐文本是一个大问题(至少对我来说是这样),我想我应该分享这个解决方案,因为我自己找不到。
在我看来,使用 drawRect 有点贵。让 UILabel 垂直对齐的正确方法是不使用 UILabel。使用 UITextView(多行 UITextField)并像这样观察 content 属性:
- (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;
}
}
基本上这里发生的事情是我们将我们所在的类设置为观察者,使用 NSKeyValueObservingOptionNew 选项查看 contentSize 属性,因此每次内容更改时,-(void)observeValueForKeyPath:ofObject:change:context: 将被调用,然后您可以计算偏移量大小以适当地对齐文本。
我不能相信这一点,最初的想法来自here。但是,此解决方案不适用于 iOS7。在 SO 上闲逛了几个小时后,我发现了这个:iOS 7 vertical alignment fix。那里的关键行是contentheight = [tv sizeThatFits:CGSizeMake(tv.frame.size.width, FLT_MAX)].height;。出于某种原因,在 iOS 7 中,获取 contentSize 高度不起作用,但可以修复它。这两种解决方案都不能单独工作,但经过一些修补后,我能够将上述解决方案综合在一起。
【讨论】:
如果您使用自动布局,请在代码或 IB 中将垂直 contentHuggingPriority 设置为 1000。在 IB 中,您可能必须通过将其优先级设置为 1 然后删除它来移除高度限制。
【讨论】:
我已经为此苦苦挣扎了很长时间,我想分享我的解决方案。
这将为您提供UILabel,它将自动将文本缩小到 0.5 比例并将文本垂直居中。这些选项在 Storyboard/IB 中也可用。
[labelObject setMinimumScaleFactor:0.5];
[labelObject setBaselineAdjustment:UIBaselineAdjustmentAlignCenters];
【讨论】:
yourLabel.baselineAdjustment = UIBaselineAdjustmentAlignCenters;
【讨论】: