Edited at

UIScrollView(横)に複数のUITableViewを配置してpagingする。

More than 5 years have passed since last update.

UIScrollViewをpagingで横スクロールする仕様にしつつ、その上にUITableViewを複数配置してそれぞれを縦にスクロール可能にする方法です。


1. UIScrollViewのサブクラスを用意します。


PagingScrollView.m

#import <UIKit/UIKit.h>


@interface PagingScrollView : UIScrollView
@end


2. 大元のViewControllerへPagingScrollViewをAddして、その上にtableViewを並べる。


MasterViewController.m


#define TABLE_WIDTH 106.f

- (void)viewDidAppear:(BOOL)animated {

int numberOfTables = 5;

CGFloat height = self.view.bounds.size.height;
CGRect tableBounds = CGRectMake(0.0f, 50.f, TABLE_WIDTH, height);

PagingScrollView *scrollView = [[PagingScrollView alloc] initWithFrame:self.view.bounds];
scrollView.contentSize = CGSizeMake(TABLE_WIDTH * numberOfTables, height);
scrollView.pagingEnabled = YES;
scrollView.bounds = tableBounds; // scrollViewのページングをTABLE_WIDTH単位に。
scrollView.clipsToBounds = NO; // 非表示になっているtableBounds外を表示。
scrollView.backgroundColor = [UIColor redColor];
[self.view addSubview:scrollView];

// 5つのtableViewを横に並べる
CGRect tableFrame = tableBounds;
tableFrame.origin.x = 0.f;
for (int i = 0; i < numberOfTables; i++) {

UITableView *tableView = [[UITableView alloc] initWithFrame:tableFrame style:UITableViewStylePlain];
switch (i) {
case 0:
tableView.backgroundColor = [UIColor yellowColor];
break;
case 1:
tableView.backgroundColor = [UIColor blueColor];
break;
case 2:
tableView.backgroundColor = [UIColor brownColor];
break;
case 3:
tableView.backgroundColor = [UIColor greenColor];
break;
case 4:
tableView.backgroundColor = [UIColor cyanColor];
break;
default:
break;
}
[scrollView addSubview:tableView];

tableFrame.origin.x += TABLE_WIDTH;
}
}


※ テスト用として分かりやすいようにbackgroundColorを付けています。


3. ScrollViewのタッチ検出範囲を拡張する。

2の状態ではscrollView.boundsで指定した領域(赤い枠)外のタッチイベントに反応出来ないため、PagingScrollViewのhitTestをオーバーライドして対応します。


PagingScrollView.m

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {

for (UIView *subview in self.subviews) {
if (CGRectContainsPoint(subview.frame, point))
return subview;
}
return self;
}


上のように[(UIView*)hitTest:withEvent:]をオーバーライドすることで、scrollView.bounds外のタッチイベントも受け取れるようになります。

第一引数のpointはtouchPointですので、その位置にサブビュー(この場合はtableView)があればそれを返してあげることでレスボンダチェーンをtableViewに返すことが出来ます。

通常、hitTestをオーバーライドする場合は以下のようにする場合が多いようです。


hitTestOverrideView.m


- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {

UIView *hitView = [super hitTest:point withEvent:event];
if (hitView == self)
return anotherView;

return hitView;
}


ですが、今回はscrollViewが全画面表示 + bounds指定されていることが前提ですので[super hitTest:...]を返してしまうとbounds外へのタップ時はhitViewがnilになり、レスポンダは最下部のmasterViewController.viewに移ってしまいます。

そこで前述のように、touchPoint上にtableViewがない場合はself(PagingScrollView)を返してあげることで、tableViewが配置されていない場所へのtouchでも横スクロールが可能になります。



補足

以上の形で指定サイズのpagingとtableViewの縦スクロールは可能になります。

ですが、UIScrollViewに一度にたくさんのUITableViewをAddすることはメモリの圧迫に繋がる可能性が高いです。

それを防ぐためには、UITableViewが持つ『Cellの再利用によってメモリ使用量を抑える仕様』をPagingScrollViewでも独自に実装する必要がありますのでご注意を!

また別の実装として、下地のScrollViewを90度回転させた横向きのUITableViewにして、そのcellに-90度回転させた縦向きのUITableViewをAddする。という方法もあります。

この場合は独自のリユース処理を書かなくても良くなるので楽になります。

また、iOS6以降であればUICollectionViewを使うという手も。