Egor
Egor

Reputation: 1

The header section in UICollectionView when using UIColelctionViewCompositionalLayout is not attached to the top of the screen

When I created the section header I put collectionView there and I want it to attach to the navigation bar when scrolling, I did this through pinToVisibleBounds. The header is attached as I wanted, but when scrolling, going through 1 cell it detaches and leaves the screen.

SectionHeader.swift

class SectionHeader: UICollectionReusableView {
    
    static let reuseId = "SectionHeader"
    var sections: [Section] = Bundle.main.decode([Section].self, from: "model.json").filter { section in
        return section.type != "Names" && section.type != "Numbers"
    }
    
    private var indexPathOfSelectedCell: IndexPath?
    
    fileprivate var collectionView: UICollectionView! = nil
    fileprivate var dataSource: UICollectionViewDiffableDataSource<Section, Item>! = nil
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        
        cofigureCollectionView()
        setupConstraints()
        createDataSource()
        reloadData()
        collectionView.delegate = self
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    //MARK:- Configure collection view
    private func cofigureCollectionView() {
        collectionView = UICollectionView(frame: self.bounds, collectionViewLayout: createLayout())
        collectionView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
        collectionView.layer.shadowOffset = CGSize(width: collectionView.frame.width, height: collectionView.frame.height)
        collectionView.backgroundColor = #colorLiteral(red: 0.880524771, green: 0.9031272657, blue: 0.875127862, alpha: 1)
        collectionView.alpha = 1
        collectionView.register(UINib(nibName: "EmojiCell", bundle: nil), forCellWithReuseIdentifier: EmojiCell.reuseId)
        addSubview(self.collectionView)
    }
    
    
    //MARK: - Create layout
    private func createLayout() -> UICollectionViewLayout {
        let layout = UICollectionViewCompositionalLayout {[weak self](sectionIndex: Int,
                                                                      layoutEnvironment: NSCollectionLayoutEnvironment) -> NSCollectionLayoutSection? in
            guard let section = self?.sections[sectionIndex] else { return nil }
            switch section.type {
            case "Emoji":
                return self?.createEmojiSection()
            default:
                print("There is no such item")
                return nil
            }
        }
        
        return layout
    }
    
    //MARK:- Create section
    private func createEmojiSection() -> NSCollectionLayoutSection {
        let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .fractionalHeight(1))
        let item = NSCollectionLayoutItem(layoutSize: itemSize)
        item.contentInsets = NSDirectionalEdgeInsets(top: 0, leading: 8, bottom: 8, trailing: 8)
        let groupSize = NSCollectionLayoutSize(widthDimension: .estimated(120), heightDimension: .estimated(50))
        let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item])
        let section = NSCollectionLayoutSection(group: group)
        section.orthogonalScrollingBehavior = .continuous
        section.contentInsets = NSDirectionalEdgeInsets(top: 10, leading: 12, bottom: 0, trailing: 12)
        return section
    }
    
    //MARK:- setup constraints
    private func setupConstraints() {
        collectionView.translatesAutoresizingMaskIntoConstraints = false
        let inset = CGFloat(10)
        NSLayoutConstraint.activate([
            self.collectionView.topAnchor.constraint(equalTo: self.topAnchor),
            self.collectionView.trailingAnchor.constraint(equalTo: self.trailingAnchor),
            self.collectionView.leadingAnchor.constraint(equalTo: self.leadingAnchor),
            self.collectionView.bottomAnchor.constraint(equalTo: self.bottomAnchor, constant: -inset)
        ])


    }
    
    //MARK:- Create data source
    private func createDataSource() {
        dataSource = UICollectionViewDiffableDataSource<Section, Item>(collectionView: collectionView) { [weak self]
            (collectionView: UICollectionView, indexPath: IndexPath, item: Item) -> UICollectionViewCell? in
            switch self?.sections[indexPath.section].type {
            case "Emoji":
                guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: EmojiCell.reuseId, for: indexPath) as? EmojiCell else {return nil}
                cell.configureCell(item: item)
                return cell
            default:
                print("There is no such item")
                return nil
            }
        }
    }
    
    //MARK:- Reload data
    
    private func reloadData() {
        var snapshot = NSDiffableDataSourceSnapshot<Section, Item>()
        snapshot.appendSections(sections)
        
        for section in sections {
            if section.type == "Emoji" {
                snapshot.appendItems(section.items, toSection: section)
            }
        }
        
        dataSource?.apply(snapshot)
    }
}
//MARK:- CollectionView delegate
extension SectionHeader: UICollectionViewDelegate {
    func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
        guard let firstItem = self.dataSource.itemIdentifier(for: indexPath) else { return }
        let section = self.dataSource.snapshot().sectionIdentifier(containingItem: firstItem)
        switch section?.type {
        case "Emoji":
            let cell = collectionView.cellForItem(at: indexPath)
            NotificationCenter.default.post(name: Notification.Name("SelectedIndexPath"), object: nil, userInfo: ["indexPath":indexPath])
            if let previousIndex = indexPathOfSelectedCell {
                let previousCell = collectionView.cellForItem(at: previousIndex)
                previousCell?.backgroundColor = .clear
                cell?.backgroundColor = #colorLiteral(red: 0.3647058904, green: 0.06666667014, blue: 0.9686274529, alpha: 1)
            } else {
                cell?.backgroundColor = #colorLiteral(red: 0.3647058904, green: 0.06666667014, blue: 0.9686274529, alpha: 1)
            }
            
            indexPathOfSelectedCell = indexPath
        default:
            break
        }
    }
}

Section code

 private func createStringSection() -> NSCollectionLayoutSection {
        let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .fractionalHeight(1.0))
        let item = NSCollectionLayoutItem(layoutSize: itemSize)
        let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .fractionalHeight(0.2))
        let group = NSCollectionLayoutGroup.vertical(layoutSize: groupSize, subitems: [item])
        let section = NSCollectionLayoutSection(group: group)
        let sectionBackgroundDecoration = NSCollectionLayoutDecorationItem.background(
            elementKind: ViewController.sectionBackgroundDecorationElementKind)
        sectionBackgroundDecoration.contentInsets = NSDirectionalEdgeInsets(top: 80, leading: 0, bottom: 0, trailing: 0)
        section.decorationItems = [sectionBackgroundDecoration]
        let header = createHeader()
        section.boundarySupplementaryItems = [header]
        return section
    }

Creating header.

private func createHeader() -> NSCollectionLayoutBoundarySupplementaryItem {
        let headerSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .estimated(80))
        let header = NSCollectionLayoutBoundarySupplementaryItem(layoutSize: headerSize, elementKind: UICollectionView.elementKindSectionHeader, alignment: .top)
        header.pinToVisibleBounds = true
        header.zIndex = 2
        return header
    }

I would be very grateful for help, I sit for 4 days can not understand why so.

Here's a gif of what it looks like.

Upvotes: 0

Views: 1614

Answers (1)

Viktor Gavrilov
Viktor Gavrilov

Reputation: 976

If you want to have a header which is attached to all sections, you should add it to the whole UICollectionViewCompositionalLayout, not to a particular section.

Create an instance of UICollectionViewCompositionalLayoutConfiguration() and add your header items to it:

private func createCompositionalLayout() -> UICollectionViewLayout {
    let layout = UICollectionViewCompositionalLayout { [self] sectionIndex, layoutEnvironment in
        // your code for section
    }
        
    let headerSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0),heightDimension: .estimated(44))
    let header = NSCollectionLayoutBoundarySupplementaryItem(layoutSize: headerSize, elementKind: "header", alignment: .top)
    header.pinToVisibleBounds = true
    
    let config = UICollectionViewCompositionalLayoutConfiguration()
    config.boundarySupplementaryItems = [header]
    
    layout.configuration = UICollectionViewCompositionalLayoutConfiguration()
    
    return layout
}

Upvotes: 1

Related Questions