diffable 数据源节标题在更新期间闪烁

Posted

技术标签:

【中文标题】diffable 数据源节标题在更新期间闪烁【英文标题】:diffable data source section header blinks during update 【发布时间】:2020-11-11 16:47:04 【问题描述】:

我目前面临的问题是,当将新快照应用到我当前的数据源时,页眉、页脚和装饰视图不属于集合视图的子视图,这可能会导致奇怪的闪烁。以前有人遇到过这个问题吗?

我通过以下方式更新数据源:

    var snapshot = NSDiffableDataSourceSnapshot<Section, Item>()
    snapshot.appendSections(Sections.allCases)
    items.forEach  snapshot.appendItems([$0], toSection: ItemSectionMapper.getSection(for: $0)) 
    self.dataSource?.apply(snapshot)

编辑: 它似乎只发生在 ios 14 设备上。

编辑2: 这是示例项目中相同问题的屏幕记录: https://imgur.com/a/0rS9aZU

下面的代码:

import UIKit

// MARK: - Cell -

final class Cell: UICollectionViewCell 
    static let reuseIdentifier = "Cell"

    var isExpanded = false 
        didSet  label.numberOfLines = numberOfLines 
    

    var numberOfLines: Int  isExpanded ? 0 : 3 

    lazy var label: UILabel = 
        let label = UILabel()
        label.numberOfLines = numberOfLines
        label.frame.size = contentView.bounds.size
        label.autoresizingMask = [.flexibleWidth, .flexibleHeight]
        return label
    ()

    override init(frame: CGRect) 
        super.init(frame: frame)
        contentView.addSubview(label)
    

    required init?(coder: NSCoder) 
        fatalError("init(coder:) has not been implemented")
    

    override func sizeThatFits(_ size: CGSize) -> CGSize 
        label.sizeThatFits(size)
    


final class Header: UICollectionReusableView 
    static let elementKind = "Header"
    
    lazy var label: UILabel = 
        let label = UILabel()
        label.numberOfLines = 1
        label.frame.size = bounds.size
        label.autoresizingMask = [.flexibleWidth, .flexibleHeight]
        return label
    ()
    
    override init(frame: CGRect) 
        super.init(frame: frame)
        addSubview(label)
    
    
    required init?(coder: NSCoder) 
        fatalError("init(coder:) has not been implemented")
    
    
    override func sizeThatFits(_ size: CGSize) -> CGSize 
        label.sizeThatFits(size)
    


// MARK: - UIViewController -

class ViewController: UIViewController 
    struct Item: Hashable 
        let text: String
        var isExpanded = false
        private let uuid = UUID()
    

    var items: [Item] = [
        .init(
            text: """
            Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor
            incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis
            nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
            Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore
            eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident.
            """
        ),
        .init(
            text: """
            Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor
            incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis
            nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
            Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore
            eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident.
            """,
            isExpanded: true
        )
    ]

    lazy var collectionView: UICollectionView = 
        let collectionView = UICollectionView(frame: view.bounds, collectionViewLayout: createCollectionViewLayout())
        collectionView.register(Cell.self, forCellWithReuseIdentifier: Cell.reuseIdentifier)
        collectionView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
        collectionView.contentInset.top = 44
        collectionView.backgroundColor = .white
        collectionView.delegate = self
        return collectionView
    ()

    lazy var dataSource = UICollectionViewDiffableDataSource<Int, Item>(collectionView: collectionView)  collectionView, indexPath, itemIdentifier in
        guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: Cell.reuseIdentifier, for: indexPath) as? Cell else  fatalError() 
        cell.isExpanded = itemIdentifier.isExpanded
        cell.label.text = itemIdentifier.text
        return cell
    

    override func viewDidLoad() 
        super.viewDidLoad()
        dataSource.supplementaryViewProvider =  (collectionView, kind, indexPath) -> UICollectionReusableView? in
            guard let view = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: Header.elementKind, for: indexPath) as? Header else  fatalError() 
            view.label.text = "Test"
            return view
        
        view.addSubview(collectionView)
        collectionView.register(Header.self, forSupplementaryViewOfKind: Header.elementKind, withReuseIdentifier: Header.elementKind)
        updateSnapshot()
    

    private func createCollectionViewLayout() -> UICollectionViewCompositionalLayout 
        let layoutSize = NSCollectionLayoutSize.init(
            widthDimension: .fractionalWidth(1.0),
            heightDimension: .estimated(200)
        )

        let section = NSCollectionLayoutSection(group:
            .vertical(
                layoutSize: layoutSize,
                subitems: [.init(layoutSize: layoutSize)]
            )
        )
        
        let header = NSCollectionLayoutBoundarySupplementaryItem(layoutSize: .init(widthDimension: .fractionalWidth(1), heightDimension: .estimated(20)), elementKind: Header.elementKind, alignment: .top)
        section.boundarySupplementaryItems = [header]
        section.contentInsets = .init(top: 0, leading: 16, bottom: 0, trailing: 16)
        section.interGroupSpacing = 20

        return .init(section: section)
    

    private func updateSnapshot() 
        var snapshot = NSDiffableDataSourceSnapshot<Int, Item>()
        snapshot.appendSections([0])
        snapshot.appendItems(items)
        dataSource.apply(snapshot, animatingDifferences: true)
    


// MARK: - UICollectionViewDelegate -

extension ViewController: UICollectionViewDelegate 
    public func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) 
        guard let itemIdentifier = dataSource.itemIdentifier(for: indexPath) else  return 
        items[indexPath.row] = .init(text: itemIdentifier.text, isExpanded: !itemIdentifier.isExpanded)
        updateSnapshot()
    

向@JWK致敬

【问题讨论】:

【参考方案1】:

这种行为似乎出乎意料,尽管我相信它的发生仅仅是因为整个部分都在apply(_:animatingDifferences:completion:) 中使用动画进行了更新。您可以尝试一些解决方法:

    在调用apply(_:animatingDifferences:completion:) 时将animatingDifferences 设置为false。如果您想要动画,则不理想。 添加另一个部分而不是使用boundarySupplementaryItems。不展开的部分不应该在视觉上受到影响。您可能需要引入另一个单元格并为此使用UICollectionViewCompositionalLayoutinit(sectionProvider:)(为每个部分提供正确的NSCollectionLayoutSection)。 如果您使用的是 iOS 14+,我认为您可以通过将 UICollectionViewListCellaccessories 属性设置为 UICellAccessory.OutlineDisclosureOptions(style: .header) 来免费获得所需的行为。有一个示例项目以及其他有用的示例here。

【讨论】:

以上是关于diffable 数据源节标题在更新期间闪烁的主要内容,如果未能解决你的问题,请参考以下文章

iOS 13 上 TableView 错误的 Diffable 数据源:移动的关联不一致

更新期间停止文本框闪烁

领域和 Diffable 数据源

UITableView diffable 数据源

支持 iOS 12 和 13 时的 UITableView 和 Diffable 数据源

在表/集合视图控制器及其关联的 diffable 数据源子类之间共享数据模型的好方法是啥?