2016-12-11 7 views
4

私はtableViewにカスタムビューを表示しようとしていて、iOSは各ローの高さをUITableViewAutomaticDimensionで計算させようとしています。不幸にも、私のセルのサイズが適切ではありません。高さと幅が固定されたビューのintrinsictContentSizeをオーバーライドする方法はありますか?

は私のカスタムビューは、(私は多くの理由のため、この部分のための自動レイアウトを使用していない、と私はそれを使用する必要はありません)以下のように定義されます。

class MyView : UIView { 
    var label: UILabel! 

    override init(frame: CGRect) { 
     super.init(frame: frame) 
     setupView() 
    } 

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

    func setupView() { 
     label = UILabel() 
     label.numberOfLines = 0 
     addSubview(label) 
    } 

    func setText(text: String) { 
     label.text = text 
     setNeedsLayout() 
    } 

    override func layoutSubviews() { 
     super.layoutSubviews() 
     label.frame = bounds 
     invalidateIntrinsicContentSize() 
    } 

    override var intrinsicContentSize: CGSize { 
     guard let text = label.text else { return .zero } 
     let width = bounds.width 
     let height = text.heightWithConstrainedWidth(width: width, font: label.font) 
     return CGSize(width: width, height: height) 
    } 
} 

は今、私はこのカスタムビューを包んUITableViewで使用するUITableViewCellここで私は、自動レイアウトを使用しています:

class MyCell : UITableViewCell { 
    var myView: MyView! 

    override init(style: UITableViewCellStyle, reuseIdentifier: String?) { 
     super.init(style: style, reuseIdentifier: reuseIdentifier) 
     setupCell() 
    } 

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

    func setupCell() { 
     myView = MyView() 
     contentView.addSubview(myView) 
     myView.snp.makeConstraints { make in 
      make.edges.equalTo(contentView) 
     } 
    } 
} 

は今、次のViewControllerを使用して、私はその高さが自動的に計算されますかどうかを確認するために、大きな文字でセルを表示:

生憎
class ViewController: UIViewController { 
    var tableView: UITableView! 

    override func viewDidAppear(_ animated: Bool) { 
     super.viewDidAppear(animated) 
     tableView = UITableView() 
     view.addSubview(tableView) 
     tableView.snp.makeConstraints { make in 
      make.edges.equalTo(view) 
     } 
     tableView.register(MyCell.self, forCellReuseIdentifier: "MyCell") 
     tableView.rowHeight = UITableViewAutomaticDimension 
     tableView.estimatedRowHeight = 30 
     tableView.dataSource = self 
    } 
} 

extension ViewController: UITableViewDataSource { 
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 
     return 1 
    } 

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 
     let cell = tableView.dequeueReusableCell(withIdentifier: "MyCell") as! MyCell 
     cell.myView.setText(text: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur") 
     return cell 
    } 
} 

、セルの高さがありますMyViewの最後intrinsicContentSizeの呼び出しによって返された高さはここで143であっても、21に等しく、これは、結果のスクリーンショットです: enter image description here

セルが正しいサイズであります私はMyViewintrinsictContentSizeは、そのフレームを使用していることを知っていて、それがいけない enter image description here

が、私は持っているUIViewのサブクラスを設計する方法については考えている。それがリサイクルされている場合(私は画面外のセルをスクロール)固定幅を指定した柔軟な高さ。私はinvalidateIntrinsicContentSize()をMyViewのlayoutSubviews()と呼ぶという事実を考えればうまくいくと思った。

このコードを変更して、セルの高さを最初から高く設定するにはどうすればよいですか?

+0

ここでAutolayoutを使用しない理由を詳しく説明できますか?これはまさにそれが素晴らしいことです。 – Ben

+0

私の実際の使用例では、単にUILabelの代わりに何か他のものがあります。私はたくさんのサブビューで構成された非常に豊富なUIViewを持っています。私がこのすべてのために自動レイアウトをしていたなら、私は最終的にはtableViewsでパフォーマンスの問題が発生するでしょう。 – MartinMoizard

答えて

2

ドキュメントでは、intrinsicContentSizeを上書きすることが、達成したいことを達成する最良の方法ではないことが示唆されています。ご覧のとおり、UIViewクラスリファレンス:

このプロパティを設定すると、カスタムビューでレイアウトシステムに、その内容に基づいてサイズを指定することができます。この固有のサイズは、コンテンツフレームから独立していなければなりません。変更された幅を変更された高さに基づいてレイアウトシステムに動的に伝達する方法がないからです。

MYVIEWのboundsは、そのintrinsicContentSizeが要求された後まで設定されていない、と自動レイアウトコンテキストではlayoutSubviews()が呼び出される前の任意の時点で設定されていることが保証されていません。しかし、これを回避し、カスタムビューに幅を渡してintrinsicContentSizeで使用する方法がありますが、これはこの回答の2番目のオプションに含まれています。

オプション1:自動レイアウトが存在しない場合に、sizeThatFits(_:)

ファーストをオーバーライドすることにより、手動レイアウトは、サイズにMYVIEWとそのラベルのサブビューを設定し、適切に自分自身のサイズを変更:

// MyView.swift 

func setupView() { 
    label = UILabel() 
    label.numberOfLines = 0 
    label.autoresizingMask = [.flexibleWidth] // New 
    addSubview(label) 
} 

override func layoutSubviews() { 
    super.layoutSubviews() 
    label.sizeToFit() // New 
} 

override func sizeThatFits(_ size: CGSize) -> CGSize { 
    return label.sizeThatFits(size) // New 
} 

MYVIEWは今意志(ラベル自動サイズ調整マスクのため)とテキストコンテンツ(numberOfLinesは0に設定されているため)のサイズに合わせてサイズを変更します。 MyViewに他のサブビューがある場合は、合計サイズを計算してsizeThatFits(_:)に戻す必要があります。

第2に、上記のマニュアルレイアウトに従って、UITableViewでセルの高さとカスタムサブビューを計算できるようにします。自己サイズ設定のセルの場合、UITableViewは各セルのsystemLayoutSizeFitting(_:withHorizontalFittingPriority:verticalFittingPriority)を呼び出し、セルのsizeThatFits(_:)を呼び出します。 (WWDC 2014 Session 226を参照してください。)

これらのメソッドに渡されるターゲットサイズまたはフィッティングサイズは、高さがゼロのテーブルビューの幅です。水平フィッティングの優先度はUILayoutPriorityRequiredです。独自のセルサブクラス(自動レイアウトの場合は後者、手動レイアウトの場合は後者)でこれらのメソッドの1つをオーバーライドし、渡されたサイズの幅を使用して高さを計算して返すことによって、セルフサイジングセルプロセスを制御します細胞。この場合

、セルの大きさは、myViewのサイズである:

// MyCell.swift 

override func systemLayoutSizeFitting(_ targetSize: CGSize, withHorizontalFittingPriority horizontalFittingPriority: UILayoutPriority, verticalFittingPriority: UILayoutPriority) -> CGSize { 
    return myView.sizeThatFits(targetSize) 
} 

そして、あなたは完了です。

オプション2:MYVIEWのサイズは、そのラベルのサイズに依存するようintrinsicContentSize

をオーバーライドすることにより、自動レイアウトは、最初のステップは、ラベルが配置され、正確な大きさであることを保証することです。

// MyView.swift 

override func layoutSubviews() { 
    super.layoutSubviews() 
    label.frame = bounds 
} 

2番目の手順は、MyViewの組み込みコンテンツサイズを定義することです。この場合、(その寸法は、細胞または他のスーパービューによって完全に決定されるため)、何の固有の幅がなく、本来の高さは、ラベルの本来の高さ:

// MyView.swift 

override var intrinsicContentSize: CGSize { 
    return CGSize(width: UIViewNoIntrinsicMetric, height: label.intrinsicContentSize.height) 
} 

あなたのラベルが複数行であるため、最大幅が存在しない場合、その内在する高さを決定することはできません。デフォルトでは、最大幅が存在しない場合、UILabelは単一の行ラベルに適した本来のコンテンツサイズを返します。これは私たちが望むものではありません。

固有のコンテンツサイズを計算する目的で複数行ラベルに最大幅を指定する唯一の文書化された方法は、preferredMaxLayoutWidthプロパティです。 UILabelのヘッダファイルは、そのプロパティの次のコメントを提供する:

//ゼロでない場合、これは複数行ラベル

ため-intrinsicContentSizeを決定する際に第3のステップはpreferredMaxLayoutWidthが設定されていることを確認することに使用され適切な幅に調整する。 intrinsicContentSizeプロパティは自動レイアウトプロセスの一部であり、自動レイアウトプロセスにのみ関連し、カスタムセルサブクラスが自動レイアウトを実行するコードの唯一の部分であるため、レイアウトプリファレンスを設定する適切な場所はそのクラスにあります。

// MyCell.swift 

override func systemLayoutSizeFitting(_ targetSize: CGSize, withHorizontalFittingPriority horizontalFittingPriority: UILayoutPriority, verticalFittingPriority: UILayoutPriority) -> CGSize { 
    myView.label.preferredMaxLayoutWidth = targetSize.width 
    myView.invalidateIntrinsicContentSize() 

    return super.systemLayoutSizeFitting(targetSize, withHorizontalFittingPriority: horizontalFittingPriority, verticalFittingPriority: verticalFittingPriority) 
} 

MyViewに複数のラベルが付いている場合、またはMyViewのサブビュー階層をスーパービューから隠しておくことを希望する場合は、MyViewで独自のpreferredMaxLayoutWidthプロパティを作成してフォロースルーできます。

上記のコードは、マルチラインラベルの内容に基づいてMyViewが計算して適切なintrinsicContentSizeを返すようにし、サイズ変更がローテーションで無効になるようにします。時にはそれが正しく読み込ま初めてレイアウトされるから、そのセルを防ぎ、テーブルビュー上のバグがありますviewDidAppear:

DispatchQueue.main.async { 
    self.tableView.reloadData() 
} 

の下にこれを追加すること

1

ここには、このような現象を引き起こすいくつかの異なることがあります。

intrinsicContentSize内の高さの値を印刷すると、最初にビューのサイズが指定されていないため、幅がゼロで呼び出されます。次に、レイアウトのサブビューでラベルのフレームを強制しています。この時点で、セルはのが既に配置されており、invalidateIntrinsicContentSizeを使用していてもすぐに再描画されません。セルを表示外にスクロールして戻すと、その点で再描画されます。

brute-forceの解決策は、layoutSubviews内で正しいラベルの高さを計算することです。または、後で適用するテキスト文字列を設定するときに計算、保存、保存することもできます。私はまだ制約ベースのソリューションを試してみたいと思う。

override var intrinsicContentSize: CGSize { 
    guard 
     let text = label.text, 
     let parent = superview 
    else { return .zero } 
    let width = parent.bounds.width 
    let height = text.heightWithConstrainedWidth(width: width, font: label.font) 
    return CGSize(width: width, height: height) 
} 

また、あなたは柔軟性の高さを使用してコンテンツにMYVIEWのエッジのすべてを拘束している:すでに幅を持つことになります - ハックのビットはスーパーオフ幅をベースにするだろう表示します。通常は、上/上/下をMyViewからコンテンツビューに、コンテンツビューの一番下をMyViewに制限します。このようにして、コンテンツビューの高さは常にMyViewの高さに収まります。

+0

あなたの答えをありがとう、私はスーパービューのハックを使用することはできません:私は 'MyView'の幅は、セルの幅と等しいが、私のアプリのどこかに私はしたくないそれ。 – MartinMoizard

+0

次に、私が提案した他のアプローチの1つを使用する必要があります。根本的な問題は、最初の実行時にセル幅がないことです。最初のパスでサイズを正確にするために、またはセルを強制的に再描画するための追加情報が必要です。 – Ben

0

してみてください。

関連する問題