iOS AutoLayout 多行 UILabel
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/17491376/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
iOS AutoLayout multi-line UILabel
提问by ancajic
Following question is sort-of continuation of this one:
以下问题是这个问题的延续:
iOS: Multi-line UILabel in Auto Layout
The main idea is that every view is supposed to state it's "preferred" (intrinsic) size so that AutoLayout can know how to display it properly. UILabel is just an example of a situation where a view cannotby itself know what size it needs for display. It depends on what width is provided.
主要思想是每个视图都应该说明它的“首选”(内在)大小,以便 AutoLayout 知道如何正确显示它。UILabel 只是视图本身无法知道它需要显示多大尺寸的情况的一个示例。这取决于提供的宽度。
As mwhusspointed out, setPreferredMaxLayoutWidthdid the trick of making the label span across multiple lines. But that is not the main question here. The question is where and when do I get this widthvalue that I send as an argument to setPreferredMaxLayoutWidth.
正如mwhuss指出的那样,setPreferredMaxLayoutWidth做了使标签跨越多行的技巧。但这不是这里的主要问题。问题是我何时何地获得这个宽度值作为参数发送给setPreferredMaxLayoutWidth。
I managed to make something that looks legit, so correct me if I am wrong in any way and tell me please if you know a better way.
我设法制作了一些看起来合法的东西,所以如果我有任何错误,请纠正我,如果你知道更好的方法,请告诉我。
In the UIView's
在 UIView 的
-(CGSize) intrinsicContentSize
I setPreferredMaxLayoutWidthfor my UILabels according to self.frame.width.
我setPreferredMaxLayoutWidth根据self.frame.width我UILabels。
UIViewController's
UIViewController 的
-(void) viewDidLayoutSubviews
is the first callback method I know where subviews of the main view are appointed with their exact frames that they inhabit on the screen. From inside that method I, then, operate on my subviews, invalidating their intrinsic sizes so that UILabels are broken into multiple lines based on the width that was appointed to them.
是我知道的第一个回调方法,主视图的子视图在哪里指定了它们在屏幕上的确切框架。然后,从该方法内部,我对我的子视图进行操作,使它们的固有大小无效,以便 UILabels 根据指定给它们的宽度分成多行。
采纳答案by jrturton
It seems annoying that a UILabel doesn't default to its width for the preferred max layout width, if you've got constraints that are unambiguously defining that width for you.
如果您有明确地为您定义该宽度的约束,则 UILabel 没有默认为其首选最大布局宽度的宽度似乎很烦人。
In nearly every single case I've used labels under Autolayout, the preferred max layout width has been the actual width of the label, once the rest of my layout has been performed.
几乎在我在 Autolayout 下使用标签的每一种情况下,首选的最大布局宽度都是标签的实际宽度,一旦我的布局的其余部分已经执行。
So, to make this happen automatically, I have used a UILabel subclass, which overrides setBounds:
. Here, call the super implementation, then, if it isn't the case already, set the preferred max layout width to be the bounds size width.
因此,为了使这自动发生,我使用了一个 UILabel 子类,它覆盖了setBounds:
. 在这里,调用 super 实现,然后,如果不是这种情况,则将首选的最大布局宽度设置为边界大小宽度。
The emphasis is important - setting preferred max layout causes another layout pass to be performed, so you can end up with an infinite loop.
重点很重要 - 设置首选最大布局会导致执行另一个布局传递,因此您可能会以无限循环结束。
回答by nevan king
There's an answer this question on objc.ioin the "Intrinsic Content Size of Multi-Line Text" section of Advanced Auto Layout Toolbox. Here's the relevant info:
在objc.io的Advanced Auto Layout Toolbox的“Intrinsic Content Size of Multi-Line Text”部分有一个答案。以下是相关信息:
The intrinsic content size of UILabel and NSTextField is ambiguous for multi-line text. The height of the text depends on the width of the lines, which is yet to be determined when solving the constraints. In order to solve this problem, both classes have a new property called preferredMaxLayoutWidth, which specifies the maximum line width for calculating the intrinsic content size.
Since we usually don't know this value in advance, we need to take a two-step approach to get this right. First we let Auto Layout do its work, and then we use the resulting frame in the layout pass to update the preferred maximum width and trigger layout again.
UILabel 和 NSTextField 的内在内容大小对于多行文本是不明确的。文本的高度取决于线条的宽度,这在求解约束时尚未确定。为了解决这个问题,这两个类都有一个名为 preferredMaxLayoutWidth 的新属性,它指定了计算内在内容大小的最大线宽。
由于我们通常事先不知道这个值,因此我们需要采取两步方法来做到这一点。首先我们让 Auto Layout 做它的工作,然后我们在布局过程中使用结果框架来更新首选的最大宽度并再次触发布局。
The code they give for use inside a view controller:
他们提供在视图控制器中使用的代码:
- (void)viewDidLayoutSubviews
{
[super viewDidLayoutSubviews];
myLabel.preferredMaxLayoutWidth = myLabel.frame.size.width;
[self.view layoutIfNeeded];
}
Take a look at their post, there's more information about why it's necessary to do the layout twice.
看看他们的帖子,有更多关于为什么需要进行两次布局的信息。
回答by Nick Snyder
Update
更新
My original answer appears to be helpful so I have left it untouched below, however, in my own projects I have found a more reliable solution that works around bugs in iOS 7 and iOS 8. https://github.com/nicksnyder/ios-cell-layout
我最初的答案似乎很有帮助,所以我在下面没有改动它,但是,在我自己的项目中,我找到了一个更可靠的解决方案,可以解决 iOS 7 和 iOS 8 中的错误 。https://github.com/nicksnyder/ios -单元格布局
Original answer
原答案
This is a complete solution that works for me on iOS 7 and iOS 8
这是一个完整的解决方案,适用于 iOS 7 和 iOS 8
Objective C
目标 C
@implementation AutoLabel
- (void)setBounds:(CGRect)bounds {
if (bounds.size.width != self.bounds.size.width) {
[self setNeedsUpdateConstraints];
}
[super setBounds:bounds];
}
- (void)updateConstraints {
if (self.preferredMaxLayoutWidth != self.bounds.size.width) {
self.preferredMaxLayoutWidth = self.bounds.size.width;
}
[super updateConstraints];
}
@end
Swift
迅速
import Foundation
class EPKAutoLabel: UILabel {
override var bounds: CGRect {
didSet {
if (bounds.size.width != oldValue.size.width) {
self.setNeedsUpdateConstraints();
}
}
}
override func updateConstraints() {
if(self.preferredMaxLayoutWidth != self.bounds.size.width) {
self.preferredMaxLayoutWidth = self.bounds.size.width
}
super.updateConstraints()
}
}
回答by Ben Clayton
We had a situation where an auto-layouted UILabel inside a UIScrollView laid out fine in portrait, but when rotated to landscape the height of the UILabel wasn't recalculated.
我们遇到过这样一种情况,即 UIScrollView 内的自动布局的 UILabel 以纵向布局很好,但是当旋转到横向时,不会重新计算 UILabel 的高度。
We found that the answer from @jrturton fixed this, presumably because now the preferredMaxLayoutWidth is correctly set.
我们发现@jrturton 的答案解决了这个问题,大概是因为现在正确设置了 preferredMaxLayoutWidth。
Here's the code we used. Just set the Custom class from Interface builder to be CVFixedWidthMultiLineLabel.
这是我们使用的代码。只需将 Interface builder 中的 Custom 类设置为CVFixedWidthMultiLineLabel。
CVFixedWidthMultiLineLabel.h
CVFixedWidthMultiLineLabel.h
@interface CVFixedWidthMultiLineLabel : UILabel
@end
CVFixedWidthMultiLineLabel.m
CVFixedWidthMultiLineLabel.m
@implementation CVFixedWidthMultiLineLabel
// Fix for layout failure for multi-line text from
// http://stackoverflow.com/questions/17491376/ios-autolayout-multi-line-uilabel
- (void) setBounds:(CGRect)bounds {
[super setBounds:bounds];
if (bounds.size.width != self.preferredMaxLayoutWidth) {
self.preferredMaxLayoutWidth = self.bounds.size.width;
}
}
@end
回答by Cameron Lowell Palmer
Using boundingRectWithSize
使用 boundingRectWithSize
I resolved my struggle with two multi-line labels in a legacy UITableViewCell
that was using "\n" as a line-break by measuring the desired width like this:
我UITableViewCell
通过像这样测量所需的宽度来解决使用“\n”作为换行符的传统中的两个多行标签的问题:
- (CGFloat)preferredMaxLayoutWidthForLabel:(UILabel *)label
{
CGFloat preferredMaxLayoutWidth = 0.0f;
NSString *text = label.text;
UIFont *font = label.font;
if (font != nil) {
NSMutableParagraphStyle *mutableParagraphStyle = [[NSMutableParagraphStyle alloc] init];
mutableParagraphStyle.lineBreakMode = NSLineBreakByWordWrapping;
NSDictionary *attributes = @{NSFontAttributeName: font,
NSParagraphStyleAttributeName: [mutableParagraphStyle copy]};
CGRect boundingRect = [text boundingRectWithSize:CGSizeZero options:NSStringDrawingUsesLineFragmentOrigin attributes:attributes context:nil];
preferredMaxLayoutWidth = ceilf(boundingRect.size.width);
NSLog(@"Preferred max layout width for %@ is %0.0f", text, preferredMaxLayoutWidth);
}
return preferredMaxLayoutWidth;
}
Then calling the method was then as simple as:
然后调用该方法就很简单了:
CGFloat labelPreferredWidth = [self preferredMaxLayoutWidthForLabel:textLabel];
if (labelPreferredWidth > 0.0f) {
textLabel.preferredMaxLayoutWidth = labelPreferredWidth;
}
[textLabel layoutIfNeeded];
回答by dergab
As I'm not allowed to add a comment, I'm obliged to add it as an answer.
The version of jrturton only worked for me if I call layoutIfNeeded
in updateViewConstraints
before getting the preferredMaxLayoutWidth
of the label in question.
Without the call to layoutIfNeeded
the preferredMaxLayoutWidth
was always 0
in updateViewConstraints
. And yet, it had always the desired value when checked in setBounds:
. I didn't manage to get to know WHEN the correct preferredMaxLayoutWidth
was set. I override setPreferredMaxLayoutWidth:
on the UILabel
subclass, but it never got called.
由于我不允许添加评论,因此我不得不将其添加为答案。jrturton的版本只工作对我来说,如果我呼吁layoutIfNeeded
在updateViewConstraints
得到之前preferredMaxLayoutWidth
标签的问题。如果没有调用layoutIfNeeded
的preferredMaxLayoutWidth
是总是0
在updateViewConstraints
。然而,它在签入时始终具有所需的值setBounds:
。我没能知道什么preferredMaxLayoutWidth
时候设置正确。我覆盖setPreferredMaxLayoutWidth:
了UILabel
子类,但它从未被调用。
Summarized, I:
总结一下,我:
- ...sublcassed UILabel
- ...and override
setBounds:
to, if not already set, setpreferredMaxLayoutWidth
toCGRectGetWidth(bounds)
- ...call
[super updateViewConstraints]
before the following - ...call
layoutIfNeeded
before gettingpreferredMaxLayoutWidth
to be used in label's size calculation
- ...子类 UILabel
- ...并覆盖
setBounds:
为,如果尚未设置,则设置preferredMaxLayoutWidth
为CGRectGetWidth(bounds)
- ...
[super updateViewConstraints]
在以下之前打电话 - ...
layoutIfNeeded
在preferredMaxLayoutWidth
用于标签大小计算之前调用
EDIT:This workaround only seems to work, or be needed, sometimes. I just had an issue (iOS 7/8) where the label's height were not correctly calculated, as preferredMaxLayoutWidth
returned 0
after the layout process had been executed once. So after some trial and error (and having found this Blog entry) I switched to using UILabel
again and just set top, bottom, left and right auto layout constraints. And for whatever reason the label's height was set correctly after updating the text.
编辑:此解决方法似乎仅适用于或有时需要。我刚刚遇到了一个问题(iOS 7/8),标签的高度没有正确计算,因为在执行一次布局过程后preferredMaxLayoutWidth
返回0
。因此,经过一些试验和错误(并找到此博客条目)后,我UILabel
再次切换到使用,只需设置顶部、底部、左侧和右侧的自动布局约束。无论出于何种原因,更新文本后标签的高度设置正确。
回答by jbandi
As suggested by another answer I tried to override viewDidLayoutSubviews
:
正如另一个答案所建议的那样,我试图覆盖viewDidLayoutSubviews
:
- (void)viewDidLayoutSubviews
{
[super viewDidLayoutSubviews];
_subtitleLabel.preferredMaxLayoutWidth = self.view.bounds.size.width - 40;
[self.view layoutIfNeeded];
}
This worked, but it was visible on the UI and caused a "visible flicker" i.e. first the label was rendered with the height of two lines, then it was re-rendered with the height of only one line.
这有效,但它在 UI 上可见并导致“可见闪烁”,即首先使用两行高度渲染标签,然后仅使用一行高度重新渲染标签。
This was not acceptable for me.
这对我来说是不可接受的。
I found then a better solution by overriding updateViewConstraints
:
我通过覆盖找到了一个更好的解决方案updateViewConstraints
:
-(void)updateViewConstraints {
[super updateViewConstraints];
// Multiline-Labels and Autolayout do not work well together:
// In landscape mode the width is still "portrait" when the label determines the count of lines
// Therefore the preferredMaxLayoutWidth must be set
_subtitleLabel.preferredMaxLayoutWidth = self.view.bounds.size.width - 40;
}
This was the better solution for me, because it did not cause the "visual flickering".
这对我来说是更好的解决方案,因为它不会导致“视觉闪烁”。
回答by netshark1000
A clean solution is to set rowcount = 0
and to use a property for the heightconstraint of your label. Then after the content is set call
一个干净的解决方案是设置rowcount = 0
和使用标签的高度约束属性。然后在设置内容后调用
CGSize sizeThatFitsLabel = [_subtitleLabel sizeThatFits:CGSizeMake(_subtitleLabel.frame.size.width, MAXFLOAT)];
_subtitleLabelHeightConstraint.constant = ceilf(sizeThatFitsLabel.height);
-(void) updateViewConstraints
has a problem since iOS 7.1.
-(void) updateViewConstraints
自 iOS 7.1 以来有问题。
回答by phatmann
In iOS 8, you can fix multi-line label layout problems in a cell by calling cell.layoutIfNeeded()
after dequeuing and configuring the cell. The call is harmless in iOS 9.
在 iOS 8 中,您可以通过在单元格cell.layoutIfNeeded()
出列和配置后调用来修复单元格中的多行标签布局问题。该调用在 iOS 9 中是无害的。
See Nick Snyder's answer. This solution was taken from his code at https://github.com/nicksnyder/ios-cell-layout/blob/master/CellLayout/TableViewController.swift.
参见尼克斯奈德的回答。这个解决方案取自他在https://github.com/nicksnyder/ios-cell-layout/blob/master/CellLayout/TableViewController.swift 的代码。