【问题标题】:UICollectionViewCompositionalLayout optional sectionsUICollectionViewCompositionalLayout 可选部分
【发布时间】:2022-07-15 22:05:18
【问题描述】:

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


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

代码

布局定义

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

每个部分都有不同的项目大小,有些是正交滚动部分。所以如果第四节的数据使用第三节的布局来渲染,那么它会看起来不对。

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];

数据源定义

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

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

快照构建

这里是包含每个部分(如果有数据)或排除(如果该部分为空)的位置。但是留下一个部分(例如,如果第三部分没有任何数据,那么它将被遗漏,但这将使第四部分的数据具有索引为 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];
}

总结

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

问题

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

【问题讨论】:

  • 你找到答案了吗?
  • 是的,你是怎么解决这个问题的?

标签: ios uicollectionview uicollectionviewcompositionallayout uicollectionviewdiffabledatasource nsdiffabledatasourcesnapshot


【解决方案1】:

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

示例

让我们来看看这个数据模型:

struct ViewControllerData {
    let texts: [String]
    let colors: [UIColor]
    let numbers: [Int]
}

集合视图数据源定义:

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

配置 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)
}

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

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

为方便起见,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]
    }
}

这个变量也可以用在collection view数据源中,提供补充视图:

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

结果:

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-16
    • 2021-02-23
    • 1970-01-01
    • 1970-01-01
    • 2022-06-23
    • 1970-01-01
    相关资源
    最近更新 更多