Okay, here's an answer that has the benefit of getting built and tried out.
I found it too hard to manipulate the frame of the table's actual header view, so I added a subview to the table above the rows. In order for that view to show up as a regular table header, I gave the table a fixed sized, transparently colored header view.
The main idea is like what I answered above: using the table's content offset as the parameter for modifying the image view frame, and the imageView's content mode (corrected to UIViewContentModeScaleAspectFill) to provide the zooming effect as the frame changes.
Here's the whole view controller. This is built from a storyboard where the view controller is inside a navigation controller. It has nothing more than a table view filling its view, with the datasource and delegate set.
#import "ViewController.h"
// how much of the image to show when the table is un-scrolled
#define HEADER_HEIGHT (100.0)
// the height of the image scaled down to fit in the header. the real image can/should be taller than this
// i tested this with a 600x400 image
#define SCALED_IMAGE_HEIGHT (200.0)
// zoom image up to this offset
#define MAX_ZOOM (150.0)
@interface ViewController () <UITableViewDataSource, UITableViewDelegate>
@property(weak,nonatomic) IBOutlet UITableView *tableView;
@end
@implementation ViewController
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
// build the header in view will appear after other layout constraints are applied
UIImageView *headerView = (UIImageView *)[self.tableView viewWithTag:99];
if (!headerView) {
headerView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"landscape.png"]];
headerView.tag = 99;
headerView.contentMode = UIViewContentModeScaleAspectFill;
headerView.clipsToBounds = YES;
headerView.frame = CGRectMake(0, HEADER_HEIGHT, self.view.bounds.size.width, SCALED_IMAGE_HEIGHT);
[self.tableView addSubview:headerView];
}
}
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
CGFloat offsetY = -self.tableView.contentOffset.y - 64;
// minus 64 is kind of a bummer here. this calc wants the offset to be 0
// when no scrolling has happened. for some reason my table view starts at -64
CGFloat clamped = MIN(MAX(offsetY, 0), MAX_ZOOM);
CGFloat origin = -HEADER_HEIGHT - clamped;
CGFloat height = SCALED_IMAGE_HEIGHT + clamped;
UIImageView *headerView = (UIImageView *)[self.tableView viewWithTag:99];
CGRect frame = headerView.frame;
frame.origin.y = origin;
frame.size.height = height;
headerView.frame = frame;
}
// this is a trick to make the view above the header visible: make the table header a clear UIView
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {
UIView *view = [[UIView alloc] initWithFrame:CGRectMake(0, 0, tableView.bounds.size.width, HEADER_HEIGHT)];
view.backgroundColor = [UIColor clearColor];
return view;
}
- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section {
return HEADER_HEIGHT;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return 30;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"Cell"];
cell.textLabel.text = [NSString stringWithFormat:@"Cell %ld", indexPath.row];
return cell;
}
@end