UITableViewのセクション数と行数を動的に変える方法


UITableViewStyleGroupedにしているUITableViewでたまにやりたくなるけど意外と面倒くさいパターンです。

## まずはenumで各セクションと各行に名前を振る

これは動的じゃない場合も、面倒臭がらずにやっておくと後々楽です。
今作ってるアイテム詳細画面の場合、こんな感じ。

```Objective-C:
// section index
enum {
	kItemDetailSectionImage = 0,
	kItemDetailSectionLike,
	kItemDetailSectionMemo,
	kItemDetailSectionInfo,
	kItemDetailSectionComment,
	kItemDetailSectionAddComment
};
// row index
enum {
	kItemDetailRowName = 0,
	kItemDetailRowImage,
	kItemDetailRowCountry,
	kItemDetailRowLike = 0,
	kItemDetailRowTags = 0,
	kItemDetailRowMemo,
	kItemDetailRowUser,
	kItemDetailRowBoughtAt = 0,
	kItemDetailRowPrice,
	kItemDetailRowLocation,
	kItemDetailRowComments = 0,
	kItemDetailRowAddComment = 0
};
```

## セクション数と行数を計算する処理

条件によってセクション数と行数を変えるため、表示する場所のみを格納した配列をフィールドに用意します。
下の例ではモデルself.itemの各項目が空じゃなかったら表示したいので、その場合のみ配列に番号（上で定義したenum）を追加するようにしています。

```Objective-C:
@interface ItemDetailVC ()
@property(nonatomic, strong) NSMutableArray *sectionIndices;
@property(nonatomic, strong) NSMutableArray *rowIndices;
@end
```

```Objective-C:
- (void)calcSection {
	self.sectionIndices = [[NSMutableArray alloc] init];
	self.rowIndices = [[NSMutableArray alloc] init];
	{
		NSMutableArray *rowTags = [[NSMutableArray alloc] init];
		if (! [Util isEmpty:self.item.name]) {
			[rowTags addObject:@(kItemDetailRowName)];
		}
		if (! [Util isEmpty:self.item.photoUrl]) {
			[rowTags addObject:@(kItemDetailRowImage)];
		}
		if (! [Util isEmpty:self.item.country]) {
			[rowTags addObject:@(kItemDetailRowCountry)];
		}
		if (rowTags.count > 0) {
			[self.rowIndices addObject:rowTags];
			[self.sectionIndices addObject:@(kItemDetailSectionImage)];
		}
	}
	{
		NSMutableArray *rowTags = [[NSMutableArray alloc] init];
		[rowTags addObject:@(kItemDetailRowLike)];
		
		if (rowTags.count > 0) {
			[self.rowIndices addObject:rowTags];
			[self.sectionIndices addObject:@(kItemDetailSectionLike)];
		}
	}
	{
		NSMutableArray *rowTags = [[NSMutableArray alloc] init];
		if (self.item.tags.count > 0) {
			[rowTags addObject:@(kItemDetailRowTags)];
		}
		if (! [Util isEmpty:self.item.memo]) {
			[rowTags addObject:@(kItemDetailRowMemo)];
		}
		if (! [Util isEmpty:self.item.user]) {
			[rowTags addObject:@(kItemDetailRowUser)];
		}
		
		if (rowTags.count > 0) {
			[self.rowIndices addObject:rowTags];
			[self.sectionIndices addObject:@(kItemDetailSectionMemo)];
		}
	}
	...
}
```

でモデルが更新される度にセクション数と行数を計算し直すようにします。

```Objective-C:
- (void) refresh:(int)itemId {
	NSDictionary *params = @{
		@"id" : [NSNumber numberWithInt:itemId]
	};
	
	__weak ItemDetailVC *wself = self;
	
	[[APIClient sharedClient] getItemDetail:params success:^(id res) {
		wself.item = [[Item alloc] initWithDictionary:res];
		[wself calcSection];
		[wself.tableView reloadData];
	}];
}
```

## UITableViewDataSourceのメソッド修正

セクション数、行数を用意した配列から読むように変更します。

```Objective-C:
- (NSInteger) numberOfSectionsInTableView:(UITableView *)tableView {
	return [self.sectionIndices count];
}
```

```Objective-C:
- (NSInteger) tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
	return [self.rowIndices[section] count];
}
```

セルの内容決定についても、以下のようにindexPathを調整してやる必要があります。

```Objective-C:
- (UITableViewCell*) tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
	int section = [self.sectionIndices[indexPath.section] intValue];
	int row = [self.rowIndices[indexPath.section][indexPath.row] intValue];

	NSString *cellId = @"ItemDetailCell";
	UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellId];
	if (cell == nil) {
		cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:cellId];
		cell.selectionStyle = UITableViewCellSelectionStyleNone;
	}

	switch (section) {
		case kItemDetailSectionImage:
			switch (row) {
				case kItemDetailRowName:
					cell.textLabel.text = @"名前";
					cell.detailTextLabel.text = self.item.name;
					break;
				case kItemDetailRowCountry:
					cell.textLabel.text = @"国";
					cell.detailTextLabel.text = self.item.country.name;
					break;
				...
			}	break;
		...
	}
	return cell;
}
```

## 以上
ざっとこんな感じですが、けっこう大変。。
もうちょっとエレガントな方法はないすかねぇ。