30

I am trying to set the height of a view that is on top of my prototype cell in a table view controller. I use IB to set it's height (size inspector) and set it to 61 like so (the green view is the 'header' view):

header view

But whenever I run the app, its' height ends up being 568.0. I have an IBOutlet called testUIView for the view in my table view controller, and I do: println("testUIView Height->\(testUIView.frame.height)") and indeed ends up being 568.0 at runtime.

Here is a screenshot showing its' height at runtime:

enter image description here

So my question is: How can I set the view's height so it is 61 at runtime so it indeed looks like my first screenshot (size-wise)?

I tried to set its' height property inside override func viewWillLayoutSubviews() but it did not let me assign a value to the height testUIView.frame.height = CGFloat(61.0).

Any help is appreciated! Thanks in advance!

Cheers!

Naresh
  • 16,698
  • 6
  • 112
  • 113
idelara
  • 1,786
  • 4
  • 24
  • 48

6 Answers6

68

Here is a solution which uses section header views rather than the actual table header view:


If you'd like to use a header for you UITableView instead you can design another prototype cell in Interface Builder, make a custom class based on a UITableViewCell and assign it to the prototype cell in interface builder on the class inspector.

Then in your controller you're going to use

func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView?

In that function you're actually going to create a reusable cell from your table view but cast as the custom cell you made for the header. You will have access to all of it's properties like a regular UITableViewCell, then you're just going to return the cell's view

return cell.contentView

Another method you're going to use is

func tableView(tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
    return 61.0
}

That one is pretty self explanatory.

Swift 3.0.1

public override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
    return 61.0
}
Fattie
  • 27,874
  • 70
  • 431
  • 719
Fred Faust
  • 6,696
  • 4
  • 32
  • 55
  • 2
    Thanks! It worked! Another thing I wanted to ask was... Do you know any technique to make it stay beneath the navigation bar at all times i.e not scroll with the rest of the cells? Or is it not possible? – idelara Jul 28 '15 at 01:54
  • 1
    This has you covered for that: http://stackoverflow.com/questions/17582818/uitableview-with-fixed-section-headers - glad it worked out! – Fred Faust Jul 28 '15 at 01:57
  • Thanks for the link! Regarding the code... do you know which section would be relevant for what I am trying to achieve? I gave it a try but idk which method it would be of use. Sorry for asking too much, but I don't really know Objective-C and I get lost in all those square brackets... – idelara Jul 28 '15 at 02:09
  • No problem, I actually think you set the group or plain property right on your Storyboard. To do it in code you'll use: newTableView.style = UITableViewStyle.Plain where "newTableView" matches the name of your tableView's IBOutlet. – Fred Faust Jul 28 '15 at 02:11
  • 1
    Got it working, thanks so much! Hey... so your approach works, but I just realized whenever I scroll down/up in the table view, you can actually see on the right side of the table view the 'height indicator' (where you see where you are positioned in the table view) even in the header cell. My ideal approach is to have kind of like a view stuck with the navigation bar, so that you can't see that you are actually scrolling in the header view. I want this to act as sub-menu where I can switch between tableViewControllers, this is the kind of functionality that a segmented control control offers – idelara Jul 28 '15 at 02:47
  • Do you know how can achieve this functionality? Don't worry, since your answer worked, I am marking it as the solution. I was just wondering if you knew how to achieve that kind of functionality. Thank you for all your help! – idelara Jul 28 '15 at 02:48
  • I think you can add an inset to the scroll indicator using this: newTableView.scrollIndicatorInsets = UIEdgeInsetsMake(top: 64.0, left: 0, bottom: 0, right: 0) - where top is the height of your header, you'll probably have to play around with it – Fred Faust Jul 28 '15 at 02:51
  • Where do I declare it? class-level or inside a method? – idelara Jul 28 '15 at 02:52
  • viewWillAppear or viewDidLayoutSubviews feels safe, though since it's pretty inclusive you may be able to do it earlier – Fred Faust Jul 28 '15 at 02:53
  • Edited above code to use UIEdgeInsetsMake instead of UIEdgeInsets – Fred Faust Jul 28 '15 at 02:54
  • Got it to work. Somehow 120 pts from top did it... Do you know how to make the header Cell to stay kind of attached to the navigation bar? if you scroll all the way up and keep pulling on the table view, you can eventually get it to detach from the top and it flows with the rest of the tableView. Will this work with pullToRefresh? Really man, thank you so much for your help, I've been trying to figure this out for 3 days! – idelara Jul 28 '15 at 03:10
  • I think you can get that by turning off the bounce property I'm not sure though. You can change that in storyboard of myNewTableView.bounces = false & no problem. Pretty good thread for some decent UITableView stuff. – Fred Faust Jul 28 '15 at 03:18
  • I don't know about this answer. To me it looks like the user wanted to adjust the table header view not the section header view. I solved the issue by setting bottom and trailing constraint priorities not by using section headers. It was literally a 1 liner for me, not this hack proposed here. – Jacob Mar 26 '19 at 04:46
  • @Kubee hack? This is intended use of the api. – Fred Faust Mar 27 '19 at 03:21
  • Maybe the word "Hack" is a strong word. But the user wanted to use a table header view NOT a table SECTION header view. This reply tells the user to use a table section header view. Which works, unless you're using your section header for an actual section header like a section title. – Jacob Mar 29 '19 at 19:12
  • @Kubee did you happen to read the comment on the question? I understand what you're saying but this seemed quick at the time and seemed to work out ok, feel free to add an alternate answer. – Fred Faust Mar 30 '19 at 03:12
27

Swift 3/Xcode 8:

Add this in viewDidLoad():

let HEADER_HEIGHT = 100
tableView.tableHeaderView?.frame.size = CGSize(width: tableView.frame.width, height: CGFloat(HEADER_HEIGHT))

Enjoy!

Nick Ivanov
  • 754
  • 7
  • 7
  • THANKS. Was trying to set the height directly and was getting 'height' is a get-only property – Sean May 23 '18 at 09:40
4

The accepted answer doesn't actually answer the question. It instead offers an alternative by using the SECTION header. This question has been answered by others but I will duplicate the answer here with a few more instructions.


Loading the view

Table views are as old as iPhones and therefore you sometimes have to force it to do what you want.

First we need to load the header and manually set its height. Otherwise the view will take more height than it needs. We do this on the viewDidLayoutSubviews callback:

lazy var profileHeaderView: ProfileHeaderView = {
    let headerView = ProfileHeaderView()
    return headerView
}()

override func viewDidLayoutSubviews() {
    super.viewDidLayoutSubviews()
    sizeHeaderToFit()
}

private func sizeHeaderToFit() {
    profileHeaderView.setNeedsLayout()
    profileHeaderView.layoutIfNeeded()

    var frame = profileHeaderView.frame
    frame.size.height = profileHeaderView.calculateHeight()
    profileHeaderView.frame = frame

    tableView.tableHeaderView = profileHeaderView
}

As you can see, I like to put my views inside lazy vars. This ensures that they are always created but only when I start using them.

You can also see that I'm calculating the height. In some cases, your height is fixed and therefore you can just set the frame height to a hardcoded value.


Set some priorities

We will likely see some constraint warnings appear in our debugger. This happens because the table view first forces a 0x0 size before using the size we specified above At this moment, your constraints and the height of the view are in conflict with each other.

To clear these, we simply set the constraint priorities. First you should wrap your header view components inside another view (I generally always do this for header views). This will make managing constraints much easier on your header view.

We then need to set the bottom constraint priorities to high:

containerView.setContentCompressionResistancePriority(.defaultHigh, for: .vertical)
containerView.setContentHuggingPriority(.defaultHigh, for: .vertical)

Here is a more complete example:

WARNING: Thought it is still useful as a guide for laying out your views, do not use this code if you're creating your views using nibs or storyboards.

class ProfileHeaderView: UIView {
    lazy var containerView: UIView = {
        let view = UIView()
        return view
    }()

    override init(frame: CGRect) {
        super.init(frame: frame)
        setupLayout()
    }

    required init?(coder aDecoder: NSCoder) {
        // We do this because the view is not created using storyboards or nibs.
        fatalError("init(coder:) has not been implemented")
    }

    private func setupLayout() {
        self.addSubview(containerView)

        containerView.translatesAutoresizingMaskIntoConstraints = false
        containerView.topAnchor.constraint(equalTo: self.topAnchor).isActive = true
        containerView.leadingAnchor.constraint(equalTo: self.leadingAnchor).isActive = true
        containerView.trailingAnchor.constraint(equalTo: self.trailingAnchor).isActive = true
        containerView.bottomAnchor.constraint(equalTo: self.bottomAnchor).isActive = true
        containerView.setContentCompressionResistancePriority(.defaultHigh, for: .vertical)
        containerView.setContentHuggingPriority(.defaultHigh, for: .vertical)

        // Set the rest of your constraints against your containerView not self and add your subviews to your containerView not self
    }
}

Here is the example of the constraints set using snap-kit:

containerView.snp.makeConstraints() { make in
    make.top.equalTo(self.snp.top)
    make.leading.equalTo(self.snp.leading)
    make.trailing.equalTo(self.snp.trailing)
    make.bottom.equalTo(self.snp.bottom).priority(.high)
}

Make sure you add your constraints to the containerView not self and use containerView to add your subviews and rest of your constraints.

Jacob
  • 1,052
  • 8
  • 10
3

It has to be one of the strangest issues in iOS.

If you do just want a fixed height, as of 2019 you can:

public override func viewDidLayoutSubviews() {
    var frame = tableView.tableHeaderView!.frame
    frame.size.height = 68
    tableView.tableHeaderView!.frame = frame
}

Strange stuff.

A fuller example. Perfect if you just need a "spacer" table view header.

override func viewDidLayoutSubviews() {
    
    super.viewDidLayoutSubviews()
    
    if table.tableHeaderView == nil {
        table.tableHeaderView = UIView()
        table.tableHeaderView?.backgroundColor = .green
    }
    
    if let thv = table.tableHeaderView {
        var frame = thv.frame
        frame.size.height = ... your needed "spacer" height
        thv.frame = frame
    }
}

You'll see the green area clearly.

Fattie
  • 27,874
  • 70
  • 431
  • 719
2

In swift 4.1 and Xcode 9.4.1

func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
     if UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiom.pad {
        return 75.0
      } else {
        return 50.0
      }
}
Naresh
  • 16,698
  • 6
  • 112
  • 113
0

If you use .xib file with UIVIew for your HeaderView, you can use self-sizing header like this

    override func layoutSubviews() {
    super.layoutSubviews()

    // Manually set the view's frame based on layout constraints.
    // The parent UITableView uses the header view's frame height when laying out it's subviews.
    // Only the header view's height is respected.
    // The UITableView ignores the view frame's width.
    // Documentation: https://developer.apple.com/documentation/uikit/uitableview/1614904-tableheaderview
    frame.size = systemLayoutSizeFitting(
        .init(
            width: frame.size.width,
            height: 0
        ),
        withHorizontalFittingPriority: .required,
        verticalFittingPriority: .fittingSizeLevel
    )
}