Некорректно работает прикрепление заголовка секции в compositional layout

uicollectionview

#1

Когда я создал заголовок раздела, я поместил туда collectionView и хочу, чтобы он прикреплялся к навигационной панели при прокрутке, я сделал это через pinToVisibleBounds. Заголовок прикрепляется, как я хотел, но при прокрутке, проходя через 1 ячейку, он отсоединяется и покидает экран.
Это код для создание заголовка секции.
import UIKit

class SecondSectionHeader: UICollectionReusableView {

@IBOutlet weak var collectionView: UICollectionView!
static let reuseId = "SecondSectionHeader"
fileprivate var sections: [Section] = Bundle.main.decode([Section].self, from: "model.json").filter { section in
    return section.type != "Names" && section.type != "Numbers"
}
fileprivate var indexPathOfSelectedCell: IndexPath?
override func awakeFromNib() {
    super.awakeFromNib()
    configureCollectionView()
}

private func configureCollectionView() {
    collectionView.delegate = self
    collectionView.dataSource = self
    collectionView.register(UINib(nibName: "EmojiCell", bundle: nil), forCellWithReuseIdentifier: EmojiCell.reuseId)
    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.reloadData()
}
}
//MARK:- UICollectionViewDataSource
extension SecondSectionHeader: UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
    guard let countOfCells = sections.first?.items.count else { return 0 }
    return countOfCells
}

func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
    guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: EmojiCell.reuseId, for: indexPath) as? EmojiCell else { return UICollectionViewCell()}
    guard let item = sections.first?.items[indexPath.item] else { return UICollectionViewCell()}
    cell.configureCell(item: item)
    return cell
}

}
//MARK:- UICollectionViewDelegate

extension SecondSectionHeader:UICollectionViewDelegate {
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
    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)
    }
    collectionView.scrollToItem(at: indexPath, at: .left, animated: true)
    indexPathOfSelectedCell = indexPath
}

}

Код для создания секции с заголовком.

  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]
        sectionBackgroundDecoration.zIndex = 1
        print(sectionBackgroundDecoration.zIndex)
        print(header.zIndex)
        return section
    }

Функция, которая создает заголовок.

     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
        }


#2

У вас скорее всего неправильно организованы данные в датасорсе. По виду получается, что каждое имя - это секция. И хедер прикреплён к первой секции и когда она начинается скрываться, то хедер уходит, потому что подходит следущая. Вам нужно все имена поместить в одну секцию с одним заголовком. Обычно для такой задачи лучшим решением бывает коллекция с коллекцией данных, что-то типа: [SectionHeaderData: [sectionData]] ну или любые вложенные массивы данных.


#3

Это код, при помощи которого создается datasource. Я вывел в консоль для какого indexPath создается заголовок (скрин после кода).

//MARK:- create data sourse
extension ViewController {
private func configureDataSource() {
    
    dataSource = UICollectionViewDiffableDataSource<Section, Item>(collectionView: collectionView) { [weak self]
        (collectionView: UICollectionView, indexPath: IndexPath, item: Item) -> UICollectionViewCell? in
        switch self?.sections[indexPath.section].type {
        case "Numbers":
            guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: NumberCell.reuseId, for: indexPath) as? NumberCell else {return nil}
            cell.configureCell(item: item)
            return cell
        case "Names":
            guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: StringCell.reuseId, for: indexPath) as? StringCell else {return nil}
            cell.configureCell(item: item)
            print("section with indexPath: \(indexPath)")
            return cell
        default:
            print("There is no such item")
            return nil
        }
    }
    
    
    dataSource.supplementaryViewProvider = {
        collectionView, kind, indexPath in
        print("kind \(kind) for indexPath: \(indexPath)")
        
            guard let sectionHeader = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: SecondSectionHeader.reuseId, for: indexPath) as? SecondSectionHeader else { return nil }
            return sectionHeader
        
    }
}

Я правильно понимаю, что то в таком случае заголовок прикрепляется только к первой ячейке?

P.S.
И хотелось бы понять, это нормально что принты в таком странном порядке выводятся?


#4

Ну всё верно: у вас первая секция с индексом 0 пустая, вот она и прячется. А вторая с индексом 1 - это похоже там где имена. А принтится в таком порядке, потому что ячейки переиспользуются и порядок зависит от скролла таблицы (это нормально :slight_smile: ).


#5

Первая секция и должна прятаться, у меня получается так что в заголовке секции под индексом 1 (с именами которая) есть collectionView и при скроле он открепляется от navigation bar, почему то …