UICollectionViewCompositionalLayout 可选部分

ash*_*pma 7 ios uicollectionview nsdiffabledatasourcesnapshot uicollectionviewcompositionallayout uicollectionviewdiffabledatasource

我看过这个问题,它是类似的:How to deal with empty items section in UICollectionView CompositionalLayout,但答案似乎是在快照中留下部分(我这样做,但这留下了另一个问题,我描述了这个问题)稍后)或渲染一个非常小的部分。该解决方案似乎不是一个好的解决方案。


我有一个集合视图,使用具有可比较数据源的组合布局。集合视图有四个部分,但每个部分都是可选的,这意味着如果该部分的相应数据为空,则不应显示该部分。

代码

布局定义

我有一个部分提供程序,它使用sectionIndex来配置每个部分的外观。我认为这很糟糕,因为例如,如果我在快照中没有第三部分的数据,那么通常应该在第四部分中的所有内容现在都会有一个索引路径,这将导致它像第三部分一样布局。

每个部分都有不同的项目大小,有些是正交滚动部分。因此,如果使用第三部分布局呈现第四部分数据,那么它看起来会是错误的。

NSCollectionLayoutSection * _Nullable (^sectionProvider)(NSInteger, id<NSCollectionLayoutEnvironment> _Nonnull) = ^NSCollectionLayoutSection * _Nullable (NSInteger sectionIndex, id<NSCollectionLayoutEnvironment> _Nonnull layoutEnvironment) {
    if (sectionIndex == 0) {
        //configure and return a layout for the first section
    } else if (sectionIndex == 1) {
        //configure and return a layout for the second section
    } else if (sectionIndex == 2) {
        //configure and return a layout for the third section
    } else if (sectionIndex == 3) {
        //configure and return a layout for the fourth section
    }
    return nil;
};


UICollectionViewCompositionalLayoutConfiguration *configuration = [[UICollectionViewCompositionalLayoutConfiguration alloc] init];
configuration.interSectionSpacing = 10;
configuration.scrollDirection = UICollectionViewScrollDirectionVertical;


self->_collectionViewLayout = [[UICollectionViewCompositionalLayout alloc] initWithSectionProvider:sectionProvider configuration:configuration];
Run Code Online (Sandbox Code Playgroud)

数据源定义

这是定义数据源的地方。每个部分都使用不同的数据模型类,因此我根据数据模型类的类型而不是索引路径来决定使用哪种类型的单元格。

self->_dataSource = [[UICollectionViewDiffableDataSource alloc] initWithCollectionView:self.collectionView cellProvider:^UICollectionViewCell * _Nullable(UICollectionView * _Nonnull collectionView, NSIndexPath * _Nonnull indexPath, id  _Nonnull item) {
    if ([item isKindOfClass:[MyFirstSectionModel class]]) {
        return [collectionView dequeueConfiguredReusableCellWithRegistration:firstSectionCellRegistration forIndexPath:indexPath item:item];
    } else if ([item isKindOfClass:[MySecondSectionModel class]]) {
        return [collectionView dequeueConfiguredReusableCellWithRegistration:secondSectionCellRegistration forIndexPath:indexPath item:item];
    } else if ([item isKindOfClass:[MyThirdSectionModel class]]) {
        return [collectionView dequeueConfiguredReusableCellWithRegistration:thirdSectionCellRegistration forIndexPath:indexPath item:item];
    } else if ([item isKindOfClass:[MyFourthSectionModel class]]) {
        return [collectionView dequeueConfiguredReusableCellWithRegistration:fourthSectionCellRegistration forIndexPath:indexPath item:item];
    }
    return nil;
}];
Run Code Online (Sandbox Code Playgroud)

快照构建

这里是包含每个部分(如果有数据)或排除(如果该部分为空)的位置。但是省略一个部分(例如,如果第三部分没有任何数据,那么它将被省略,但这将使第四部分的数据具有索引为 2 的索引路径,这将不适用于部分提供者。

如果我在快照中插入一个空部分,这仍然不起作用,因为其中一些部分具有标题,因此如果它是具有标题的部分,则标题仍将显示。但即使没有一个部分有标题,我认为它仍然会为该部分呈现一些额外的空白空间(但这可能是不正确的)。

- (void)reloadDataSourceAnimated:(BOOL)animated {
    NSDiffableDataSourceSnapshot<CICustomerReviewsSectionIdentifierType, CICustomerReviewsItemIdentifierType> *snapshot = [[NSDiffableDataSourceSnapshot alloc] init];
    
    
    if (self.firstSectionItems.count) {
        [snapshot appendSectionsWithIdentifiers:@[MyFirstSectionIdentifier]];
        [snapshot appendItemsWithIdentifiers:@[self.firstSectionItems] intoSectionWithIdentifier:MyFirstSectionIdentifier];
    }
    
    if (self.secondSectionItems.count) {
        [snapshot appendSectionsWithIdentifiers:@[MySecondSectionIdentifier]];
        [snapshot appendItemsWithIdentifiers:@[self.secondSectionItems] intoSectionWithIdentifier:MySecondSectionIdentifier];
    }
    
    if (self.thirdSectionItems.count) {
        [snapshot appendSectionsWithIdentifiers:@[MyThirdSectionIdentifier]];
        [snapshot appendItemsWithIdentifiers:@[self.thirdSectionItems] intoSectionWithIdentifier:MyThirdSectionIdentifier];
    }
    
    if (self.fourthSectionItems.count) {
        [snapshot appendSectionsWithIdentifiers:@[MyFourthSectionIdentifier]];
        [snapshot appendItemsWithIdentifiers:self.fourthSectionItems intoSectionWithIdentifier:MyFourthSectionIdentifier];
    }
    
    
    [self.dataSource applySnapshot:snapshot animatingDifferences:animated];
}
Run Code Online (Sandbox Code Playgroud)

概括

所以问题是,如果我的一个或多个部分没有数据,那么当它们被排除在快照之外时,这将导致后续部分的数据呈现在错误的部分中(因为部分提供程序基于部分配置上的索引和空部分之后的每个部分的indexPaths不再是原始的indexPath)。

问题

  1. 有没有办法让这些部分是可选的,并且不为“空”部分呈现任何常规视图和补充视图?

ifa*_*fau 2

我通过在应用数据源快照之前将集合视图数据分配给局部变量来解决这个问题。可以通过闭包访问此变量,UICollectionViewCompositionalLayoutSectionProvider以确定需要为给定索引返回哪个布局。

例子

让我们采用这个数据模型:

struct ViewControllerData {
    let texts: [String]
    let colors: [UIColor]
    let numbers: [Int]
}
Run Code Online (Sandbox Code Playgroud)

集合视图数据源定义:

enum Section: Hashable {
    case first
    case second
    case third
}

enum SectionData: Hashable {
    case text(String)
    case color(UIColor)
    case number(Int)
}

lazy var datasource: UICollectionViewDiffableDataSource<Section, SectionData> = {
    
    let dataSource = UICollectionViewDiffableDataSource<Section, SectionData>(collectionView: self.collectionView) { [weak self] (collectionView, indexPath, data) -> UICollectionViewCell? in
        
        switch data {
        case .text(let text):
            let cell = collectionView.dequeueReusableCell(withReuseIdentifier: TextCollectionViewCell.reuseIdentifier, for: indexPath) as? TextCollectionViewCell
            cell?.textLabel.text = text
            return cell
            
        case .color(let color):
            let cell = collectionView.dequeueReusableCell(withReuseIdentifier: ColorCollectionViewCell.reuseIdentifier, for: indexPath) as? ColorCollectionViewCell
            cell?.colorView.backgroundColor = color
            return cell
            
        case .number(let number):
            let cell = collectionView.dequeueReusableCell(withReuseIdentifier: NumberCollectionViewCell.reuseIdentifier, for: indexPath) as? NumberCollectionViewCell
            cell?.numberLabel.text = "\(number)"
            return cell
        }
    }
    
    dataSource.supplementaryViewProvider = ...

    return dataSource
}()
Run Code Online (Sandbox Code Playgroud)

配置 diffable 快照,排除没有数据的部分,并将模型数据分配给局部变量:

private var currentData: ViewControllerData?

public func showData(_ data: ViewControllerData) {
    
    self.currentData = data
    
    var snapshot = NSDiffableDataSourceSnapshot<Section, SectionData>()
    
    if !data.texts.isEmpty {
        snapshot.appendSections([.first])
        snapshot.appendItems(data.texts.map { SectionData.text($0 )}, toSection: .first)
    }
    
    if !data.colors.isEmpty {
        snapshot.appendSections([.second])
        snapshot.appendItems(data.colors.map { SectionData.color($0) }, toSection: .second)
    }
    
    if !data.numbers.isEmpty {
        snapshot.appendSections([.third])
        snapshot.appendItems(data.numbers.map { SectionData.number($0) }, toSection: .third)
    }
    
    datasource.apply(snapshot, animatingDifferences: true)
}
Run Code Online (Sandbox Code Playgroud)

使用此变量提供正确的部分布局:

lazy var collectionViewLayout: UICollectionViewLayout = {
    
    let layout = UICollectionViewCompositionalLayout { [weak self] (sectionIndex, layoutEnvironment) -> NSCollectionLayoutSection? in
        
        guard let section = self?.currentData?.visibleSection(at: sectionIndex) else { return nil }
        
        switch section {
        case .first:
            let section = ...
            return section

        case .second:
            let header = ...
            let section = ...
            section.boundarySupplementaryItems = [header]
            return section
            
        case .third:
            let section = ...
            return section
        }
    }
    
    return layout
}()
Run Code Online (Sandbox Code Playgroud)

visibleSection(at index:)是为了方便起见的扩展ViewControllerData

extension ViewControllerData {
    
    var visibleSections: [ViewController.Section] {

        var sections: [ViewController.Section] = []
        if !texts.isEmpty { sections.append(.first) }
        if !colors.isEmpty { sections.append(.second) }
        if !numbers.isEmpty { sections.append(.third) }
        
        return sections
    }
    
    func visibleSection(at index: Int) -> ViewController.Section? {
        guard visibleSections.indices.contains(index) else { return nil }
        return visibleSections[index]
    }
}
Run Code Online (Sandbox Code Playgroud)

该变量也可以在集合视图数据源中使用,以提供补充视图:

dataSource.supplementaryViewProvider = { [weak self] (collectionView, kind, indexPath) in
    
    guard let section = self?.currentData?.visibleSection(at: indexPath.section) else { return nil }
    
    switch section {
    case .second:
        let header = collectionView.dequeueReusableSupplementaryView(ofKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: HeaderView.reuseIdentifier, for: indexPath) as? HeaderView
        header?.textLabel.text = "Colors section header"
        return header

    default: return nil
    }
}
Run Code Online (Sandbox Code Playgroud)

结果:

集合视图无

  • 我不明白在经常发生的情况下,这怎么会如此复杂。组合布局和快照应该通过将布局与对象类型相关联来轻松处理这个问题。 (2认同)