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

SOLVED: UITableView Diffable data source section headers && Putting data source outside viewcontroller

Forums > iOS

I have a UITableView with a diffable datasource and for the life of me I can't seem to work out how to get headers for my sections. The sections are there, as well as the empty header cells. Bit how do I populate the headers with my custom type AnnotationType?

Related, how do I properly refactor my ViewController to have the data source ij it's own seperate class?

My enum (for the sections):

class AnnotationsController {
    enum AnnotationType: String, Codable, Hashable, CaseIterable {
        case tips, poi

        var image: UIImage {
            switch self {
            case .tips:
                return ProjectImages.annotationTypeTips
            case .poi:
                return ProjectImages.annotationTypePoi
            }
        }
    }
    (...)
}

My viewcontroller:

class AnnotationsViewController: UIViewController {
    typealias Annotation = AnnotationsController.Annotation
    typealias AnnotationType = AnnotationsController.AnnotationType

    let annotationsController = AnnotationsController(with: ProjectsController.activeProject!)

    var tableView: UITableView!
    var dataSource: UITableViewDiffableDataSource<AnnotationType, Annotation>!

    override func viewDidLoad() {
        super.viewDidLoad()

        configureViewController()
        configureTableView()
        createDataSource()
        updateData(on: annotationsController.filteredAnnotations())
        configureSearchController()
    }

    (...)

    func createDataSource() {
        dataSource = UITableViewDiffableDataSource<AnnotationType, Annotation>(tableView: tableView) { (tableView, indexPath, annotation) in
            return self.configure(AnnotationCell.self, with: annotation, for: indexPath)
        }
    }

    func updateData(on annotations: [Annotation]) {
        var snapshot = NSDiffableDataSourceSnapshot<AnnotationType, Annotation>()
        //Append available sections
        AnnotationType.allCases.forEach { snapshot.appendSections([$0]) }

        //Append annotations to their corresponding sections
        annotations.forEach { (annotation) in
            snapshot.appendItems([annotation], toSection: annotation.type as AnnotationType)
        }

        //Force the update on the main thread to silence a warning about tableview not being in the hierarchy!
        DispatchQueue.main.async {
            self.dataSource.apply(snapshot, animatingDifferences: true)
        }
    }

    ///Configure any type of cell that conforms to selfConfiguringAnnotationCell!
    func configure<T: SelfConfiguringAnnotationCell>(_ cellType: T.Type, with annotation: Annotation, for indexPath: IndexPath) -> T {
        guard let cell = tableView.dequeueReusableCell(withIdentifier: cellType.reuseIdentifier, for: indexPath) as? T else {
            fatalError("Unable to dequeue \(cellType)")
        }

        cell.configure(with: annotation)
        return cell
    }

    (...)

    private func configureViewController() {
        title = annotationsController.project.title
        view.backgroundColor = .systemPurple
        navigationController?.navigationBar.prefersLargeTitles = true
        navigationController?.navigationBar.tintColor = .systemOrange
        navigationItem.leftBarButtonItem = UIBarButtonItem(image: ProjectImages.barButtonClose, style: .plain, target: self, action: #selector(closeProject))
    }

    private func configureTableView() {
        tableView = UITableView(frame: view.bounds, style: .grouped)
        view.addSubview(tableView)
        tableView.delegate = self
        tableView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
        tableView.backgroundColor = .clear

        tableView.register(AnnotationCell.self, forCellReuseIdentifier: AnnotationCell.reuseIdentifier)
    }
}

extension AnnotationsViewController: UITableViewDelegate {   
}

Any help really appreciated. I'm going blind here, staring at this thing forever ...

3      

You need the viewForHeaderInSection method implemented. Here dequeue the header view, configure it and return it.

As for the refactor, you can create your own class that inherits from NSObject and implements the table view datasource protocol. Then create instance of this class in your VC and set it as the table view delegate.

3      

Thanks @nemecek-filip ~~Could you help me a bit more, how do I get the correct section from my enum? My enum doesn't have an Int. I'd also like to use a custom header cell ...~~ I'm somewhat further ... 👍

//MARK: - Ext. TableViewDelegate
extension AnnotationsViewController: UITableViewDelegate {
    func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
        let annotationType = AnnotationType.allCases[section]
        let label = UILabel()
        label.text = annotationType.rawValue
        return label
    }
}

Now onto dequeing a header cell and displaying it!

3      

You want to have different views for headers or one? Also if you plan on using only UILabel you can use the titleForHeader method instead and just return string.

3      

I want the same header cell for all headers. I'll configure it with the AnnotationType. I have that working now, but have a hard time setting the headers height ?

//MARK: - Ext. TableViewDelegate
extension AnnotationsViewController: UITableViewDelegate {
    func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
        let annotationType = AnnotationType.allCases[section]
        let cell = tableView.dequeueReusableHeaderFooterView(withIdentifier: AnnotationHeaderCell.reuseIdentifier) as! AnnotationHeaderCell

        cell.configure(with: annotationType)

        return cell
    }
}
class AnnotationHeaderCell: UITableViewHeaderFooterView {
    //MARK: - Properties
    static var reuseIdentifier: String {
        return String(describing: self)
    }

    let typeLabel = ProjectTitleLabel(withTextAlignment: .left, andFont: UIFont.preferredFont(forTextStyle: .caption1), andColor: .white, numberOfLines: 1)
    let imageView = ProjectImageView(frame: .zero)

    //MARK: - Init
    override init(reuseIdentifier: String?) {
        super.init(reuseIdentifier: reuseIdentifier)

        imageView.layer.cornerRadius = 5

        let seperator = Seperator(frame: .zero)

        let padding: CGFloat = 20
        let horizontalStackView = UIStackView(arrangedSubviews: [imageView, typeLabel])
        horizontalStackView.translatesAutoresizingMaskIntoConstraints = false
        horizontalStackView.spacing = padding
        horizontalStackView.distribution = .fill

        let verticalStackView = UIStackView(arrangedSubviews: [horizontalStackView, seperator])
        verticalStackView.translatesAutoresizingMaskIntoConstraints = false
        verticalStackView.spacing = padding
        verticalStackView.distribution = .fillProportionally

        contentView.addSubview(verticalStackView)

        NSLayoutConstraint.activate([
            imageView.heightAnchor.constraint(equalToConstant: 44),
            imageView.widthAnchor.constraint(equalTo: imageView.heightAnchor)
        ])
    }

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

    //MARK: - Configure
    func configure(with annotationType: AnnotationsController.AnnotationType) {
        typeLabel.text = annotationType.rawValue
        imageView.image = annotationType.image
    }
}

3      

Hacking with Swift is sponsored by Essential Developer

SPONSORED Join a FREE crash course for mid/senior iOS devs who want to achieve an expert level of technical and practical skills – it’s the fast track to being a complete senior developer! Hurry up because it'll be available only until April 28th.

Click to save your free spot now

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.