Edited at

[Objective-C] テキスト量に応じてTextViewを可変対応させる

More than 5 years have passed since last update.

ユーザ投稿型のアプリや、ユーザ操作に応じてフォントサイズが変わったりなどした場合にTextViewのサイズを変更する方法です。

方法は以下の3つ。



  • NSString#sizeWithFont:constrainedToSize:メソッドを使う


  • NSAttributedString#boundingRectWithSize:options:attributes:context:メソッドを使う


  • contentSizeプロパティを使う

1, 2についてはUITextViewの場合はスクロールバー分の余白の都合で、どうやら微妙にサイズが小さくなってしまい、テキストが切れてしまう問題があるようなので注意が必要です。

(余白分をハードコードしてしまえば対応できますが、将来、スクロールバーのサイズが変更されるなどした場合は問題になるのでちょっと悩みどころです)

ちなみに、一番目のメソッドはiOS7からdeprecatedになりました。iOS7以降は二番目を使うほうがよいです。


[追記]

UITextView.textContainerのプロパティをいじるとUILabelと同様の見た目にすることができました。具体的には以下。

aTextView.textContainer.lineFragmentPadding = 0;

aTextView.textContainerInset = UIEdgeInsetsZero;

ひとつ目が左右の余白を、ふたつ目が上下の余白を0にしてくれます。

どうやらNSAttributedString#boundingRectWithSize:options:attributes:context:メソッドで返されるサイズは、純粋に、設定された文字列が収まる矩形サイズのようです。

そのため、上記設定やcontentInsetsなどの余白を追加した場合は、実際にレンダリングされるサイズとの誤差が生じることになります。

(CSSのwidthとpaddingの関係が分かる人はそっちがイメージしやすいです)



NSString#sizeWithFont:constrainedToSize:メソッド

UITextView *aTextView = [[UITextView alloc] initWithFrame:CGRectMake(0, 0, 200, 0)];

aTextView.text = @"...some long text...";

// 200, 400のサイズ内に収まるサイズを取得
CGSize size = [aTextView.text sizeWithFont:self.textView.font
constrainedToSize:CGSizeMake(200, 400)];

CGRect frame = aTextView.frame;
frame.size.height = size.height;
aTextView.frame = frame;


NSAttributedString#boundingRectWithSize:options:attributes:context:メソッド

CGSize checkSize = CGSizeMake(200, 400);

NSDictionary *attributes = @{NSForegroundColorAttributeName:[UIColor whiteColor],
NSFontAttributeName:[UIFont systemFontOfSize:12.0]};
NSAttributedString *string = [[NSAttributedString alloc] initWithString:@"some string"
attributes:attributes];

UITextView *aTextView = [[UITextView alloc] init];
aTextView.attributedText = string;

CGRect textFrame = [string boundingRectWithSize:checkSize
options:NSStringDrawingUsesLineFragmentOrigin
context:nil];
aTextView.frame = textFrame;


contentSizeプロパティ

UITextView *aTextView = [[UITextView alloc] initWithFrame:CGRectMake(0, 0, 200, 0)];

aTextView.text = @"some text.";

// 設定されたテキストのレンダリング結果を得るため、若干の遅延を挟む
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(10 * NSEC_PER_MSEC)), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
CGRect newFrame = aTextView.frame;
newFrame.size.height = aTextView.contentSize.height;
aTextView.frame = newFrame;
});