I guess you want something like this:

or this:

I laid out my table view over my map view. I set the table view's contentInset
and contentOffset
like this:
- (void)viewDidLoad {
[super viewDidLoad];
self.tableView.rowHeight = 44;
}
- (void)viewDidLayoutSubviews {
[super viewDidLayoutSubviews];
self.tableView.contentInset = (UIEdgeInsets){ .top = self.view.bounds.size.height - self.tableView.rowHeight };
self.tableView.contentOffset = CGPointMake(0, -self.tableView.contentInset.top);
}
Note that, although the default row height is 44, tableView.rowHeight
return -1 unless you explicitly set it. (Setting it to 44 in the storyboard doesn't change this.)
I used a subclass of UITableView
in which I did two things:
I explicitly set self.backgroundColor = [UIColor clearColor]
. I found that setting the background color to clear in the storyboard didn't work.
I overrode pointInside:withEvent:
:
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
return point.y >= 0 && [super pointInside:point withEvent:event];
}
Note that you don't care about contentInset
here. The table view's contentOffset.y
(which is the same as its bounds.origin.y
) is set to a negative number when its top content inset is exposed. It's set to 0 when the top of item 0 is at the top edge of the table view, which isn't the case when the item as at the bottom edge of the screen.
Another thing you might want is to prevent the table from stopping half-on the screen. If the user drags item 0 halfway up the screen, you want the table to scroll so item 0 is all the way at the top of the screen (if there are sufficient items), and if the user drags item 0 halfway down the screen, you want the table to scroll so just item 0 is showing.
I did that by making my view controller act as the table view's delegate and implementing this delegate method, inherited from UIScrollViewDelegate
:
- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset {
CGFloat yMin = -self.tableView.contentInset.top;
CGFloat yMax = MIN(0, self.tableView.contentSize.height - self.tableView.bounds.size.height);
if (targetContentOffset->y < yMax) {
if (velocity.y < 0) {
targetContentOffset->y = yMin;
} else {
targetContentOffset->y = yMax;
}
}
}
That method is carefully written so that it works for tables too short to fill the screen vertically, and for tables that can fill the screen vertically.
I've uploaded my test project here: https://github.com/mayoff/tableView-over-mapview
Update for side-by-side tables
I don't think side-by-side tables is going to be a good user interface. I think it's going to be confusing. But here's how you do it.
The view hierarchy looks like this:
- Root view
MKMapView
MyScrollView
ScrollContentView
MyTableView
for first table
MyTableView
for second table
MyTableView
for third table
- etc.
The map view and the scroll view have the same frames. The scroll view handles the sideways scrolling and each table view is independently scrollable vertically.
Since the scroll view should only capture touches that land in one of the table views, it needs a custom hitTest:withEvent:
that returns nil
for touches outside any of the table views:
@implementation MyScrollView
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
UIView *hitView = [super hitTest:point withEvent:event];
return hitView == self ? nil : hitView;
}
@end
But this won't actually do the job, because (in my implementation) the scroll view has just one big subview, the ScrollContentView
. So we need to do the same thing in ScrollContentView
:
@implementation ScrollContentView
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
UIView *hitView = [super hitTest:point withEvent:event];
return hitView == self ? nil : hitView;
}
That's sufficient to pass touches down to the map view if they land outside of the tables.
I also use ScrollContentView
to lay out the tables and set the scroll view's content size:
- (void)layoutSubviews {
[super layoutSubviews];
// Layout of subviews horizontally:
// [gutter/2][gutter][subview][gutter][subview][gutter][subview][gutter][gutter/2]
// where 3 * gutter + subview = width of superview
CGSize superSize = self.superview.bounds.size;
CGFloat x = kGutterWidth * 3 / 2;
CGFloat subWidth = superSize.width - kGutterWidth * 3;
for (UITableView *subview in self.subviews) {
subview.frame = CGRectMake(x, 0, subWidth, superSize.height);
x += subWidth + kGutterWidth;
CGFloat topInset = superSize.height - subview.rowHeight;
subview.contentInset = (UIEdgeInsets){ .top = topInset };
subview.contentOffset = CGPointMake(0, -topInset);
}
x += kGutterWidth / 2;
self.frame = CGRectMake(0, 0, x, superSize.height);
((UIScrollView *)self.superview).contentSize = self.bounds.size;
_pageWidth = subWidth + kGutterWidth;
}
I also made my view controller be the scroll view's delegate, and implemented a delegate method to force the scroll view to stop on “page” (table) boundaries:
- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset {
CGFloat pageWidth = contentView.pageWidth;
// Force scroll view to stop on a page boundary.
CGFloat pageNumber = targetContentOffset->x / pageWidth;
if (velocity.x < 0) {
pageNumber = floor(pageNumber);
} else {
pageNumber = ceil(pageNumber);
}
pageNumber = MAX(0, MIN(pageNumber, contentView.subviews.count - 1));
targetContentOffset->x = pageNumber * pageWidth;
}
The result:

I've updated the git repository with this version.