使用estimatedItemSize删除项目时,UICollectionView单元格会调整大小

时间:2015-12-02 14:44:02

标签: ios swift autolayout uicollectionview

我有一个简单的项目,其故事板只包含一个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的动画,然后交换回正确的大小。

enter image description here

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

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
    }
}

思想?

2 个答案:

答案 0 :(得分:2)

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

然而,当它动画时,当它计算动画的初始位置时,它不会到达出列单元格并在那里运行自动布局的阶段,所以它只使用估计的大小。

最简单的方法是尝试提供最接近的估算尺寸,或者如果您可以在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)
}

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

答案 1 :(得分:1)

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,则它会平滑地上下动画。

所有涉及动画的非委托方法都失败了,如上所述。也许有一种方法可以不使用委托方法(我很想看看是否可行),但是我找不到它。

enter image description here