1

I have a table view controller, and I drag-and-drop an additional UIView on a storyboard to make a header, it becomes a subview of UITableView.

I do not make it an "official" section header with viewForHeaderInSection: and friends (I'll explain why), it's just a subview of a table view, which appears on top of the table, alongside internal table view wrapper and refresh control views in the hierarchy of views.

The hierarchy looks like this:

UITableView 0,0 320x455
  UIRefreshControl 0,0 320x60
  UITableViewWrapperView 0,0 320x455
    cell 0,190 320x44
    cell 0,146 320x44
    cell 0,102 320x44
    cell 0, 58 320x44
  custom UIView 0,0 320x58

Now, the header view is working great... until I want to resize it. The idea is, the header view has a text field, which, when focused, causes additional form fields to expand under it, pushing table cells down.

The problem is that I have little control over laying out of cells. While resizing the custom view (by changing its frame), it overlays the table wrapper view.

So, why don't I just make it a section header view and resize it via heightForHeaderInSection:? The problem is that when a text field is focused or typed into, I need to call [self.tableView reloadData] to refresh the data in the table (search-as-you-type kind of thing), which reloads the headers too and makes text field lose focus.

So to sum it up,

  1. How do I actually make a table header that can dynamically resize and push down table cells?
  2. How can I keep focus of the text view inside the table header while reloading said table?
Community
  • 1
  • 1
Alex B
  • 82,554
  • 44
  • 203
  • 280
  • How about trying it this way -- give your table two sections, with the first section having just one cell, that cell would be your "header". When you need to reload, use reloadSections: to only reload section 1, so your "header" doesn't get reloaded. – rdelmar Nov 28 '13 at 16:58
  • @rdelmar I entertained that idea. *Another* thing I didn't mention is that I have a base controller class that's in use by other similar views, I'd have to refactor it for a 2-section table view, so I was avoiding that. – Alex B Nov 29 '13 at 00:08
  • @rdelmar I just realised that this won't work either because I need to reload the top section for header height change to take effect, which, of course, destroys UITextField focus... – Alex B Nov 29 '13 at 05:09
  • Do you want this header to be a subview of the table? Can you use a a UIView controller, and have this view be outside the table view, or does this not work with your base controller setup? – rdelmar Nov 29 '13 at 05:59
  • @rdelmar I'm not too fussed about the structure as long as it does what I want. However, I found a simpler solution (see answer). – Alex B Nov 29 '13 at 13:08
  • Instead of using `reloadData`, have you considered using `beginUpdates` and `endUpdates`? I'm not sure I have understand your dilemma after a brisk first read, but the effect you are looking for works decently with "updates". – n00bProgrammer Nov 29 '13 at 13:12
  • @n00bProgrammer I just want to change one row's height. I'm not sure beginUpdates/endUpdates is applicable here, as I'm not actually changing rows (at least not in all scenarios). – Alex B Nov 29 '13 at 13:23

2 Answers2

1

A solution with tableHeaderView (unrelated to section header view):

  1. Assign a dragged view (or any view) to self.tableView.tableHeaderView.
  2. If you want to change its height, change its frame and assign it again.
  3. The view will be redrawn with new height, yet the text field will keep its focus.
Community
  • 1
  • 1
Alex B
  • 82,554
  • 44
  • 203
  • 280
  • I noticed that you don't need to do that initial assign if you drag the view into the table view in IB -- it you log self.tableView.tableHeaderView, you'll find that the dragged in view is automatically the table header view. You can also animate the header expansion using an NSTimer if you want, by assigning it again after each incremental change it height. – rdelmar Nov 29 '13 at 16:18
0

I also found that you need to reassign the header view to the table view's tableHeaderView property to get it to expand properly. I next tried to figure out how to animate that effect. I could do it with an NSTimer, resetting the table view's tableHeaderView property after every incremental size increase, but this looked a little jerky. Trying to animate it within an animation block, led to several strange effects -- if I changed the height (and reassigned the tableHeaderView property) in an animation block, it worked ok for small size increases, but larger ones caused some of the cells to disappear off the bottom of the screen when the animation started (although at the end of the animation, all was well). Eventually, I found that changing the height of the table view itself, and changing it back to its original size in the completion block, as well as changing the header's size (again) in the completion block made everything work.

-(void)growHeaderBy:(NSInteger) delta {
    [UIView animateWithDuration:.6 animations:^{
        self.tableView.frame = CGRectMake(self.tableView.frame.origin.x, self.tableView.frame.origin.y, self.tableView.frame.size.width, self.tableView.frame.size.height + delta);
        self.header.frame = CGRectMake(self.headerFrame.origin.x, self.headerFrame.origin.y, self.headerFrame.size.width, self.headerFrame.size.height + delta);
        self.tableView.tableHeaderView = self.header;
    } completion:^(BOOL finished) {
        self.tableView.frame = CGRectMake(self.tableView.frame.origin.x, self.tableView.frame.origin.y, self.tableView.frame.size.width, self.tableView.frame.size.height - delta);
        self.header.frame = CGRectMake(self.headerFrame.origin.x, self.headerFrame.origin.y, self.headerFrame.size.width, self.headerFrame.size.height + delta);
        self.tableView.tableHeaderView = self.header;
    }];
}

headerFrame is a property that's set to the original frame of the header view in viewDidLoad.

rdelmar
  • 103,982
  • 12
  • 207
  • 218