0

I have a UITableView and I want to display a dynamic header at the top of the table. I am trying to do this using tableHeaderView. The header contains a titleLabel and a descriptionLabel arranged inside a UIStackView. I also want to add padding around the UIStackView.

Here is the simplified version of my CustomHeaderView:

class CustomHeaderView: UIView {
    
    var padding: UIEdgeInsets = .zero

    // MARK: - UI Elements
    private lazy var titleLabel: UILabel = {
        let label = UILabel()
        label.numberOfLines = 0
        label.translatesAutoresizingMaskIntoConstraints = false
        return label
    }()

    private lazy var descriptionLabel: UILabel = {
        let label = UILabel()
        label.numberOfLines = 0
        label.translatesAutoresizingMaskIntoConstraints = false
        return label
    }()

    private lazy var stackView: UIStackView = {
        let stackView = UIStackView(arrangedSubviews: [titleLabel, descriptionLabel])
        stackView.axis = .vertical
        stackView.alignment = .fill
        stackView.spacing = 8
        stackView.translatesAutoresizingMaskIntoConstraints = false
        return stackView
    }()

    override init(frame: CGRect = .zero) {
        super.init(frame: frame)
        setupView()
    }

    required init?(coder: NSCoder) {
        super.init(coder: coder)
        setupView()
    }

    private func setupView() {
        addSubview(stackView)
        NSLayoutConstraint.activate([
            stackView.topAnchor.constraint(equalTo: topAnchor, constant: padding.top),
            stackView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: padding.left),
            stackView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -padding.right),
            stackView.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -padding.bottom),
        ])
    }
}

In my ViewController, I set up the header like this:

override func viewDidLoad() {
    super.viewDidLoad()

    let headerView = CustomHeaderView()
    headerView.padding = .init(top: 70, left: 23, bottom: 8, right: 23)
    tableView.tableHeaderView = headerView
}

override func viewDidLayoutSubviews() {
    super.viewDidLayoutSubviews()
    tableView.updateHeaderViewHeight()
}

I use the following extension to dynamically update the height of the header:

extension UITableView {
    func updateHeaderViewHeight() {
        guard let headerView = self.tableHeaderView else { return }
        headerView.setNeedsLayout()
        headerView.layoutIfNeeded()
        
        let size = headerView.systemLayoutSizeFitting(CGSize(width: self.bounds.width, height: 0))
        if headerView.frame.size.height != size.height {
            headerView.frame.size.height = size.height
            self.tableHeaderView = headerView
        }
    }
}

The setup works as expected, and the header adjusts its size dynamically based on the content. However, I am encountering Auto Layout warnings.

First warning:

(
    "<NSLayoutConstraint:0x600002137250 V:|-(70)-[UIStackView:0x10312c3f0]   (active, names: '|':VeriQSDK.CustomHeaderView:0x101504910 )>",
    "<NSLayoutConstraint:0x600002134cd0 UIStackView:0x10312c3f0.bottom == VeriQSDK.CustomHeaderView:0x101504910.bottom - 8   (active)>",
    "<NSLayoutConstraint:0x600002143f70 'UISV-canvas-connection' UIStackView:0x10312c3f0.top == UILabel:0x10312cba0.top   (active)>",
    "<NSLayoutConstraint:0x600002143de0 'UISV-canvas-connection' V:[UILabel:0x10312d130]-(0)-|   (active, names: '|':UIStackView:0x10312c3f0 )>",
    "<NSLayoutConstraint:0x600002143f20 'UISV-spacing' V:[UILabel:0x10312cba0]-(8)-[UILabel:0x10312d130]   (active)>",
    "<NSLayoutConstraint:0x600002148000 'UIView-Encapsulated-Layout-Height' VeriQSDK.CustomHeaderView:0x101504910.height == 0   (active)>"
)

Will attempt to recover by breaking constraint 
<NSLayoutConstraint:0x600002143f20 'UISV-spacing' V:[UILabel:0x10312cba0]-(8)-[UILabel:0x10312d130]   (active)>

Second warning:

(
    "<NSLayoutConstraint:0x600002137250 V:|-(70)-[UIStackView:0x10312c3f0]   (active, names: '|':VeriQSDK.CustomHeaderView:0x101504910 )>",
    "<NSLayoutConstraint:0x600002134cd0 UIStackView:0x10312c3f0.bottom == VeriQSDK.CustomHeaderView:0x101504910.bottom - 8   (active)>",
    "<NSLayoutConstraint:0x600002148000 'UIView-Encapsulated-Layout-Height' VeriQSDK.CustomHeaderView:0x101504910.height == 0   (active)>"
)

Will attempt to recover by breaking constraint 
<NSLayoutConstraint:0x600002134cd0 UIStackView:0x10312c3f0.bottom == VeriQSDK.CustomHeaderView:0x101504910.bottom - 8   (active)>

The warnings seem to be related to the UIView-Encapsulated-Layout-Height constraint, which forces the CustomHeaderView height to 0.

How can I properly manage padding and dynamically adjust the tableHeaderView height without encountering these Auto Layout warnings?

2
  • The usual solution to this problem is to reduce the priority of one vertical constraint to 999. Did you try that? Commented Nov 27, 2024 at 1:05
  • Thanks for the suggestion! I tried reducing the priority to 999, but unfortunately, it didn’t resolve the issue. Commented Nov 27, 2024 at 6:07

2 Answers 2

1

Using CustomHeaderView(), you provide initial frame for CustomHeaderView is .zero so intial height of header is 0. When add to tableHeaderView, CustomHeaderView inherits a height constraint as you see (UIView-Encapsulated-Layout-Height). So to get rid of this warning, you need to provide initial height for your header view:

// padding top + stackview spacing + padding bottom
let headerView = CustomHeaderView(frame: .init(x: 0, y: 0, width: 200, height: 70 + 8 + 8))
Sign up to request clarification or add additional context in comments.

1 Comment

Thank you! Providing an initial height for CustomHeaderView as suggested solved the issue. @duckSern1108
0

You can use this. It'll automatically increase height based on your content.

override func viewDidLayoutSubviews() {
        super.viewDidLayoutSubviews()
        
        guard let headerView = yourTblVw.tableHeaderView else {
            return
        }
        let size = headerView.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize)
        
        if headerView.frame.size.height != size.height {
            headerView.frame.size.height = size.height
            yourTblVw.tableHeaderView = headerView
            yourTblVw.layoutIfNeeded()
        }
    }

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.