簡體   English   中英

刪除帶有EstimateItemSize的項目時,UICollectionView單元格調整大小

[英]UICollectionView cells resizing when deleting items with estimatedItemSize

我有一個帶有故事板的簡單項目,該故事板僅包含一個UICollectionViewController ,它是使用iOS 9.1的Xcode 7.1.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
    }
}

有什么想法嗎?

流布局通過估計大小以定義可見的單元格后,計算單元的實際大小。 之后,它會根據實際尺寸調整布局。

但是,在設置動畫時,當它計算動畫的初始位置時,它不會到達使單元出隊並在那里運行自動布局的階段,因此它僅使用估計的大小。

最簡單的方法是嘗試提供最接近的估計大小,或者是否可以在sizeForItemAt調用中提供委托中的大小。

就我而言,我試圖為layoutAttributes設置動畫,而不插入或刪除單元格,對於這種特定情況,我將UICollectionViewFlowLayout子類UICollectionViewFlowLayout ,然后覆蓋了此方法:

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

這樣可以防止在未進行任何更改的情況下使用估計的大小來重新計算布局屬性。

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,則它會平滑地上下動畫。

上面提到的所有非動畫方法在動畫方面都失敗了。 也許有一種方法可以不使用委托方法(我很想看看是否可行),但是我找不到它。

在此處輸入圖片說明

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM