UPGRADE YOUR SKILLS: Learn advanced Swift and SwiftUI on Hacking with Swift+! >>

SOLVED: UITableView DiffableDataSource Headers.

Forums > iOS

Hey guys. I am trying to work out how to add headers to my sections in my uitableview. I have seen a post from here about that and the guy had a solutions. But it doesnt work for me tho. I have created an enum for sections:

enum Section: String, Hashable {
        case sectionA = "Section A"
        case sectionB = "Section B"
    }

Then I have created UITableViewHeaderFooterView

class SectionHeaderView: UITableViewHeaderFooterView {

    var title = CustomSecondaryTitleLabel(with: "Section A")

    static let reuseId = "HeaderView"

    override init(reuseIdentifier: String?) {
        super.init(reuseIdentifier: reuseIdentifier)
        configure()
    }

    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    private func configure() {
        addSubview(title)

        NSLayoutConstraint.activate([
            title.topAnchor.constraint(equalTo: topAnchor),
            title.leadingAnchor.constraint(equalTo: leadingAnchor),
            title.trailingAnchor.constraint(equalTo: trailingAnchor),
            title.bottomAnchor.constraint(equalTo: bottomAnchor)
        ])
    }
}

At last, I used viewForHeaderInSection

extension ListVC: UITableViewDelegate {
    func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
        let cell = tableView.dequeueReusableHeaderFooterView(withIdentifier: SectionHeaderView.reuseId) as! SectionHeaderView
        return cell
    }

But when I run the program and create some cell with my action button into TableView let cell = tableView.dequeueReusableHeaderFooterView(withIdentifier: SectionHeaderView.reuseId) as! SectionHeaderView Thread 1: Fatal error: Unexpectedly found nil while unwrapping an Optional value

It's my third month into programming so I am gonna be honest. I would highly appriciate some slow explanation what am I doing wrong and what could I do better.

Let me explain the app exactly. It's a simple Note app. UITableView has two sections. By filling the textField and clicking an actionButton, program creates a cell with that string and update the first section of the table view. Before creating viewForHeaderInSection everyhting worked just fine. Now it does not. I would like to know how to create two headers with the two different names for the section1 and section2. Many thanks for any help and your own time. :) App is made fully programatically not storyboard and for the purpose of learning I'd like to keep this way

3      

Hi,

I think you are missing the code to register the header class with the TableView. Without the registration TableViews does. ot know about it.

4      

Hey Filip. You were right I did miss that in my collectionView configuration. That however did not solve the issue entirely. It seems like the headerViewCell is indeed registered as program proceed and does not crush. However that cell did not appear in my tableView.

func configureTableView() {
        view.addSubview(noteTableView)
        noteTableView.translatesAutoresizingMaskIntoConstraints = false
        noteTableView.register(CustomNoteCell.self, forCellReuseIdentifier: CustomNoteCell.reuseId)
        noteTableView.register(SectionHeaderView.self, forHeaderFooterViewReuseIdentifier: SectionHeaderView.reuseId)
        noteTableView.delegate = self
        noteTableView.rowHeight = 80
        noteTableView.sectionHeaderHeight = 50

Also, Xcode reccomend me to make that function private and I am not sure why:

 func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: CustomNoteCell.reuseId) as! CustomNoteCell
        cell.set(with: notes[indexPath.row])
        return cell

    }

Any suggestion what else may be wrong?

Is my diffableDataSource missing something?

func configureDataSource() {
        dataSource = UITableViewDiffableDataSource<Section, String>(tableView: noteTableView, cellProvider: { (tableView, indexPath, notes) -> UITableViewCell? in
            let cell = tableView.dequeueReusableCell(withIdentifier: CustomNoteCell.reuseId, for: indexPath) as! CustomNoteCell
            cell.set(with: self.notes[indexPath.row])
            return cell
        })

    }

    func updateData(on notes: [String]) {
        var snapshot = NSDiffableDataSourceSnapshot<Section,String>()
        snapshot.appendSections([.sectionA, .sectionADone, .sectionB, .sectionBDone])
        snapshot.appendItems(notes, toSection: .sectionA)

        DispatchQueue.main.async { self.dataSource.apply(snapshot, animatingDifferences: true) }
    }

3      

I missed that you have a diffable data source, you need to set the supplementaryViewProvider method on the data source. The method you have is for older collectionView or tableView implementations that dont use diffable.

Also I have this open source project on GitHub which shows how to use Diffable with section headers. Might be useful to take a look :-)

3      

Hey, thanks for help. I have checked out your app and trynna figure out what is what. However I have noticed supplementaryViewProvider conforms tu uicollectionviewdiffabledatasource but it seems like it doesnt to UITableView. Have you got some suggestions for UITableView? thanks

3      

Hey. From what you gave me and from what I found out, I have managed to create Cells that appears on my tableView. To clarify, I am sending what I have left in a code and it touches the issue.

 func configureDataSource() {
        dataSource = UITableViewDiffableDataSource<Section, String>(tableView: noteTableView, cellProvider: { (tableView, indexPath, notes) -> UITableViewCell? in
            let cell = tableView.dequeueReusableCell(withIdentifier: CustomNoteCell.reuseId, for: indexPath) as! CustomNoteCell
            cell.set(with: self.notes[indexPath.row])
            return cell
        })
    }

    func updateData(on notes: [String]) {
        var snapshot = NSDiffableDataSourceSnapshot<Section,String>()
        snapshot.appendSections([.sectionA, .sectionADone, .sectionB, .sectionBDone])
        snapshot.appendItems(notes, toSection: .sectionA)

        DispatchQueue.main.async { self.dataSource.apply(snapshot, animatingDifferences: true) }
    }

and extension on UITableViewDelegate

extension ListVC: UITableViewDelegate {

    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        let note = notes[indexPath.row]
        let destVC = DetailedNoteVC()
        destVC.note = note
        let navbar = UINavigationController(rootViewController: destVC)
        present(navbar, animated: true)
    }

    func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {  //castt sectionHeaderView as cell for Header and creates it
        let cell = tableView.dequeueReusableHeaderFooterView(withIdentifier: SectionHeaderView.reuseId) as! SectionHeaderView
        return cell
    }

    func numberOfSections(in tableView: UITableView) -> Int {
        sections.count
    }
}

Like I said before, sadly it seems like the mothed that you have used in your UICollectionView, supplementaryViewProvider, does not conform to UITableView. From what You can see here. How can I give those headers (there are 4 headers that I would like to display) different names? I higly appriciate your help man!

What I could do actually is to create UICollectionView with diffable data source instead. But now since I digged into it, I wonder how to do what I am trying to accomplish with UITableView

3      

You are right, apologies I forgot that TableView is quite different when it comes to Diffable and header views. Header views are responsibility of the UITableViewDelegate so they are entirely separate from the Diffable part. Which you already have implemented.

The delegate method gives you section parameter. You can use this, to get the section object from the data source:

let sectionObject = datasource.snapshot().sectionIdentifiers[section]

And based on this you can use switch to configure different headers.

4      

Solutions: I have decided to make it in a slightly modern style, following an example code of Namecek-Filip from his Git. Here is the solutions for those whom may be concerned ;)

I have created an UICollectionView with the createNSCollectionLayout that returns UICollectionViewLayout (which I will describe below) and registered custom UICollectionViewCell and custom UICollectionReusableView as a header.

private func configureCollectionView() {
        collectionView = UICollectionView(frame: view.bounds, collectionViewLayout: createNSCollectionLayout())
        view.addSubview(collectionView)
        collectionView.backgroundColor = .systemBackground

        collectionView.register(TwitTwitsCell.self, forCellWithReuseIdentifier: TwitTwitsCell.reuseId)

        collectionView.register(UserInfoCollectionHeaderView.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: UserInfoCollectionHeaderView.reuseId)

    }

Here is the function that creates UICollectionViewLayout. First Ive created an item and described its size as 100% and configured Insets.

Second, Ive created a group and described it width as 100% and estimated of 100 points. I ve added to that group one subitem (Each group is going to have one item)

Third, Ive created a section and added the group to it. I have also added a header with the function addHeader (explained below)

Lastly Ive created instance of the layout with the previously created section and returned it.

 private func createNSCollectionLayout() -> UICollectionViewLayout {
        let itemSize        = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0),
                                                     heightDimension: .fractionalHeight(1.0))

        let item            = NSCollectionLayoutItem(layoutSize: itemSize)
        item.contentInsets  = NSDirectionalEdgeInsets(top: 5, leading: 5, bottom: 5, trailing: 5)

        let groupSize       = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0),
                                                     heightDimension: .estimated(100))

        let group           = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item])

        let section         = NSCollectionLayoutSection(group: group)

        addHeader(to: section)

        let layout = UICollectionViewCompositionalLayout(section: section)

        return layout
    }

Following Nemecek example I have created a funcion that will add a header into the specified section. Ive described header size to be 100% of the width and estimated 200. combined with my cell, estimated value makes the header looks perfect on all screen sizes. Ive created an instance of NSCollectionLayoutBoundarySupplementaryItem, I specified the size, added my custom ReusableView and aligned it to the top of the section.

private func addHeader(to section: NSCollectionLayoutSection) {
        let headerSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .estimated(200))
        let headerElement = NSCollectionLayoutBoundarySupplementaryItem(layoutSize: headerSize, elementKind: UserInfoCollectionHeaderView.reuseId, alignment: .top)
        section.boundarySupplementaryItems = [headerElement]
    }

Hope it will help anyone who also learns how the stuff is done. If anyone has any suggestions according to my code, dont hesitate to repospond so I can make some fixes. :))

4      

Hacking with Swift is sponsored by RevenueCat

SPONSORED Take the pain out of configuring and testing your paywalls. RevenueCat's Paywalls allow you to remotely configure your entire paywall view without any code changes or app updates.

Learn more here

Sponsor Hacking with Swift and reach the world's largest Swift community!

Archived topic

This topic has been closed due to inactivity, so you can't reply. Please create a new topic if you need to.

All interactions here are governed by our code of conduct.

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.