Search code examples
swiftxcodeuicollectionviewuibezierpathuicollectionviewcompositionallayout

How to modify item shape of specific group when using UICollectionViewCompositionalLayout


I am making a collage app for that I will be using multiple grid layouts.I am using UICollectionViewCompositionalLayout to create grids. One of my grids requires a heart shaped cell in between. Following is my code the HeartShaped item needs to be heartShape I've no idea how to do it at first I thought of using UIBeizerPath but item is not type of layer and if somehow I manage to do it inside cellForItem still I've many more grids coming and I will have to use "if" "else" again and again. Another problem I am facing my last group i.e the bottom group is not displaying.

Any help would be really appreciated.

                let topItem = NSCollectionLayoutItem(layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(1/3), heightDimension: .fractionalHeight(1)))
                
//                top items group
                let topGroup = NSCollectionLayoutGroup.horizontal(layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .fractionalHeight(1/4)), subitem: topItem, count: 3)
                topGroup.contentInsets = .init(top: 0, leading: 3, bottom: 0, trailing: 3)
                topGroup.interItemSpacing = .fixed(2)

//                middle right item
                let middleRightItem = NSCollectionLayoutItem(layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(1/4), heightDimension: .fractionalHeight(1)))

//                 middle left item
                let middleLeftItem = NSCollectionLayoutItem(layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(1/4), heightDimension: .fractionalHeight(1)))

//                Heart Shaped
                let heartShapedItem = NSCollectionLayoutItem(layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(1/2), heightDimension: .fractionalHeight(1)))

//                 middle three items group
                let middleGroup = NSCollectionLayoutGroup.horizontal(layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .fractionalHeight(1/3)), subitems: [middleRightItem,heartShapedItem,middleLeftItem])
                middleGroup.contentInsets = .init(top: 0, leading: 3, bottom: 0, trailing: 3)
                middleGroup.interItemSpacing = .fixed(2)

//            bottom three items group
                let bottomGroup = NSCollectionLayoutGroup.horizontal(layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .fractionalHeight(1/4)), subitem: topItem, count: 3)
                
                let finalGroup = NSCollectionLayoutGroup.vertical(layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .fractionalHeight(1)), subitems: [topGroup,middleGroup,bottomGroup])
                finalGroup.interItemSpacing = .fixed(2)
                
                let section = NSCollectionLayoutSection(group: finalGroup)
                
                return section

Solution

  • A collection view layout defines the visual arrangement of the content in your collection - thus in your UICollectionViewCompositionalLayout you specify such parameters of your collection view like number of items in a row, sizes and offsets, orthogonal scrolling behavior, headers and decoration items, not how a particular cell looks like.

    In cellForItem you could customize your cells including its layers - you could modify shapes there. Cells will be arranged in the collection view based on parameters of your UICollectionViewCompositionalLayout.

    If you are using UICollectionViewDiffableDataSource, you could differentiate between different type of cells (i.e. cells with a heart shape and a circle shape) with a following code example:

    func configureDataSource() {
        dataSource = DataSource(collectionView: collectionView) { [self] (collectionView: UICollectionView, indexPath: IndexPath, item: ViewCell) -> UICollectionViewCell? in
            switch item.type {
            case .heart:  return hearCell(for: indexPath)
            case .circle: return circleCell(for: indexPath)
            default:      return normalCell(for: indexPath)
            }
        }
    }
    

    In custom struct ViewCell I have an enum for CellType:

    struct ViewCell: Hashable {
        var tag: Int
        var type: CellType
    }
    
    enum CellType { case heart, circle }
    

    Regarding the problem with the bottom group - do you have enough items in your collection view to fill all the groups?

    UPDATE:

    The problem in your layout is with this line:

    middleGroup.interItemSpacing = .fixed(2)
    

    With it 3 rows in the middle group can't be placed in one row. You could remove it and achieve the same spacing by adding insets to the middle item:

    heartShapedItem.contentInsets = .init(top: 0, leading: 2, bottom: 0, trailing: 2)
    

    Since you are not using UICollectionViewDiffableDataSource, you could differentiate your cells by indexPath. So if you have just 1 section, your heartShape item will have indexPath.row of 4:

    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: MyCollectionViewCell.reuseID, for: indexPath) as? MyCollectionViewCell else { return UICollectionViewCell() }
    
        cell.label.text = "\(indexPath.row)"
        cell.label.textAlignment = .center
        cell.backgroundColor = .green
        
        if indexPath == IndexPath(row: 4, section: 0) {
            cell.label.text = "HEART"
        }
        
        return cell
    }
    

    enter image description here

    (in your original code there is not spacing between items in the bottom group, I don't know if it was done intentionally or not)