次の質問は、この質問の続きです。
主なアイデアは、すべてのビューが「優先」(固有)サイズを示すようになっているため、AutoLayoutが適切に表示する方法を認識できるようにすることです。 UILabelは、ビューcannot自体が表示に必要なサイズを認識している状況の単なる例です。提供される幅によって異なります。
mwhussが指摘したように、setPreferredMaxLayoutWidthはラベルを複数行にまたがるトリックを行いました。しかし、それはここでの主要な質問ではありません。問題は、setPreferredMaxLayoutWidthへの引数として送信するこのwidth値をどこで、いつ取得するかです。
私は何とか合法的に見えるものを作ることができたので、私が何らかの形で間違っている場合は私を修正し、より良い方法を知っているなら教えてください。
UIViewの
-(CGSize) intrinsicContentSize
Self.frame.widthによるUILabelsのIsetPreferredMaxLayoutWidth.
UIViewControllerの
-(void) viewDidLayoutSubviews
は、メインビューのサブビューが画面上に存在する正確なフレームで指定される場所である最初のコールバックメソッドです。そのメソッドの内部から、サブビューを操作し、その固有のサイズを無効にして、UILabelsに指定された幅に基づいて複数の行に分割します。
その幅を明確に定義している制約がある場合、UILabelが優先最大レイアウト幅のデフォルトの幅にならないのは面倒です。
Autolayoutでラベルを使用したほぼすべての場合、残りのレイアウトが実行されると、推奨される最大レイアウト幅はラベルの実際の幅になります。
そのため、これを自動的に行うために、setBounds:
をオーバーライドするUILabelサブクラスを使用しました。ここで、スーパー実装を呼び出してから、まだそうでない場合、優先最大レイアウト幅を境界サイズ幅に設定します。
強調が重要です-優先最大レイアウトを設定すると、別のレイアウトパスが実行されるため、無限ループになる可能性があります。
Advanced Auto Layout Toolbox の「複数行テキストの本質的なコンテンツサイズ」セクションの objc.io に、この質問に対する答えがあります。関連情報は次のとおりです。
UILabelとNSTextFieldの本質的なコンテンツサイズは、複数行のテキストではあいまいです。テキストの高さは、線の幅に依存しますが、これは制約を解決するときにまだ決定されていません。この問題を解決するために、両方のクラスにpreferredMaxLayoutWidthという新しいプロパティがあります。このプロパティは、固有のコンテンツサイズを計算するための最大行幅を指定します。
通常、この値は事前にわからないため、これを正しく行うには2段階のアプローチをとる必要があります。最初に自動レイアウトを機能させ、次にレイアウトパスで結果のフレームを使用して、優先最大幅を更新し、レイアウトを再度トリガーします。
View Controller内で使用するために提供するコード:
- (void)viewDidLayoutSubviews
{
[super viewDidLayoutSubviews];
myLabel.preferredMaxLayoutWidth = myLabel.frame.size.width;
[self.view layoutIfNeeded];
}
彼らの投稿を見てください、なぜレイアウトを2回行う必要があるのかについての詳細があります。
更新
私の元の答えは有用であるように見えるので、以下に触れずに残しましたが、自分のプロジェクトでは、iOS 7とiOS 8のバグを回避するより信頼性の高いソリューションを見つけました https://github.com/nicksnyder/ios-cell-layout
元の答え
これは、iOS 7およびiOS 8で動作する完全なソリューションです
目的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()
}
}
UIScrollView内の自動レイアウトUILabelが縦向きにレイアウトされていたが、横向きに回転してもUILabelの高さが再計算されない状況がありました。
おそらくpreferredMaxLayoutWidthが正しく設定されたため、@ jrturtonからの回答がこれを修正したことがわかりました。
使用したコードは次のとおりです。 Interface BuilderのCustomクラスをCVFixedWidthMultiLineLabelに設定するだけです。
CVFixedWidthMultiLineLabel.h
@interface CVFixedWidthMultiLineLabel : UILabel
@end
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
コメントを追加することは許可されていないので、回答として追加する必要があります。 jrturtonのバージョンは、問題のラベルのlayoutIfNeeded
を取得する前にupdateViewConstraints
でpreferredMaxLayoutWidth
を呼び出す場合にのみ機能しました。 layoutIfNeeded
への呼び出しなしでは、preferredMaxLayoutWidth
は常にupdateViewConstraints
の0
でした。それでも、setBounds:
でチェックすると、常に目的の値になりました。正しいpreferredMaxLayoutWidth
が設定されているとき、私はなんとか知りませんでした。 UILabel
サブクラスでsetPreferredMaxLayoutWidth:
をオーバーライドしますが、呼び出されませんでした。
要約すると、I:
setBounds:
をオーバーライドし、まだ設定されていない場合、preferredMaxLayoutWidth
をCGRectGetWidth(bounds)
に設定します[super updateViewConstraints]
を呼び出しますlayoutIfNeeded
を取得する前にpreferredMaxLayoutWidth
を呼び出すEDIT:この回避策は、機能している、または必要な場合があるようです。レイアウトプロセスが1回実行された後にpreferredMaxLayoutWidth
が0
を返したため、ラベルの高さが正しく計算されないという問題(iOS 7/8)がありました。試行錯誤の後(そして、これを見つけた ブログエントリ )、UILabel
を再び使用するように切り替えて、上下左右の自動レイアウト制約を設定しました。また、何らかの理由で、テキストの更新後にラベルの高さが正しく設定されました。
次のように目的の幅を測定することで、「\ n」を改行として使用していた従来のUITableViewCell
にある2つの複数行ラベルの問題を解決しました。
- (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;
}
次に、メソッドの呼び出しは次のように簡単でした:
CGFloat labelPreferredWidth = [self preferredMaxLayoutWidthForLabel:textLabel];
if (labelPreferredWidth > 0.0f) {
textLabel.preferredMaxLayoutWidth = labelPreferredWidth;
}
[textLabel layoutIfNeeded];
別の答えが示唆するように、viewDidLayoutSubviews
をオーバーライドしようとしました:
- (void)viewDidLayoutSubviews
{
[super viewDidLayoutSubviews];
_subtitleLabel.preferredMaxLayoutWidth = self.view.bounds.size.width - 40;
[self.view layoutIfNeeded];
}
これは機能しましたが、UIで表示され、「目に見えるちらつき」が発生しました。つまり、最初にラベルが2行の高さでレンダリングされ、次に1行だけの高さで再レンダリングされました。
これは私には受け入れられませんでした。
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;
}
「視覚的なちらつき」を引き起こさなかったので、これは私にとってより良い解決策でした。
きれいな解決策は、_rowcount = 0
_を設定し、ラベルの高さの制約にプロパティを使用することです。次に、コンテンツが設定された後
_CGSize sizeThatFitsLabel = [_subtitleLabel sizeThatFits:CGSizeMake(_subtitleLabel.frame.size.width, MAXFLOAT)];
_subtitleLabelHeightConstraint.constant = ceilf(sizeThatFitsLabel.height);
_
-(void) updateViewConstraints
にはiOS 7.1以降の問題があります。
IOS 8では、セルをデキューして構成した後にcell.layoutIfNeeded()
を呼び出すことにより、セル内の複数行ラベルレイアウトの問題を修正できます。この呼び出しはiOS 9では無害です。
Nick Snyderの答えをご覧ください。このソリューションは、 https://github.com/nicksnyder/ios-cell-layout/blob/master/CellLayout/TableViewController.Swift の彼のコードから取られました。