Search code examples
swiftuicollectionviewcompositionallayoutuicollectionviewdiffabledatasource

Show different layout section if collection view section has no data to display


I am trying to implement a collection view that consists of the following sections:

User section: Showing the user's profile info (avatar, email, etc)

Children section: Showing the user's children (if available)

Statistics section: Showing the user's statistics (if available)

The problem I face

I cannot figure out how to show a placeholder cell in case the user has no children or no statistics.

What I can do

Hide the sections (children, statistics) if there is no data.

What I want

Show an empty section with a placeholder if no children

Show an empty section with a placeholder if there are no statistics

This is what I tried so far

enum UserProfileSection {
    case user
    case children
    case statistics
}
        let layout = UICollectionViewCompositionalLayout(sectionProvider: { sectionNumber, env in
            let sectionIdentifier = self.dataSource.snapshot().sectionIdentifiers[sectionNumber]
            
            if sectionIdentifier == .user {
                return UserProfileVC.userSection()
            } else if (sectionIdentifier == .children) {
                if let userChildren = self.userProfileSectionData?.children, !userChildren.isEmpty {
                    return UserProfileVC.childrenSection()
                } else {
                    return UserProfileVC.emptyChildrenSection()
                }
            } else if (sectionIdentifier == .statistics) {
                if let stats = self.userProfileSectionData?.stats, !stats.isEmpty {
                    return UserProfileVC.statsSection()
                } else {
                    return UserProfileVC.emptyStatsSection()
                }
            } else {
                return nil
            }
        })
        
        self.collectionView.setCollectionViewLayout(layout, animated: true)
      
       Firebase.Database.database().fetchLoggedInUserProfileData { userProfileSectionData in
    
            self.userProfileSectionData = userProfileSectionData
            
            var snapshot = NSDiffableDataSourceSnapshot<UserProfileSection, AnyHashable>()
            
            
            if let _ = userProfileSectionData.settings?.uid {
                snapshot.appendSections([.user])
                snapshot.appendItems([userProfileSectionData.user], toSection: .user)
            }
            
            snapshot.appendSections([.children])
            snapshot.appendItems(userProfileSectionData.children ?? [], toSection: .children)

            snapshot.appendSections([.statistics])
            snapshot.appendItems(userProfileSectionData.statistics ?? [], toSection: .statistics)

            self.dataSource.apply(snapshot, animatingDifferences: animated)
        }   

The problem is that the snapshot will not contain any children or statistics data if I return an empty array from the Firebase call. So the sections .children and/or .statistics will be hidden.

The user has no children linked. I want to insert a different layout section in case the user has no children; not hide it.

Any idea how to achieve this? Thanks in advance.


Solution

  • You can't really show a section without any items, it has to have at least one item to display. Since there's a possibility that you don't receive any children or statistics, you have to give something to your data source to show what you want.

    I'd start with defining additional cases for your section enum. Let's name them childrenPlaceholder and statisticsPlaceholder. This would allow your layout object properly select a NSLayoutSection based on your code.

    Then, in your response you have to do some modifications:

    if let children = userProfileSectionData.children, !children.isEmpty {
        snapshot.appendSections([.children])
        snapshot.appendItems(children, toSection: .children)
    } else {
        snapshot.appendSections([.childrenPlaceholder])
        snapshot.appendItems([/*your model that describes a placeholder*/], toSection: .children)
    }
    
    if let statistics = userProfileSectionData.statistics, !statistics.isEmpty {
        snapshot.appendSections([.statistics])
        snapshot.appendItems(statistics, toSection: .statistics)
    } else {
        snapshot.appendSections([.statisticsPlaceholder])
        snapshot.appendItems([/*your model that describes a placeholder*/], toSection: .statistics)
    }
    

    This leads of course to some changes to your layout code:

    switch sectionIdentifier {
    case .user:
        return UserProfileVC.userSection()
    case .children:
        return UserProfileVC.childrenSection()
    case .childrenPlaceholder:
        return UserProfileVC.emptyChildrenSection()
    case .statistics:
        return UserProfileVC.statsSection()
    case .statisticsPlaceholder:
        return UserProfileVC.emptyStatsSection()
    }
    

    Hope this helps!