UITableViewCellの高さを可変にする方法


チャットやコメントの一覧など、ラベルが複数行になるためセルの高さを可変にしたい場合の対応方法です。

以下のようなUITableViewCellを継承したカスタムセルMessageCellがあるとします。 ビューに対応したモデルとしてMessageクラスのmessageプロパティを持ちます。
Message.bodyは改行を含む長い文字列が入ることを想定し、bodyを表示するためのbodyLabelを定義します。
セルの高さはheightで参照できるようにします。

```objc:MessageCell.h
@interface MessageCell : UITableViewCell

// model
@property(nonatomic, strong) Message *message;

// view
@property(nonatomic, strong) UILabel *bodyLabel;

// other
@property(nonatomic, readonly) CGFloat height;

@end
```

複数行表示に対応するため、initでbodyLabel.numberOfLinesを0にしておきます。
また、モデルのセッター(setMessage:)でbodyLabel.attributedText経由で本文を設定することで、行間を調整します。
heightメソッドでは、boundingRectWithSize:を使って行の高さを返すようにします。

```objc:MessageCell.m
@implementation MessageCell

-(id) initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString*)reuseIdentifier {
    self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
    if (self) {
		_bodyLabel = [[UILabel alloc] init];
		// 行数制限なし
		_bodyLabel.numberOfLines = 0;
		[self addSubview:_bodyLabel];
    }
    return self;
}

-(void) layoutSubviews {
	[super layoutSubviews];
	
	// パディング
	CGFloat pad = 5;

	// 本文ラベルのサイズ調整
	CGFloat bodyLabelW = self.bounds.size.width - pad * 2;
	CGFloat bodyLabelH = self.bounds.size.height - pad * 2;
	self.bodyLabel.frame = CGRectMake(pad, pad, bodyLabelW, bodyLabelH);
}

-(void) setMessage:(Message*)message {
	_message = message;

	// 行間設定
	CGFloat lineHeight = 16.0;
	NSMutableParagraphStyle *paragrahStyle = [[NSMutableParagraphStyle alloc] init];
	paragrahStyle.minimumLineHeight = lineHeight;
	paragrahStyle.maximumLineHeight = lineHeight;
	// 本文
	NSString *text = (message.body == nil) ? @"" : message.body;
	UIFont *font = [UIFont fontWithName:@"Helvetica" size:10];
	NSDictionary *attributes = @{NSParagraphStyleAttributeName: paragrahStyle,
								 NSFontAttributeName: font};
	NSAttributedString *atext = [[NSAttributedString alloc] initWithString:text attributes:attributes];
	self.bodyLabel.attributedText = atext;
}

-(CGFloat) height {
	CGFloat pad = 5;
	CGFloat bodyLabelW = self.bodyLabel.bounds.size.width;
	CGSize bodySize = [self.bodyLabel.attributedText boundingRectWithSize:CGSizeMake(bodyLabelW, MAXFLOAT)
															  options:NSStringDrawingUsesLineFragmentOrigin
															  context:nil].size;
	return bodySize.height + pad * 2;
}

@end
```

続いてMessageCellを利用するViewController側の実装です。
tableView:cellForRowAtIndexPath:でmessageプロパティを使い、モデル設定を行います。
最後にtableView:heightForRowAtIndexPath:を実装し、セルの高さをMessageCell.heightを呼び出すことで可変にします。

```objc:ViewController
-(UITableViewCell*) tableView:(UITableView*)tableView cellForRowAtIndexPath:(NSIndexPath*)indexPath {
	NSString *cellId = @"MessageCell";
	MessageCell *cell = [tableView dequeueReusableCellWithIdentifier:cellId];
	
	if (cell == nil) {
		cell = [[MessageCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellId];
	}
	// モデル設定
	cell.message = self.messages[indexPath.row];
	
	return cell;
}

// セルの高さを可変にする
-(CGFloat) tableView:(UITableView*)tableView heightForRowAtIndexPath:(NSIndexPath*)indexPath {
	MessageCell *cell = (MessageCell*)[self tableView:self.tableView cellForRowAtIndexPath:indexPath];
	return cell.height;
}
```