0

enter image description here enter image description here

This question is being updated every time I act on someone's suggestion here. Unfortunately though, non of the minor housekeeping suggestions helped enough, so the problem remains.

In the nutshell:

  1. I have a viewController (secondVC) with a tableView in it (secondTableView).
  2. Within that tableview, at the very top, I am trying to have a tableHeaderView (with orange background as shown on the attached image).
  3. Inside of the tableHeaderView I am trying to nest two labels one on top of the other (labelOne on top of labelTwo).
  4. labelOne is static. I just says "LIST" and remains perpetually unchanged.
  5. labelTwo is dynamic. It is going to change. It is going to occupy as many lines as needed and the tableHeaderView height should adjust to accommodate and contain both labelOne (LIST) and whatever ends up in labelTwo (long text)

Right now though, as you can see from the picture, labelOne disappeared altogether and labelTwo has weird layout. Something is wrong with constraints I guess. Please help me fix the constraints right.

import UIKit

class SecondVC: UIViewController, UITableViewDelegate, UITableViewDataSource {

@IBOutlet weak var secondTableView: UITableView!

var stuff = [("Some other stuff 1"),("Some other stuff 2"),("Some other stuff 3")]
var insertionText: String = ""

override func viewDidLoad() {
    super.viewDidLoad()
    secondTableView.delegate = self
    secondTableView.dataSource = self
    
    let tableHeader = UIView()
    secondTableView.tableHeaderView = tableHeader
    tableHeader.backgroundColor = .systemOrange
    tableHeader.translatesAutoresizingMaskIntoConstraints = false
    let size = tableHeader.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize)
    let height = size.height
    let width = size.width
    tableHeader.frame = CGRectMake(0, 0, width, height)
    
    let labelOne = UILabel(frame: tableHeader.bounds)
    labelOne.text = "USER:"
    labelOne.numberOfLines = 0
    labelOne.lineBreakMode = .byWordWrapping
    labelOne.textAlignment = .center
    labelOne.translatesAutoresizingMaskIntoConstraints = false
    
    let labelTwo = UILabel(frame: tableHeader.bounds)
    labelTwo.translatesAutoresizingMaskIntoConstraints = false
    labelTwo.text = "Inherited text here: \(insertionText)"
    labelTwo.numberOfLines = 0
    labelTwo.lineBreakMode = .byWordWrapping
    labelTwo.textAlignment = .center
    labelTwo.translatesAutoresizingMaskIntoConstraints = false

    tableHeader.addSubview(labelOne)
    tableHeader.addSubview(labelTwo)
    
    labelOne.topAnchor.constraint(
        equalTo: tableHeader.layoutMarginsGuide.topAnchor,
        constant: 11).isActive = true
    labelOne.leadingAnchor.constraint(
        equalTo: tableHeader.layoutMarginsGuide.leadingAnchor,
        constant: 20).isActive = true
    labelOne.trailingAnchor.constraint(
        equalTo: tableHeader.layoutMarginsGuide.trailingAnchor,
        constant: -20).isActive = true
    
    labelTwo.topAnchor.constraint(
        equalTo: labelOne.layoutMarginsGuide.bottomAnchor,
        constant: 0).isActive = true
    labelTwo.leadingAnchor.constraint(
        equalTo: tableHeader.leadingAnchor,
        constant: 20).isActive = true
    labelTwo.trailingAnchor.constraint(
        equalTo: tableHeader.trailingAnchor,
        constant: -20).isActive = true
    labelTwo.bottomAnchor.constraint(
        equalTo: tableHeader.bottomAnchor,
        constant: -11).isActive = true }

override func viewDidLayoutSubviews() {
    super.viewDidLayoutSubviews()
    if let tableHeader = secondTableView.tableHeaderView {
        let height = tableHeader.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize).height
        var headerFrame = tableHeader.frame
        if height != headerFrame.size.height {
            headerFrame.size.height = height
            tableHeader.frame = headerFrame
            secondTableView.tableHeaderView = tableHeader } } }

func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    stuff.count }

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = secondTableView.dequeueReusableCell(withIdentifier: "SecondTVCell", for: indexPath) as! SecondTVCell
    cell.someOtherStuffLabel.text = stuff[indexPath.row]
    return cell } }
CalebGates
  • 35
  • 5
  • 1. You never set `labelOne topAnchor` to `tableHeader topAnchor`. 2. Did you follow the answers to the duplicate question from your previous question? – HangarRash May 06 '23 at 21:47
  • If I set the topAnchor, labelOne disappears altogether. Also the duplicate questions provided no solution for my case. Either this problem is super hard, that no one can resolve it, or I screwed something up big time, so that no solutions are able to help me – CalebGates May 06 '23 at 21:51
  • You have to set labelOne's topAnchor and you need to implement the code as shown in the answers to the duplicate. – HangarRash May 06 '23 at 22:06
  • nope. I just added the TopAnchor once again. No changes – CalebGates May 06 '23 at 22:13
  • But did you also implement the code from those other answers in the way shown? – HangarRash May 06 '23 at 22:16
  • Which answer are you referring to? There are a bunch, but none of them seem to help my case. Can you please give the link? – CalebGates May 06 '23 at 22:18
  • https://stackoverflow.com/a/34689293/20287183 or any of the upvoted answers shown. – HangarRash May 06 '23 at 22:21
  • Oh yes, tried all of them. Tbh nothing changes, so I figured that the problem is in the constraints – CalebGates May 06 '23 at 22:23
  • 1
    The bottom constraint between `labelTwo` and `tableHeader` has the wrong constant. It is requesting the bottom of the label to be the bottom of the header plus 11 points. That should be -11 as you need the bottom of the label to have a smaller vertical position, not larger, than the header bottom. The constant is not a spacing between the views, it is an equation of how the view edges relate to each other. – Geoff Hackworth May 07 '23 at 08:39

2 Answers2

2

You're not that far off... but couple of things:

The .tableHeaderView needs .translatesAutoresizingMaskIntoConstraints to be True, not False

Because you are using a multi-line label, we have to tell auto-layout how wide the view can be when requesting its "fitting" size - so viewDidLayoutSubviews() becomes:

override func viewDidLayoutSubviews() {
    super.viewDidLayoutSubviews()
    
    if let tableHeader = secondTableView.tableHeaderView {
        // define maximum width for the header view
        let fitSize: CGSize = CGSize(width: secondTableView.frame.width, height: .greatestFiniteMagnitude)
        // ask UIKit to give us the "fitting" size
        let sz: CGSize = tableHeader.systemLayoutSizeFitting(fitSize, withHorizontalFittingPriority: .required, verticalFittingPriority: .defaultLow)
        let height: CGFloat = sz.height
        var headerFrame = tableHeader.frame
        if height != headerFrame.size.height {
            headerFrame.size.height = height
            tableHeader.frame = headerFrame
            secondTableView.tableHeaderView = tableHeader
        }
    }
}

Here's your controller class, with those slight modifications:

class SecondVC: UIViewController, UITableViewDelegate, UITableViewDataSource {
    
    @IBOutlet weak var secondTableView: UITableView!
    
    var stuff = [("Some other stuff 1"),("Some other stuff 2"),("Some other stuff 3")]
    var insertionText: String = "This is some sample insertion text that is currently over writing the first cell. Let's fix that."
    
    override func viewDidLoad() {
        super.viewDidLoad()
        secondTableView.delegate = self
        secondTableView.dataSource = self
        
        let tableHeader = UIView()
        secondTableView.tableHeaderView = tableHeader
        tableHeader.backgroundColor = .systemOrange

        // SHOULD NOT BE SET TO FALSE           
        //tableHeader.translatesAutoresizingMaskIntoConstraints = false
        //      let size = tableHeader.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize)
        //      let height = size.height
        //      let width = size.width
        //      tableHeader.frame = CGRectMake(0, 0, width, height)
        
        let labelOne = UILabel(frame: tableHeader.bounds)
        labelOne.text = "USER:"
        labelOne.numberOfLines = 0
        labelOne.lineBreakMode = .byWordWrapping
        labelOne.textAlignment = .center
        labelOne.translatesAutoresizingMaskIntoConstraints = false
        
        let labelTwo = UILabel(frame: tableHeader.bounds)
        labelTwo.translatesAutoresizingMaskIntoConstraints = false
        labelTwo.text = "Inherited text here: \(insertionText)"
        labelTwo.numberOfLines = 0
        labelTwo.lineBreakMode = .byWordWrapping
        labelTwo.textAlignment = .center
        labelTwo.translatesAutoresizingMaskIntoConstraints = false
        
        tableHeader.addSubview(labelOne)
        tableHeader.addSubview(labelTwo)
        
        // let's make the constraint setup a little more readable
        let g = tableHeader.layoutMarginsGuide
        
        NSLayoutConstraint.activate([
            
            labelOne.topAnchor.constraint(equalTo: g.topAnchor, constant: 11.0),
            
            labelOne.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
            labelOne.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -20.0),
            
            labelTwo.topAnchor.constraint(equalTo: labelOne.bottomAnchor, constant: 0.0),
            
            labelTwo.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
            labelTwo.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -20.0),

            labelTwo.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: -11.0),
            
        ])

        // so we can see the label frames
        labelOne.backgroundColor = .cyan
        labelTwo.backgroundColor = .yellow
    }
    
    override func viewDidLayoutSubviews() {
        super.viewDidLayoutSubviews()
        
        if let tableHeader = secondTableView.tableHeaderView {
            // define maximum width for the header view
            let fitSize: CGSize = CGSize(width: secondTableView.frame.width, height: .greatestFiniteMagnitude)
            // ask UIKit to give us the "fitting" size
            let sz: CGSize = tableHeader.systemLayoutSizeFitting(fitSize, withHorizontalFittingPriority: .required, verticalFittingPriority: .defaultLow)
            let height: CGFloat = sz.height
            var headerFrame = tableHeader.frame
            if height != headerFrame.size.height {
                headerFrame.size.height = height
                tableHeader.frame = headerFrame
                secondTableView.tableHeaderView = tableHeader
            }
        }
    }
    
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        stuff.count }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = secondTableView.dequeueReusableCell(withIdentifier: "SecondTVCell", for: indexPath) as! SecondTVCell
        cell.someOtherStuffLabel.text = stuff[indexPath.row]
        return cell }
    
}

I find it very helpful during development to give UI elements contrasting background colors... makes it easy to see the framing at run-time. So I set the header view labels to cyan and yellow, and this is the result:

enter image description here

and when the device is rotated:

enter image description here

Note: when you run this, you will see some auto-layout complaints. That's because we're setting constraints on the labels and adding them (and the header view) to the view hierarchy in viewDidLoad() ... but the constraints have conflicts at that point.

We can safely ignore those, but if we want to avoid them we can give the bottom constraint from the second label a priority of less-than required:

let g = tableHeader.layoutMarginsGuide
        
NSLayoutConstraint.activate([
            
    labelOne.topAnchor.constraint(equalTo: g.topAnchor, constant: 11.0),
            
    labelOne.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
    labelOne.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -20.0),
            
    labelTwo.topAnchor.constraint(equalTo: labelOne.bottomAnchor, constant: 0.0),
        
    labelTwo.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
    labelTwo.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -20.0),

    // don't set this here    
    //labelTwo.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: -11.0),
            
])

// this will satisfy auto-layout
let c = labelTwo.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: -11.0)
c.priority = .required - 1
c.isActive = true
DonMag
  • 69,424
  • 5
  • 50
  • 86
  • OH MY GOD !!!! You are my personal hero now!!! Everything works like it should!!! Thanks a lot @DonMag!!! Everyone else, who participated in this question, thanks a lot as well @teamPlanetEarth !!! – CalebGates May 08 '23 at 16:05
-1

You have a mistakes in order when you add views. When you try to configure view size with autolayout you need to set

.translatesAutoresizingMaskIntoConstraints = false

before adding a view to a superview by:

.addSubview(labelOne)

In your code swift automatically create set of constraints related to labels zero size when you do addSubview(...). After that you add additional constraints that conflict with autogenereted. This is the reason why swift can't resolve correct size of your view. More about how this property work you can found here: translatesAutoresizingMaskIntoConstraints

Another problem here:

let size = tableHeader.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize)
let height = size.height
let width = size.width
tableHeader.frame = CGRectMake(0, 0, width, height)

tableHeader.translatesAutoresizingMaskIntoConstraints = true

The reason is very same. You try to get size of header from autolayout system before you finish header configuration inside. And bind this size by translatesAutoresizingMaskIntoConstraints property. So you need to add labels to header view and resolve the first problem with setting translatesAutoresizingMaskIntoConstraints at correct place. Already after this resolve header size and set it into table.

loverap007
  • 129
  • 6
  • Thank you! I will update my question, for I got even more lost and further away from the desired outcome as I moved stuff around per your suggestion – CalebGates May 08 '23 at 06:57