创建 UICollectionViewCell 时子视图框架不正确

Subview frame is incorrect when creating 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)
    }
}

设置集合视图是为了一次垂直显示两个单元格,每个单元格包含一个涂有颜色的大矩形和一个带有颜色名称的标签。

当我在模拟器上运行上述代码时,我得到如下打印结果:

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 frame的过程中发生了一些我无法理解的事情。

我认为这与从 Nib 加载单元格这一事实有关,因此控制器不使用 init(frame: frame) 初始化程序,而是使用 init(coder: aCoder) 初始化程序,因此创建单元格后,它可能会附带一些我无法编​​辑的默认框架。

如果能帮助我了解正在发生的事情,我将不胜感激!

我正在使用 Xcode 6.1.1。使用 iOS SDK 8.1.

好的,我现在明白单元格是在自动布局定义其框架之前创建的。这就是为什么在创建时边界是错误的。当单元格被重新使用时,帧已经被纠正。

我在创建自定义 UIView 时遇到了这个问题,该自定义 UIView 在特定坐标中放置了一些图层和子视图。创建此 UIView 的实例时,子视图的放置都是错误的(因为自动布局尚未启动)。

我发现我不得不重写 layoutSubviews() 方法,而不是在 init(coder: aCoder) 上配置视图子视图。当自动布局要求每个视图布局自己的子视图时调用此方法,因此此时至少父视图具有正确的框架,我可以使用它正确放置子视图。

可能如果我在自定义视图代码上使用约束而不是自己处理框架大小和定位,那么布局就会正确完成并且不需要覆盖 layoutSubviews().

我在 UICollectionViewCell 使用自动布局约束时遇到了同样的问题。

在配置依赖于视图框架宽度的子视图之前,我不得不调用 layoutIfNeeded

您可以通过覆盖自定义单元格中的 layoutIfNeeded() 来获取单元格的最终帧 class,如下所示:

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()

iOS 10、Swift 3.0.1.

中的 Core Graphics 绘图有此问题

将此方法添加到 UICollectionView 子类:

override func didMoveToSuperview() {
    super.didMoveToSuperview()
    setNeedsLayout()
    layoutIfNeeded()
}

我的问题是 Core Graphics 形状计算不正确,因为没有调用 layoutSubviews()

我建议为您正在做的任何事情创建一个子类。我需要在我的单元格中的 UIImageView 上设置一个渐变,但它计算错误。我尝试了 layoutSubviews 的建议,但它也导致了问题,它似乎会应用两次渐变。

我制作了一个 UIImageView 子类,它可以正常工作。

class MyOwnImageView: UIImageView{
  override func layoutSubviews() {
        super.layoutSubviews()
        let view = UIView(frame: frame)

        let width = bounds.width
        let height = bounds.height
        let sHeight:CGFloat = 122.0
        let shadow = UIColor.black.withAlphaComponent(0.9).cgColor

        let topImageGradient = CAGradientLayer()
        topImageGradient.frame = CGRect(x: 0, y: 0, width: width, height: sHeight)
        topImageGradient.colors = [shadow, UIColor.clear.cgColor]
        view.layer.insertSublayer(topImageGradient, at: 0)

        let bottomImageGradient = CAGradientLayer()
        bottomImageGradient.frame = CGRect(x: 0, y: height - sHeight, width: width, height: sHeight)
        bottomImageGradient.colors = [UIColor.clear.cgColor, shadow]
        view.layer.insertSublayer(bottomImageGradient, at: 0)

        addSubview(view)
        bringSubviewToFront(view)
    }
}