104

I'm having trouble resizing a tableHeaderView. It simple doesn't work.

1) Create a UITableView and UIView (100 x 320 px);

2) Set the UIView as tableHeaderView of the UITableView;

3) Build and Go. Everything is ok.

Now, I want to resizing the tableHeaderView, so I add this code in viewDidLoad:

self.tableView.autoresizesSubviews = YES;

self.tableView.tableHeaderView = myHeaderView;
self.tableView.tableFooterView = myFooterView;

CGRect newFrame = self.tableView.tableHeaderView.frame;
newFrame.size.height = newFrame.size.height + 100;
self.tableView.tableHeaderView.frame = newFrame;

The height of the tableHeaderView should appear with 200, but appears with 100.

If I write:

self.tableView.autoresizesSubviews = YES;


CGRect newFrame = myHeaderView.frame;
newFrame.size.height = newFrame.size.height + 100;
myHeaderView.frame = newFrame;


self.tableView.tableHeaderView = myHeaderView;
self.tableView.tableFooterView = myFooterView;

Then it starts with 200 of height, as I want. But I want to be able to modify it in runtime.

I've also tried this, without success:

self.tableView.autoresizesSubviews = YES;

self.tableView.tableHeaderView = myHeaderView;
self.tableView.tableFooterView = myFooterView;

CGRect newFrame = self.tableView.tableHeaderView.frame;
newFrame.size.height = newFrame.size.height + 100;
self.tableView.tableHeaderView.frame = newFrame;

[self.tableView.tableHeaderView setNeedsLayout];
[self.tableView.tableHeaderView setNeedsDisplay];
[self.tableView setNeedsLayout];
[self.tableView setNeedsDisplay];

The point here is: How do we resize a tableHeaderView in runtime ???

Have anyone able to do this?

Thanks

iMe

JOM
  • 8,139
  • 6
  • 78
  • 111
iMe
  • 1,041
  • 2
  • 9
  • 5

19 Answers19

181

FYI: I've gotten this to work by modifying the tableHeaderView and re-setting it. In this case, i'm adjusting the size of the tableHeaderView when the UIWebView subview has finished loading.

[webView sizeToFit];
CGRect newFrame = headerView.frame;
newFrame.size.height = newFrame.size.height + webView.frame.size.height;
headerView.frame = newFrame;
[self.tableView setTableHeaderView:headerView];
kubi
  • 48,104
  • 19
  • 94
  • 118
  • 13
    +1 This worked for me. Calling 'setTableHeaderView' after your subview has changed size is the key. The problem is, my subview changes size over a second as an animation. Now I'm trying to figure out how to animate the tableHeaderView with it. – Andrew Jan 18 '10 at 07:56
  • +1 Awesome, thanks a lot! I've been trying to debug this for hours now. Not sure exactly why this needs to be done this way but glad to finally know. – raidfive Jun 23 '10 at 16:58
  • 1
    Perfect, thanks a bunch. To me, this is an example of one of the less desirable qualities of properties in Objective-C. There's no way for us to know (and no reason we should know) that setting the header has the side effect of recalculating the height. It should either do it automatically when we update the header's height, or we should be required to call something like `[tableView recalculateHeaderHeight]` every time. – jakeboxer Dec 01 '10 at 17:05
  • 48
    @Andrew I know this is almost a year too late, but better late then never: I was able to animate the changing height of the table header view by wrapping the `setTableHeaderView:` call with `[tableview beginUpdates]` and `[tableview endUpdates]` – jasongregori Nov 15 '11 at 01:10
  • 2
    @jasongregori handy comment you added - i'm seeing that the same technique (beginUpdates+endUpdates) does not animate the height change for a tableFooterView the way it does a tableHeaderView. Have you figured out a good way to animate the tableFooterView as well? – kris Dec 05 '11 at 23:32
  • @kris no sorry. I've never needed to animate it. The only time I can think of where you would need to animate would be where the user was scrolled all the way to the bottom so not animating would jerk the screen. If that's your problem, maybe you could scroll up with an animation before changing the tableFooterView? – jasongregori Dec 06 '11 at 20:54
  • @jasongregori that's a pretty accurate description of where I'm at. Unfortunately scrolling up isn't a good option as the table starts off very small, and so the tablefooterview and the jerking is visible. thanks though – kris Dec 06 '11 at 22:38
  • @kris i think i see. its overly complicated but can you start an animation that scrolls up and fades the footer view out, then remove it? – jasongregori Dec 08 '11 at 04:00
  • 1
    Impressive, it event works when doing it inside a UIView animation block, although I don't think it's very efficient in that case. – Can May 05 '12 at 00:56
  • @jasongregori Since we're resurrecting this thread anyway. Your `beginUpdates` + `endUpdates` works for animating the new position of the table cells, but the tableHeaderView doesn't actually animate its own frame change. Do you have a solution for that? – SpacyRicochet May 12 '12 at 17:31
  • @jasongregori I managed to animate the size change of the tableHeaderView by wrapping it in an animateblock with duration 0.3. But I'm wondering if there's a more proper way to solve this. – SpacyRicochet May 12 '12 at 17:34
  • @SpacyRicochet thats exactly what i ended up doing except i think i might be using 0.2 seconds. – jasongregori May 15 '12 at 01:55
  • Check out [my answer below](http://stackoverflow.com/a/15416193/784318), if you want to animate the changes. – Besi Mar 14 '13 at 17:36
  • 1
    To animate the footer: `[UIView animateWithDuration:0.3 animations:^{ self.bottomSpacerView.height = 0; self.tableView.tableFooterView = self.bottomSpacerView; }];` – sobri Mar 19 '13 at 12:07
  • Fair warning, setting the header view in `viewWillLayoutSubviews` causes that method to be invoked indefinitely. I'm still trying to find a workaround. – Norswap Dec 18 '13 at 09:58
  • I'm using a UITableViewCell as a header and had to call tableView.layoutSubviews after modifying cell bounds (or frame) and re-setting it to the tableView. Otherwise the height didn't change. – Tapani Oct 15 '15 at 13:58
  • Finally, after 8 hours of debugging. Thanks for the answer. – Yugal Jindle Jun 09 '19 at 06:46
12

This answer is old and apparently doesn't work on iOS 7 and above.

I ran into the same problem, and I also wanted the changes to animate, so I made a subclass of UIView for my header view and added these methods:

- (void)adjustTableHeaderHeight:(NSUInteger)newHeight{
    NSUInteger oldHeight = self.frame.size.height;
    NSInteger originChange = oldHeight - newHeight;

    [UIView beginAnimations:nil context:nil];

    [UIView setAnimationDuration:1.0f];
    [UIView setAnimationDelegate:self];
    [UIView setAnimationDidStopSelector:@selector(animationDidStop:finished:context:)];

    self.frame = CGRectMake(self.frame.origin.x, 
                        self.frame.origin.y, 
                        self.frame.size.width, 
                        newHeight);

    for (UIView *view in [(UITableView *)self.superview subviews]) {
        if ([view isKindOfClass:[self class]]) {
            continue;
        }
        view.frame = CGRectMake(view.frame.origin.x, 
                            view.frame.origin.y - originChange, 
                            view.frame.size.width, 
                            view.frame.size.height);
    }

    [UIView commitAnimations];
}

- (void)animationDidStop:(NSString *)animationID finished:(NSNumber *)finished context:(void *)context{
    [(UITableView *)self.superview setTableHeaderView:self];
}

This essentially animates all the subviews of the UITableView that aren't the same class type as the calling class. At the end of the animation, it calls setTableHeaderView on the superview (the UITableView) – without this the UITableView contents will jump back the next time the user scrolls. The only limitation I've found on this so far is if the user attempts to scroll the UITableView while the animation is taking place, the scrolling will animate as if the header view hasn't been resized (not a big deal if the animation is quick).

garrettmoon
  • 139
  • 2
  • 11
11

If you want to conditionally animate the changes you can do the following:

- (void) showHeader:(BOOL)show animated:(BOOL)animated{

    CGRect closedFrame = CGRectMake(0, 0, self.view.frame.size.width, 0);
    CGRect newFrame = show?self.initialFrame:closedFrame;

    if(animated){
        // The UIView animation block handles the animation of our header view
        [UIView beginAnimations:nil context:nil];
        [UIView setAnimationDuration:0.3];
        [UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];

        // beginUpdates and endUpdates trigger the animation of our cells
        [self.tableView beginUpdates];
    }

    self.headerView.frame = newFrame;
    [self.tableView setTableHeaderView:self.headerView];

    if(animated){
        [self.tableView endUpdates];
        [UIView commitAnimations];
    }
}

Please note that the animation is two-folded:

  1. The animation of the cells below the tableHeaderView. This is done using beginUpdates and endUpdates
  2. The animation of the actual header view. This is done using a UIView animation block.

In order to synchronize those two animations the animationCurve has to be set to UIViewAnimationCurveEaseInOut and the duration to 0.3, which seems to be what the UITableView uses for it's animation.

Update

I created an Xcode project on gihub, which does this. Check out the project ResizeTableHeaderViewAnimated in besi/ios-quickies

screenshot

Besi
  • 22,579
  • 24
  • 131
  • 223
  • 2
    This technique does work, but it doesn't work for table views with section headers. When you use begin updates/end updates it interferes with the animation block, leaving duplicate section headers. – BlueFish Aug 15 '13 at 00:27
10

I think it should work if you just set the height of myHeaderView like so:

CGRect newFrame = myHeaderView.frame;
newFrame.size.height = newFrame.size.height + 100;
myHeaderView.frame = newFrame;

self.tableView.tableHeaderView = myHeaderView;
Greg Martin
  • 5,074
  • 3
  • 34
  • 35
6

Used @garrettmoon solution above until iOS 7.
Here's an updated solution based on @garrettmoon's:

- (void)adjustTableHeaderHeight:(NSUInteger)newHeight animated:(BOOL)animated {

    [UIView beginAnimations:nil context:nil];

    [UIView setAnimationDuration:[CATransaction animationDuration]];
    [UIView setAnimationDelegate:self];
    [UIView setAnimationDidStopSelector:@selector(animationDidStop:finished:context:)];

    self.frame = CGRectMake(self.frame.origin.x,
                        self.frame.origin.y,
                        self.frame.size.width,
                        newHeight);

    [(UITableView *)self.superview setTableHeaderView:self];

    [UIView commitAnimations];
}

- (void)animationDidStop:(NSString *)animationID finished:(NSNumber *)finished context:(void *)context{
    [(UITableView *)self.superview setTableHeaderView:self];
}
Avishay Cohen
  • 2,418
  • 1
  • 22
  • 40
5

This worked for me on iOS 7 and 8. This code is running on the table view controller.

[UIView animateWithDuration:0.3 animations:^{
    CGRect oldFrame = self.headerView.frame;
    self.headerView.frame = CGRectMake(oldFrame.origin.x, oldFrame.origin.y, oldFrame.size.width, newHeight);
    [self.tableView setTableHeaderView:self.headerView];
}];
Darcy Rayner
  • 3,385
  • 1
  • 23
  • 15
4

Its because the setter of tableHeaderView.

You have to set the UIView height before set the tableHeaderView. (Would be much easier if Apple open sources this framework...)

Thomas Decaux
  • 21,738
  • 2
  • 113
  • 124
4

On iOS 9 and below, tableHeaderView would not re-layout after resizing it. This issue is resolved in iOS 10.

To solve this issue, just do it with the following code:

self.tableView.tableHeaderView = self.tableView.tableHeaderView;
klaudz
  • 270
  • 1
  • 4
2

On iOS 9.x, doing this on viewDidLoad works just fine:

var frame = headerView.frame
frame.size.height = 11  // New size
headerView.frame = frame

headerView is declared as @IBOutlet var headerView: UIView! and connected on the storyboard, where it is placed at the top of the tableView, to function as the tableHeaderView.

Elijah
  • 8,381
  • 2
  • 55
  • 49
Eneko Alonso
  • 18,884
  • 9
  • 62
  • 84
2

This is only for when you use auto-layout and set translatesAutoresizingMaskIntoConstraints = false to a custom header view.

The best and the simplest way is to override intrinsicContentSize. Internally UITableView uses intrinsicContentSize to decide its header/footer size. Once you have override intrinsicContentSize in your custom view, What you need to do is as below

  1. configure the custom header/footer view's layout(subviews)
  2. invoke invalidateIntrinsicContentSize()
  3. invoke tableView.setNeedsLayout() and tableView.layoutIfNeeded()

Then the UITableView's header/footer will be updated as you want. No need to set the view nil or reset.

One thing really interesting for the UITableView.tableHeaderView or .tableFooterView is that UIStackView loose its ability to manage its arrangedSubviews. If you want to use UIStackView as a tableHeaderView or tableFooterView, you have to embed the stackView in a UIView and override UIView's intrinsicContentSize.

Ryan
  • 4,799
  • 1
  • 29
  • 56
2

For swift 5 Tested code

override func viewDidLayoutSubviews() {
      super.viewDidLayoutSubviews()

        guard let headerView = self.tblProfile.tableHeaderView else {
            return
        }

        let size = headerView.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize)

        if headerView.frame.size.height != size.height {
            headerView.frame.size.height = size.height
            self.tblProfile.tableHeaderView = headerView
            self.tblProfile.layoutIfNeeded()
        }
    }

Note : You need to give all subview's constraints form top, bottom, leading, trailing. So it will get whole required size.

Reference taken from : https://useyourloaf.com/blog/variable-height-table-view-header/

Hardik Thakkar
  • 15,269
  • 2
  • 94
  • 81
1

If custom headerView is designed using autolayout and headerView needs to be updated after web-fetch or similar lazy task. then in iOS-Swift I did this and got my headerView updated using bellow code:

//to reload your cell data
self.tableView.reloadData()
DispatchQueue.main.async {
// this is needed to update a specific tableview's headerview layout on main queue otherwise it's won't update perfectly cause reloaddata() is called
  self.tableView.beginUpdates()
  self.tableView.endUpdates()
}
Rafat touqir Rafsun
  • 2,777
  • 28
  • 24
1

Setting the height for header view property tableView.tableHeaderView in viewDidLoad seems not work, the header view height still not change as expected.

After fighting against this issue for many tries. I found that, you can change the height by invoking the header view create logic inside the - (void)didMoveToParentViewController:(UIViewController *)parent method.

So the example code would look like this:

- (void)didMoveToParentViewController:(UIViewController *)parent {
    [super didMoveToParentViewController:parent];

    if ( _tableView.tableHeaderView == nil ) {
        UIView *header = [[[UINib nibWithNibName:@"your header view" bundle:nil] instantiateWithOwner:self options:nil] firstObject];

        header.frame = CGRectMake(0, 0, CGRectGetWidth([UIScreen mainScreen].bounds), HeaderViewHeight);

        [_tableView setTableHeaderView:header];
    }
}
Enix
  • 4,415
  • 1
  • 24
  • 37
0

I have implemented animated height change of the table's header to expand to overall screen when tapped. However, the code can help in other cases:

// Swift
@IBAction func tapped(sender: UITapGestureRecognizer) {

    self.tableView.beginUpdates()       // Required to update cells. 

    // Collapse table header to original height
    if isHeaderExpandedToFullScreen {   

        UIView.animateWithDuration(0.5, animations: { () -> Void in
            self.scrollView.frame.size.height = 110   // original height in my case is 110
        })

    }
    // Expand table header to overall screen            
    else {      
        let screenSize = self.view.frame           // "screen" size

        UIView.animateWithDuration(0.5, animations: { () -> Void in
            self.scrollView.frame.size.height = screenSize.height
        })
    }

    self.tableView.endUpdates()  // Required to update cells. 

    isHeaderExpandedToFullScreen= !isHeaderExpandedToFullScreen  // Toggle
}
Alexander Volkov
  • 7,904
  • 1
  • 47
  • 44
0

UITableView resizing header - UISearchBar with Scope Bar

I wanted a UITableView with a UISearchBar as the header to the table so I have a hierarchy that looks like this

UITableView
  |
  |--> UIView
  |     |--> UISearchBar
  |
  |--> UITableViewCells

UISearchBarDelegate methods

As has been stated elsewhere, if you don't setTableViewHeader after changing it, nothing will happen.

- (BOOL)searchBarShouldBeginEditing:(UISearchBar *)searchBar
{
    searchBar.showsScopeBar = YES;
    [UIView animateWithDuration:0.2f animations:^{
        [searchBar sizeToFit];
        CGFloat height = CGRectGetHeight(searchBar.frame);

        CGRect frame = self.tableView.tableHeaderView.frame;
        frame.size.height = height;
        self.tableHeaderView.frame = frame;
        self.tableView.tableHeaderView = self.tableHeaderView;
    }];

    [searchBar setShowsCancelButton:YES animated:YES];
    return YES;
}

- (BOOL)searchBarShouldEndEditing:(UISearchBar *)searchBar
{
    searchBar.showsScopeBar = NO;
    [UIView animateWithDuration:0.f animations:^{
        [searchBar sizeToFit];

        CGFloat height = CGRectGetHeight(searchBar.frame);

        CGRect frame = self.tableView.tableHeaderView.frame;
        frame.size.height = height;
        self.tableHeaderView.frame = frame;
        self.tableView.tableHeaderView = self.tableHeaderView;
    }];

    [searchBar setShowsCancelButton:NO animated:YES];
    return YES;
}
Cameron Lowell Palmer
  • 21,528
  • 7
  • 125
  • 126
0

I found the initWithFrame initializer of a UIView doesn't properly honor the rect I pass in. Hence, I did the following which worked perfectly:

 - (id)initWithFrame:(CGRect)aRect {

    CGRect frame = [[UIScreen mainScreen] applicationFrame];

    if ((self = [super initWithFrame:CGRectZero])) {

        // Ugly initialization behavior - initWithFrame will not properly honor the frame we pass
        self.frame = CGRectMake(0, 0, frame.size.width, 200);

        // ...
    }
}

The advantage of this is it is better encapsulated into your view code.

0

Obviously, by now Apple should have implemented UITableViewAutomaticDimension for tableHeaderView & tableFooterView...

The following seems to work for me using layout contraint(s):

CGSize   s  = [ self  systemLayoutSizeFittingSize : UILayoutFittingCompressedSize ];
CGRect   f  = [ self  frame ];

f.size   = s;

[ self  setFrame : f ];
digitaldaemon
  • 141
  • 1
  • 2
0

If your tableHeaderView is a content adjustable webView,you can try:

[self.webView.scrollView addObserver:self forKeyPath:@"contentSize" options:NSKeyValueObservingOptionNew context:nil];

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
    self.webView.height = self.webView.scrollView.contentSize.height;
    self.tableView.tableHeaderView = self.webView;
}

I tested it on iOS9 and iOS11,worked well.

无夜之星辰
  • 5,426
  • 4
  • 25
  • 48
-3

Did you try [self.tableView reloadData] after changing the height?

codelogic
  • 71,764
  • 9
  • 59
  • 54
  • 1
    It's about the tableHeaderView, which is a static view. `- [UITableView reloadData]` is only intended for the dynamic views (cells) and also the **sectionHeaders** which you obviously thought were meant ;) – Julian F. Weinert Jul 16 '15 at 11:29