ios – Making two header sections sticky on UITableView

0
85


I researched fairly a bit however simply could not discover the reply to my query.

I’ve created a desk (fashion: .grouped) in Xcode with two headers I need to make sticky. The higher one ought to collapse on scrolling till the peak of the others cells is reached, the decrease one ought to simply keep like it’s – so fairly just like the behaviour proper now (see beneath) – simply sticking to the highest.

Table Preview

My code is as follows:

  1. HomeTableViewController.swift
import UIKit

class HomeTableViewController: UITableViewController {
    
    var headerView: HeaderView = {
        let nib = UINib(nibName: "HeaderView", bundle: nil)
        return nib.instantiate(withOwner: HomeTableViewController.self, choices: nil).first as! HeaderView
    }()
    
    override func viewDidLoad() {
        tremendous.viewDidLoad()
        
        tableView.delegate = self
        tableView.dataSource = self
        
        // Uncomment the next line to protect choice between displays
        // self.clearsSelectionOnViewWillAppear = false
        
        // Uncomment the next line to show an Edit button within the navigation bar for this view controller.
        // self.navigationItem.rightBarButtonItem = self.editButtonItem
                
        headerView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
        headerView.titleLabel.textual content = "Title"
        headerView.scrollView = tableView
        headerView.body = CGRect(
            x: 0,
            y: tableView.safeAreaInsets.prime,
            width: view.body.width,
            top: 250)
        
        tableView.backgroundView = UIView()
        tableView.backgroundView?.addSubview(headerView)
        tableView.contentInset = UIEdgeInsets(
            prime: 250,
            left: 0,
            backside: 0,
            proper: 0)
    }
    
    override func viewSafeAreaInsetsDidChange() {
        tremendous.viewSafeAreaInsetsDidChange()
        tableView.contentInset = UIEdgeInsets(prime: 250 + tableView.safeAreaInsets.prime,
                                              left: 0,
                                              backside: 0,
                                              proper: 0)
        headerView.updatePosition()
    }
    
    override func tableView(_ tableView: UITableView, viewForHeaderInSection part: Int) -> UIView? {
        let sectionHeaderLabelView = UIView()
        sectionHeaderLabelView.backgroundColor = .systemBackground

        let sectionHeaderLabel = UILabel()
        sectionHeaderLabel.textual content = "Subtitle"
        sectionHeaderLabel.textColor = .label
        sectionHeaderLabel.font = UIFont.systemFont(ofSize: 20.0, weight: .semibold)
        sectionHeaderLabel.body = CGRect(x: view.body.width * 0.05, y: 0, width: view.body.width, top: 58)
        sectionHeaderLabelView.addSubview(sectionHeaderLabel)
        
        let sectionHeaderButton = UIButton()
        sectionHeaderButton.body = CGRect(x: view.body.width * 0.58, y: 0, width: view.body.width * 0.5, top: 58)
        sectionHeaderButton.setTitle("alle anzeigen →", for: .regular)
        sectionHeaderButton.setTitleColor(.secondaryLabel, for: .regular)
        sectionHeaderButton.titleLabel?.font = UIFont.systemFont(ofSize: 15.0, weight: .semibold)
        sectionHeaderButton.addTarget(self, motion: #selector(buttonAction), for: .touchUpInside)
        sectionHeaderLabelView.addSubview(sectionHeaderButton)

        return sectionHeaderLabelView
    }
    
    @objc func buttonAction(sender: UIButton!) {
        print("Button tapped")
    }
    
    override func viewWillLayoutSubviews() {
        tremendous.viewWillLayoutSubviews()
        headerView.updatePosition()
    }
    
    // MARK: Desk view knowledge supply
    override func numberOfSections(in tableView: UITableView) -> Int {
        // #warning Incomplete implementation, return the variety of sections
        return 1
    }
    
    override func tableView(_ tableView: UITableView, numberOfRowsInSection part: Int) -> Int {
        // #warning Incomplete implementation, return the variety of rows
        return 20
    }
    
    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "providerCell", for: indexPath) as! HomeTableViewCell
        
        [...]
        
        return cell
    }
    
    [...]
    
}
  1. HeaderView.swift (only for the collapsing “prime” header)
import Basis
import UIKit

class HeaderView: UIView {
    @IBOutlet non-public(set) var titleLabel: UILabel!
    @IBOutlet weak var topBlurView: UIVisualEffectView!
    @IBOutlet weak var bottomBlurView: UIVisualEffectView!
    @IBOutlet weak var scrollView: UIScrollView?
    non-public var cachedMinimumSize: CGSize?
    
    override func layoutSubviews() {
        tremendous.layoutSubviews()

        topBlurView.impact = .none
        bottomBlurView.impact = .none
    }
    
    // Calculate and cache the minimal measurement the header's constraints will match.
    // This worth is cached since it may be a pricey calculation to make, and
    // we need to preserve the framerate excessive.
    non-public var minimumHeight: CGFloat {
        get {
            guard let scrollView = scrollView else { return 0 }
            if let cachedSize = cachedMinimumSize {
                if cachedSize.width == scrollView.body.width {
                    return cachedSize.top
                }
            }
         
            // Ask Auto Format what the minimal top of the header ought to be.
            let minimumSize = systemLayoutSizeFitting(CGSize(width: scrollView.body.width, top: 0),
                                                      withHorizontalFittingPriority: .required,
                                                      verticalFittingPriority: .defaultLow)
            cachedMinimumSize = minimumSize
            return minimumSize.top
        }
    }

    func updatePosition() {
        guard let scrollView = scrollView else { return }
        
        // Calculate the minimal measurement the header's constraints will match
        let minimumSize = minimumHeight
        
        // Calculate the baseline header top and vertical place
        let referenceOffset = scrollView.safeAreaInsets.prime
        let referenceHeight = scrollView.contentInset.prime - referenceOffset
        
        // Calculate the brand new body measurement and place
        let offset = referenceHeight + scrollView.contentOffset.y
        let targetHeight = referenceHeight - offset - referenceOffset
        var targetOffset = referenceOffset
        if targetHeight < minimumSize {
            targetOffset += targetHeight - minimumSize
        }
        
        // Replace the header's top and vertical place.
        var headerFrame = body;
        headerFrame.measurement.top = max(minimumSize, targetHeight)
        headerFrame.origin.y = targetOffset
        
        body = headerFrame;
    }
}

Might anybody help kindly on the right way to make these two persist with the highest when scrolling?

All the very best!