'How can I make section header like Android sticky view?

I am using the section header of the tableview. https://www.youtube.com/watch?v=U3gqR4dRiT8

If you look at it, almost at the end

When the section header goes up, the existing top section header goes up and collides and disappears.

And the text in the top section header is changed and floats But when I run the sample, the text changes. It doesn't seem to have the effect of pushing up the section header. What should I do?


import UIKit

class MyTableviewControllerTableViewController: UITableViewController {

    private let numberOfSections = 3
    var meat = ["Beef","Turkey","Fish", "Lamb", "Chicken", "Pork", "Beef","Turkey","Fish", "Lamb", "Chicken", "Pork"]
    var fruit = ["Apple","Banana","Cherry","Apple","Banana","Cherry","Apple","Banana","Cherry"]
    var vegetable = ["Lettuce","Broccoli","Cauliflower","Lettuce","Broccoli","Cauliflower","Lettuce","Broccoli","Cauliflower"]
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        // make the header sticky
        self.automaticallyAdjustsScrollViewInsets = false
    }

    // MARK: - Table view data source

    // return the number of sections
    override func numberOfSections(in tableView: UITableView) -> Int {
        return numberOfSections
    }

    // return the number of rows in the specified section
    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        var rowCount = 0
        switch (section) {
        case 0:
            rowCount = meat.count
        case 1:
            rowCount = fruit.count
        case 2:
            rowCount = vegetable.count
        default:
            rowCount = 0
        }
        
        return rowCount
    }

    // Content Cell
    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "tableCell", for: indexPath)

        switch (indexPath.section) {
        case 0:
            cell.textLabel?.text = meat[indexPath.row]
        case 1:
            cell.textLabel?.text = fruit[indexPath.row]
        case 2:
            cell.textLabel?.text = vegetable[indexPath.row]
        default:
            cell.textLabel?.text = "Other"
        }
        return cell
    }
    
    // Header Cell
    override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
        let  headerCell = tableView.dequeueReusableCell(withIdentifier: "HeaderCell") as! CustomTableViewHeaderCell
        headerCell.backgroundColor = UIColor.gray
        
        switch (section) {
        case 0:
            headerCell.headerLabel.text = "Meat";
        case 1:
            headerCell.headerLabel.text = "Fruit";
        case 2:
            headerCell.headerLabel.text = "Vegetable";
        default:
            headerCell.headerLabel.text = "Other";
        }
        
        return headerCell
    }
    
    // Footer Cell
    override func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? {
        let rect = CGRect(origin: CGPoint(x: 0,y :0), size: CGSize(width: tableView.frame.size.width, height: 20))
        let footerView = UIView(frame: rect)
        footerView.backgroundColor = UIColor.darkGray
        return footerView
    }
    
    // footer height
    override func tableView(_ : UITableView, heightForFooterInSection section: Int) -> CGFloat {
        return 20.0
    }


Solution 1:[1]

You can do it programmatically, and the result is perfect... Declare your table view and carry under your UIViewController (Attention, it's No UITableViewController) class:

class YourControllerClass: UIViewController {

let tableView = UITableView()
let cellId = "cellId"
let headerId = "headerId"

var meat = ["Beef","Turkey","Fish", "Lamb", "Chicken", "Pork", "Beef","Turkey","Fish", "Lamb", "Chicken", "Pork"]
var fruit = ["Apple","Banana","Cherry","Apple","Banana","Cherry","Apple","Banana","Cherry"]
var vegetable = ["Lettuce","Broccoli","Cauliflower","Lettuce","Broccoli","Cauliflower","Lettuce","Broccoli","Cauliflower"]

now in viewDidLoad configure nav bar and set tableview attributes and constraints:

override func viewDidLoad() {
    super.viewDidLoad()
    
    configureNavigationBar(largeTitleColor: .yourPreferColor, backgoundColor: .ultraDark, tintColor: .fuxiaRed, title: "My Title", preferredLargeTitle: true) // I use my function to configure navBar

    tableView.register(UITableViewCell.self, forCellReuseIdentifier: cellId)
    tableView.register(HeaderCell.self, forCellReuseIdentifier: headerId)
    tableView.backgroundColor = .yourPreferColor
    tableView.delegate = self
    tableView.dataSource = self
    tableView.separatorColor = UIColor(white: 1, alpha: 0.2)
    tableView.tableFooterView = UIView()
    tableView.translatesAutoresizingMaskIntoConstraints = false
    
    if #available(iOS 11.0, *) {
        tableView.contentInsetAdjustmentBehavior = .never
    } else {
        automaticallyAdjustsScrollViewInsets = false
    }
    
    if #available(iOS 15.0, *) {
        tableView.sectionHeaderTopPadding = 0
        tableView.sectionFooterHeight = 0
    } else {
        UITableView.appearance().sectionHeaderTopPadding = CGFloat(0)
        UITableView.appearance().sectionFooterHeight = CGFloat(0)
    }
    
    view.addSubview(tableView)
    tableView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor).isActive = true
    tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
    tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
    tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
}

Now create an extension for tableView delegate and datasource:

extension NavBarAppearenceController: UITableViewDelegate, UITableViewDataSource {

func numberOfSections(in tableView: UITableView) -> Int {
    return 3
}

func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
    return 50 // header height
}

func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
    
    let headerView = UIView()
    let headerCell = tableView.dequeueReusableCell(withIdentifier: headerId) as! HeaderCell // header cell
    headerCell.translatesAutoresizingMaskIntoConstraints = false
    
    headerView.addSubview(headerCell)
    headerCell.topAnchor.constraint(equalTo: headerView.topAnchor).isActive = true
    headerCell.leadingAnchor.constraint(equalTo: headerView.leadingAnchor).isActive = true
    headerCell.trailingAnchor.constraint(equalTo: headerView.trailingAnchor).isActive = true
    headerCell.bottomAnchor.constraint(equalTo: headerView.bottomAnchor).isActive = true

    switch section {
    case 0:
        headerCell.label.text = "Meat"
    case 1:
        headerCell.label.text = "Fruit"
    default:
        headerCell.label.text = "Vegetables"
    }
    
    return headerView
}

func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
    return 60
}

func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    switch section {
    case 0:
        return meat.count
    case 1:
        return fruit.count
    case 2:
        return vegetable.count
    default:
        return 0
    }
}

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    
    let cell = tableView.dequeueReusableCell(withIdentifier: cellId, for: indexPath)
    
    let mySection = indexPath.section
    
    switch mySection {
    case 0:
        cell.textLabel?.text = "\(meat[indexPath.row])"
    case 1:
        cell.textLabel?.text = "\(fruit[indexPath.row])"
    default:
        cell.textLabel?.text = "\(vegetable[indexPath.row])"
    }
    cell.contentView.backgroundColor = .yourPreferColor
    cell.textLabel?.textColor = .white
    cell.selectionStyle = .none
    
    return cell
 }
}

Create your custom header cell:

class HeaderCell: UITableViewCell {

let myView: UIView = {
    let v = UIView()
    v.backgroundColor = .yourPreferColor
    v.translatesAutoresizingMaskIntoConstraints = false
    
    return v
}()

let label = UILabel()

override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
    super.init(style: style, reuseIdentifier: reuseIdentifier)
    
    contentView.backgroundColor = .yourPreferColor
    
    label.textColor = .white
    label.font = .systemFont(ofSize: 20, weight: .bold)
    label.translatesAutoresizingMaskIntoConstraints = false
    
    contentView.addSubview(myView)
    myView.topAnchor.constraint(equalTo: contentView.topAnchor).isActive = true
    myView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 20).isActive = true
    myView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor).isActive = true
    myView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor).isActive = true
    
    myView.addSubview(label)
    label.topAnchor.constraint(equalTo: myView.topAnchor).isActive = true
    label.leadingAnchor.constraint(equalTo: myView.leadingAnchor).isActive = true
    label.trailingAnchor.constraint(equalTo: myView.trailingAnchor).isActive = true
    label.bottomAnchor.constraint(equalTo: myView.bottomAnchor).isActive = true
}

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

This is the result:

enter image description here

BONUS

This is my navBar configuration func:

func configureNavigationBar(largeTitleColor: UIColor, backgoundColor: UIColor, tintColor: UIColor, title: String, preferredLargeTitle: Bool) {
    
    if #available(iOS 13.0, *) {
        let navBarAppearance = UINavigationBarAppearance()
        navBarAppearance.configureWithOpaqueBackground()
        navBarAppearance.largeTitleTextAttributes = [.foregroundColor: largeTitleColor]
        navBarAppearance.titleTextAttributes = [.foregroundColor: largeTitleColor]
        navBarAppearance.backgroundColor = backgoundColor
        
        navigationController?.navigationBar.standardAppearance = navBarAppearance
        navigationController?.navigationBar.compactAppearance = navBarAppearance
        navigationController?.navigationBar.scrollEdgeAppearance = navBarAppearance
        
        navigationController?.navigationBar.prefersLargeTitles = preferredLargeTitle
        navigationItem.largeTitleDisplayMode = .always
        navigationController?.navigationBar.tintColor = tintColor
        navigationItem.title = title
        
    } else {
        // Fallback on earlier versions
        navigationController?.navigationBar.barTintColor = backgoundColor
        navigationController?.navigationBar.tintColor = tintColor
        navigationController?.navigationBar.isTranslucent = false
        navigationItem.title = title
    }
}

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1