Edited at

UITableViewまわりのきほん

毎度コピペがめんどいのでまとめておく。

しょっちゅう自分で参照してるのでちょこちょこ編集する。


UITableViewController


delegate

storyboard上でもよい

self.tableView.delegate = self;

self.tableView.dataSource = self;


Cellまわり


  • Section数返し

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView{

return 1;
}


  • Cell数返し

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{

return 1;
}


  • Cell返し

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{

static NSString* CellIdentifier = @"Cell";
UITableViewCell* cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
/*
//storyboardでひもづけしてないときに自己生成でリユースにひもづける
UITableViewCell* cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if(!cell){
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}*/

cell.clipsToBounds = YES;//frameサイズ外を描画しない
return cell;
}


  • Cell高さ返し

- (CGFloat)tableView:(UITableView*)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{

return 55;
}


  • Cell選択時

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{

[tableView deselectRowAtIndexPath:indexPath animated:YES]; // 選択状態の解除
UITableViewCell* cell = [tableView cellForRowAtIndexPath:indexPath];
}


  • Cellアクセサリボタン選択時

- (void)tableView:(UITableView *)tableView accessoryButtonTappedForRowWithIndexPath:(NSIndexPath *)indexPath{

UITableViewCell* cell = [tableView cellForRowAtIndexPath:indexPath];
}


Headerまわり


  • UIView返し

dequeueReusableCellWithIdentifierは使わないほうが良い。

segueで戻ってきた時リユースに失敗する。

xibからView返したほうが良い。

- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section{

NSArray* views = [[NSBundle mainBundle] loadNibNamed:xibName owner:nil options:nil];
UIView* view = [views objectAtIndex:0];
return view;
}


  • header高さ返し

- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section{

return 20;
}


UITableViewCell


きほん

dequeueReusableCellWithIdentifierでセル生成時にindexPath.rowとtableviewの参照を渡しておくと後々色々しあわせ。


アクセサリのカスタマイズ

accessoryViewにボタンをいれる。storyboardで紐付けてもよい。

アクセサリの押下を取る場合、accessoryViewに無理に紐付けなくても良い。

UIButtonにaddTargetを設定して押されたイベントを取得する。

押されたらTableViewControllerのaccessoryButtonTappedForRowWithIndexPath

に通達。あたかもアクセサリが押されたようなイベント通達を偽装できる。

[button addTarget:self

action:@selector(touchButton:event:)
forControlEvents:UIControlEventTouchUpInside];
self.accessoryView = button;//なくてもよい

- (void)touchButton:(id)sender event:(id)event{

NSIndexPath * indexPath = [self.tableView indexPathForRowAtPoint: [[[event touchesForView: sender] anyObject] locationInView: self.tableView]];
if ( indexPath == nil ) {
return;
}

[self.tableView.delegate tableView: self.tableView accessoryButtonTappedForRowWithIndexPath: indexPath];
}


小ネタ


上下の余計なセル消し

viewDidLoad時に高さ0のViewを当てれば良い。

大抵footerだけで事足りる。

UIView *view = [[UIView alloc] initWithFrame:CGRectZero];

view.backgroundColor = [UIColor clearColor];
//[self.tableView setTableHeaderView:view];
[self.tableView setTableFooterView:view];


reloadData と beginUpdates,endUpdates


  • 即時反映はreloadData、アニメーション絡む場合はbegenUpdates〜


reloadData


  1. viewWillAppearに仕込む

- (void)viewWillAppear:(BOOL)animated {

[self.tableView reloadData];
[super viewWillAppear:animated];
}


beginUpdates,endUpdates


  1. セルの削除、追加に使う

[self.tableView beginUpdates];

NSIndexPath* path1 = [NSIndexPath indexPathForRow:0 inSection:0];
[self.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:path1] withRowAnimation:UITableViewRowAnimationTop];
[self.tableView endUpdates];


ジェスチャー


  • pan等ジャスチャー時の反応にはreloadData、離してからはbeginUpdates〜

  • cellForRowAtIndexPathはコストが高いのでなるべく使わない。


reloadData等更新したのに値が上手く反映されないとき

-(void)reloadDataWithComplete:(void(^)(void))waitBlock {

[self.tableView reloadData];
if(waitBlock){
waitBlock();
}
}

ブロック構文入れたりすると解決したりする。


  • 上記の現象の起因はスレッドによるところが大きい。

  • 特定スレッドまで消化しないと値が反映されない。

  • スレッド分岐された処理にてreloadDataすると、反映がすごい遅い場合がある。(フリーズ or レンダリングされないような現象)

  • BreakPointを置くと、どのスレッドで何やっているか分かるのでViewの更新はメインスレッドでやるようにするとよい。

dispatch_sync(dispatch_get_main_queue(), ^{

[self.tableView reloadData];
});


TableViewの入れ子状態でのスクロール制御


  • TableView(FrontTableView)の中のtableViewCellにTableView(DeepTableView)を入れた場合、FrontTableViewの操作でDeepTableViewが一切反応しなくなる。scrollEnabled=NOでも起こる。

  • FrontTableViewのhitTestをオーバーライドしてDeepTableViewをhitTestして当たったらDeepTableViewを返すようにするとOKくさい。

  • ScrollViewが入れ子だと何でも起こる。tableViewCellにUIWebViewを入れるなど。

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

UIView *hitView = [super hitTest:point withEvent:event];
//対象のTableViewが入ったCellを取る
UITableViewCell* cell = [self cellForRowAtIndexPath:[NSIndexPath indexPathForRow:1 inSection:0]];
//縦方向にcellのframe位置分ズレるので補正
CGPoint pos = CGPointMake(point.x, point.y - cell.frame.origin.y);
UIView* view = [cell hitTest:pos withEvent:event];
if(view != nil) return cell.deepTableView;

return hitView;
}


高速化のはなし


まとめなど

UITableViewControllerはやることほぼ全部管理できるせいでよく膨れ上がって後々管理しづらくなる。

値の出し入れ、segueの管理等…

デザインの反映、アニメーション、ジェスチャ等入りだすと

子のTableView,TableViewCellのクラスを結局作ることになるので

はじめから3種作って役割仕切ったほうが後々楽なパターンが多い。

あと、親子関係のデータ受け渡しがめんどくさいので、ちゃんとCoreDataつくる。