【问题标题】:UICollectionView cells resizing when deleting items with estimatedItemSizeUICollectionView 单元格在删除带有估计项大小的项目时调整大小
【发布时间】:2016-03-06 20:39:54
【问题描述】:

我有一个简单的项目,故事板只包含一个 UICollectionViewController,使用 Xcode 7.1.1 for iOS 9.1 构建

class ViewController: UICollectionViewController {

    var values = ["tortile", "jetty", "tisane", "glaucia", "formic", "agile", "eider", "rooter", "nowhence", "hydrus", "outdo", "godsend", "tinkler", "lipscomb", "hamlet", "unbreeched", "fischer", "beastings", "bravely", "bosky", "ridgefield", "sunfast", "karol", "loudmouth", "liam", "zunyite", "kneepad", "ashburn", "lowness", "wencher", "bedwards", "guaira", "afeared", "hermon", "dormered", "uhde", "rusher", "allyou", "potluck", "campshed", "reeda", "bayonne", "preclose", "luncheon", "untombed", "northern", "gjukung", "bratticed", "zeugma", "raker"]

    @IBOutlet weak var flowLayout: UICollectionViewFlowLayout!
    override func viewDidLoad() {
        super.viewDidLoad()        
        flowLayout.estimatedItemSize = CGSize(width: 10, height: 10)
    }

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

    override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCellWithReuseIdentifier("MyCell", forIndexPath: indexPath) as! MyCell
        cell.name = values[indexPath.row]
        return cell
    }

    override func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
        values.removeAtIndex(indexPath.row)
        collectionView.deleteItemsAtIndexPaths([indexPath])
    }
}

class MyCell: UICollectionViewCell {

    @IBOutlet weak var label: UILabel!

    var name: String? {
        didSet {
            label.text = name
        }
    }
}

当从集合视图中删除单元格时,所有剩余的单元格都会动画到它们的estimatedItemSize,然后换回正确的大小。

有趣的是,这会在动画发生时为每个单元格生成自动布局约束警告:

2015-12-02 14:30:45.236 CollectionTest[1631:427853] Unable to simultaneously satisfy constraints.
    Probably at least one of the constraints in the following list is one you don't want. 
    Try this: 
        (1) look at each constraint and try to figure out which you don't expect; 
        (2) find the code that added the unwanted constraint or constraints and fix it. 
    (Note: If you're seeing NSAutoresizingMaskLayoutConstraints that you don't understand, refer to the documentation for the UIView property translatesAutoresizingMaskIntoConstraints) 
(
    "<NSAutoresizingMaskLayoutConstraint:0x14556f780 h=--& v=--& H:[UIView:0x1456ac6c0(10)]>",
    "<NSLayoutConstraint:0x1456acfd0 UIView:0x1456ac6c0.trailingMargin == UILabel:0x1456ac830'raker'.trailing>",
    "<NSLayoutConstraint:0x1456ad020 UILabel:0x1456ac830'raker'.leading == UIView:0x1456ac6c0.leadingMargin>"
)

Will attempt to recover by breaking constraint 
<NSLayoutConstraint:0x1456acfd0 UIView:0x1456ac6c0.trailingMargin == UILabel:0x1456ac830'raker'.trailing>

我最初的想法是打破这些限制是导致调整大小问题的原因。

更新单元格的awakeFromNib 方法:

override func awakeFromNib() {
    super.awakeFromNib()
    contentView.translatesAutoresizingMaskIntoConstraints = false
} 

修复了警告,但问题仍然存在。

我尝试在单元格及其contentView 之间重新添加我自己的约束,但这并没有解决问题:

override func awakeFromNib() {
    super.awakeFromNib()
    contentView.translatesAutoresizingMaskIntoConstraints = false

    for constraint in [
        contentView.leadingAnchor.constraintEqualToAnchor(leadingAnchor),
        contentView.trailingAnchor.constraintEqualToAnchor(trailingAnchor),
        contentView.topAnchor.constraintEqualToAnchor(topAnchor),
        contentView.bottomAnchor.constraintEqualToAnchor(bottomAnchor)]
    {
        constraint.priority = 999
        constraint.active = true
    }
}

想法?

【问题讨论】:

  • 两年后,我仍然面临同样的问题。它有几个雷达openradar.me/23728611;假设一个是你的,虽然? :)
  • 添加了一个骗子:rdar://35717256

标签: ios swift autolayout uicollectionview


【解决方案1】:

流布局在进行布局后根据估计的大小计算单元格的实际大小,以定义哪些是可见的。之后,它会根据实际尺寸调整布局。

但是,当它制作动画时,当它计算动画的初始位置时,它并没有达到使单元格出列并在那里运行自动布局的阶段,因此它只使用估计的大小。

最简单的方法是尝试给出最接近的估计大小,或者如果您可以在 sizeForItemAt 调用中的委托中提供大小。

在我的例子中,我试图在不插入或删除单元格的情况下对 layoutAttributes 进行动画处理,对于这种特定情况,我将 UICollectionViewFlowLayout 子类化,然后重写此方法:

override func invalidateLayout(with context: UICollectionViewLayoutInvalidationContext) {
    if !context.invalidateEverything && context.invalidatedItemIndexPaths == nil && context.contentOffsetAdjustment == .zero  && context.contentSizeAdjustment == .zero {
        return
    }
    super.invalidateLayout(with: context)
}

这可以防止在未更改任何内容时使用估计的大小重新计算布局属性。

【讨论】:

  • 在调用 reloadData() 时为我工作,但在尝试动画添加/删除项目时崩溃。
【解决方案2】:

TL;DR:我只能让集合视图与委托 sizeForItem 方法一起正常运行。此处的工作示例:https://github.com/chrisco314/CollectionView-AutoLayout

在控制器中:

func collectionView(_ collectionView: UICollectionView,
                    layout collectionViewLayout: UICollectionViewLayout,
                    sizeForItemAt indexPath: IndexPath) -> CGSize {
    var cell = Cell.prototype
    let contents = data[indexPath.section][indexPath.item]
    cell.text = contents
    cell.expand = selected.contains(indexPath)

    let width = collectionView.bounds
        .inset(collectionView.contentInset)
        .inset(layout.sectionInset)
        .width
    let finalSize = cell.systemLayoutSizeFitting(
        .init(width: width, height: 0),
        withHorizontalFittingPriority: .required,
        verticalFittingPriority: .fittingSizeLevel)
        .withWidth(width)
    print("sizeForItemAt: \(finalSize)")
    return finalSize
}

在单元格中:

override func systemLayoutSizeFitting(
    _ targetSize: CGSize,
    withHorizontalFittingPriority horizontalFittingPriority: UILayoutPriority,
    verticalFittingPriority: UILayoutPriority) -> CGSize {

    let contentSize = contentView.systemLayoutSizeFitting(
        targetSize,
        withHorizontalFittingPriority: horizontalFittingPriority,
        verticalFittingPriority: verticalFittingPriority)

    return contentSize
}

扩展面板的约束:

lazy var panel: UIView = {
    let view = Panel()
    view.pin(body, to: .left, .top, .right)
    view.clipsToBounds = true
    panelHeight = view.heightAnchor.constraint(equalTo: body.heightAnchor)
    return view
}()

var panelHeight: NSLayoutConstraint!
lazy var height:CGFloat = 60

lazy var body: UIView = {
    let view = Body()
    view.backgroundColor = .blue
    view.pin(contents, inset: 9)
    let bodyHeight = view.heightAnchor.constraint(equalToConstant: height)
    bodyHeight.isActive = true
    return view
}()

lazy var contents: UILabel = {
    let label = UILabel()
    label.backgroundColor = .white
    label.numberOfLines = 0
    label.text = "Body with height constraint of \(height)"
    return label
}()

我遇到了很多这样的问题以及许多其他问题,我花了很多时间试图找到一条适用于所有情况的路径 - 使用自动布局进行渲染、插入和删除的合理动画、处理旋转等。根据我的经验,唯一可行的方法是使用 sizeForItem 委托方法。您可以使用estimateSize 和自动布局,但对我来说,动画总是会折叠到顶部,然后一切都会再次弹出——也许是你所看到的。

我有一个示例,基本上是我的测试场地。我在这里尝试了跨选项卡视图控制器的不同选项卡的不同方法,使用估计大小、单元格本身的约束、返回所需大小的自定义 systemSizeFitting 以及基于委托的 sizeThatFits

该示例有点乱,但第三个选项卡演示了一个基于委托的方法,该方法适用于扩展单元格以及插入和删除动画。注意tab2?根据扩展单元格的比例,演示集合视图使用的不一致动画。如果比例大于 2:1,它会淡出并捕捉,如果它小于 2:1,它会平滑地上下动画。

在动画方面,所有尝试过的非委托方法都失败了,如上所述。也许有一种方法可以在没有委托方法的情况下工作(我很想看看它是否有效),但我找不到它。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2020-06-26
    • 1970-01-01
    • 1970-01-01
    • 2021-09-28
    • 2015-09-16
    • 1970-01-01
    • 2012-05-20
    • 2013-02-24
    相关资源
    最近更新 更多