web-dev-qa-db-ja.com

UICollectionViewCellの作成時にサブビューフレームが正しくない

問題

カスタムUICollectionViewCellを使用してUICollectionViewControllerを作成しました。カスタムセルには、大きな長方形のUIView(colorViewという名前)とUILabel(nameLabelという名前)が含まれています。

コレクションに最初にセルが入力され、colorView.frameを印刷すると、印刷されたフレームの値が正しくありません。 colorViewが正しく描画されても、colorViewフレームはセルフレーム自体よりも大きいため、それらが正しくないことはわかっています。

ただし、collectionViewを十分にスクロールして、以前に作成したセルの再利用をトリガーすると、colorView.frameの値が正しくなります!丸い角をcolorViewレイヤーに適用し、これを行うには正しいcoloViewサイズが必要なので、正しいフレームが必要です。ちなみに、不思議に思うかもしれませんが、colorView.boundsにはcolorView.frameと同じ間違ったサイズ値もあります。

質問

セルを作成するときにフレームが正しくないのはなぜですか?

そして今いくつかのコード

これは私のUICollectionViewCellです:

class BugCollectionViewCell: UICollectionViewCell {
    @IBOutlet weak var colorView: UIView!
    @IBOutlet weak var nameLabel: UILabel!
}

これはUICollectionViewControllerです:

import UIKit

let reuseIdentifier = "Cell"
let colors = [UIColor.redColor(), UIColor.blueColor(),
              UIColor.greenColor(), UIColor.purpleColor()]
let labels = ["red", "blue", "green", "purple"]

class BugCollectionViewController: UICollectionViewController, UICollectionViewDelegateFlowLayout {
    override func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int {
        return 1
    }

    override func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return colors.count
    }

    override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCellWithReuseIdentifier(reuseIdentifier, forIndexPath: indexPath) as BugCollectionViewCell

        println("ColorView frame: \(cell.colorView.frame) Cell frame: \(cell.frame)")

        cell.colorView.backgroundColor = colors[indexPath.row]
        cell.nameLabel.text = labels[indexPath.row]

        return cell
    }

    func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize {
        let width = self.collectionView?.frame.width
        let height = self.collectionView?.frame.height
        return CGSizeMake(width!, height!/2)
    }
}

コレクションビューは、一度に2つのセルを垂直に表示するために設定されます。各セルには、色で塗りつぶされた大きな長方形と色の名前のラベルが含まれます。

シミュレータで上記のコードを実行すると、次の印刷結果が得られます。

ColorView frame: (0.0,0.0,320.0,568.0) Cell frame: (0.0,0.0,375.0,333.5)
ColorView frame: (0.0,0.0,320.0,568.0) Cell frame: (0.0,343.5,375.0,333.5)

奇妙な結果です-colorView.frameの高さは568ポイントですが、セルフレームの高さはわずか333.5ポイントです。

CollectionViewを下にドラッグしてセルを再利用すると、次の結果が出力されます。

ColorView frame: (8.0,8.0,359.0,294.0) Cell frame: (0.0,1030.5,375.0,333.5)
ColorView frame: (8.0,8.0,359.0,294.0) Cell frame: (0.0,343.5,375.0,333.5)

ColorViewのフレームを修正する過程で、私には理解できない何かが起こりました。

セルがNibからロードされるという事実と関係があると思うので、init(frame:frame)イニシャライザーを使用する代わりに、コントローラーはinit(coder:aCoder)イニシャライザーを使用します。作成されたものには、おそらく編集できないデフォルトフレームが付属している可能性があります。

何が起こっているのかを理解できるように、どんな助けでも感謝します!

Xcode 6.1.1を使用しています。 iOS SDK 8.1で。

36
Felipe Ferri

次のようにカスタムCellクラスでlayoutIfNeeded()をオーバーライドすることで、セルの最終フレームを取得できます。

override func layoutIfNeeded() {
    super.layoutIfNeeded()
    self.subView.layer.cornerRadius = self.subView.bounds.width / 2
}

次に、UICollectionViewデータソースメソッドcellForRowAtIndexPathで:これを行います。

let cell = collectionView.dequeueReusableCellWithReuseIdentifier("Cell", forIndexPath: indexPath) as! CustomCollectionViewCell
cell.setNeedsLayout()
cell.layoutIfNeeded()
56
Jon Vogel

自動レイアウト制約を使用したUICollectionViewCellでも同じ問題が発生しました。

ビューのフレーム幅に依存するサブビューを構成する前に、layoutIfNeededを呼び出す必要がありました。

16
RyanG

iOS 10、Swift 3.0.1

このメソッドをUICollectionViewサブクラスに追加します。

_override func didMoveToSuperview() {
    super.didMoveToSuperview()
    setNeedsLayout()
    layoutIfNeeded()
}
_

私の問題は、layoutSubviews()が呼び出されなかったため、Core Graphicsの形状が適切に計算されなかったことです。

8
Vadim Bulavin

OK、自動レイアウトがフレームを定義する前にセルが作成されることを理解しました。それが、作成の瞬間に境界が間違っている理由です。セルが再利用されるとき、フレームはすでに修正されています。

特定の座標にいくつかのレイヤーとサブビューを配置するカスタムUIViewを作成しているときに、この問題が発生していました。このUIViewのインスタンスが作成されたとき、サブビューの配置はすべて間違っていました(自動レイアウトがまだ開始されていないため)。

init(coder: aCoder)のビューサブビューを構成する代わりに、メソッドlayoutSubviews()をオーバーライドする必要があることがわかりました。これは、自動レイアウトが各ビューに独自のサブビューをレイアウトするように要求するときに呼び出されるため、この時点で少なくとも親ビューに正しいフレームがあり、サブビューを正しくレイアウトするために使用できます。

おそらく、フレームサイズと位置を処理する代わりにカスタムビューコードに制約を使用した場合、レイアウトは適切に行われ、layoutSubviews()をオーバーライドする必要はないでしょう。

8
Felipe Ferri