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

Posted

技术标签:

【中文标题】创建 UICollectionViewCell 时子视图框架不正确【英文标题】:Subview frame is incorrect when creating UICollectionViewCell 【发布时间】:2015-02-02 00:09:11 【问题描述】:

问题

我创建了一个带有自定义 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框架的过程中发生了一些我无法理解的事情。

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

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

我正在使用 Xcode 6.1.1。使用 ios SDK 8.1。

【问题讨论】:

您如何定位 ColorView?您没有使用自动布局吗?我认为发生在您身上的是视图布局是从笔尖加载并转换为自动布局的。但是您的故事板视图控制器大小与模拟器不同。使用自动布局来定位您的视图,然后事情应该可以正常工作。 嘿@CharlieWu,我确实在使用自动布局... colorView 和 nameLabel 完全受限... 【参考方案1】:

您可以通过在自定义 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()

【讨论】:

这里的第一个代码示例终于是真正的答案。谢谢。 我又遇到了同样的问题,你的回答比我的好。 :-) UITableViewCell 也有同样的解决方案。 layoutSubviews() 没有帮助我。只有这有帮助。也请查看这篇文章medium.com/@abhimuralidharan/…。 @Fattie for me layoutIfNeededcellForRowAt 中显式调用UICollectionViewCell 时甚至不会被调用。任何线索为什么?【参考方案2】:

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

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

【讨论】:

你在哪里调用 layoutIfNeeded?我记得我试图在视图控制器 init 上调用它,但它不起作用,似乎在执行自动布局之前加载了单元格,即使在使用 layoutIfNeeded 排队之后也是如此...... 旁注:- (void)awakeFromNib [super awakeFromNib]; [self layoutIfNeeded]; /* frames already calculated here */ 太棒了!谢谢。【参考方案3】:

iOS 10、Swift 3.0.1 中的 Core Graphics 绘图存在此问题。

将此方法添加到UICollectionView子类:

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

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

【讨论】:

@Magnas 在 UICollectionViewCell 子类中。 这是唯一对我有帮助的事情,谢谢!【参考方案4】:

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

我在创建自定义 UIView 时遇到了这个问题,该 UIView 将一些图层和子视图放置在特定坐标中。当这个 UIView 的实例被创建时,子视图的位置都是错误的(因为自动布局还没有开始)。

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

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

【讨论】:

为我的自定义视图添加逻辑的最佳位置。【参考方案5】:

我建议为你正在做的任何事情创建一个子类。我需要在单元格中的 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)
    

【讨论】:

以上是关于创建 UICollectionViewCell 时子视图框架不正确的主要内容,如果未能解决你的问题,请参考以下文章

创建UICollectionViewCell时子视图帧不正确

我有一个使用 XIB 文件创建的自定义 UICollectionViewCell。如何让这个单元格在点击时将另一个控制器推入堆栈

如何修复滚动时从 UICollectionViewCell 中消失的图像

UICollectionViewCell 在选择时不改变颜色

下载文件时如何更新UICollectionViewCell子类中的progressBar

scrollToItemAtIndexPath 破坏 UICollectionViewCell 布局