LoginSignup
21

More than 5 years have passed since last update.

Storyboardを使わず、UITableViewに高さが可変でキーボードに隠れないUITextViewを作りたい

Last updated at Posted at 2016-02-06

これはなに?

  • タイトルどおり
  • Storyboardを使ってやる記事はあるが、使わずにやる記事はなかったので
  • http://qiita.com/mishimay/items/619f9ce60b4fabc1612f
  • なんとか出来たが、色々と改善点とかあり、もっと簡単にできるかもしれません
  • デモのビルドターゲットはiOS7です。
  • iOS7をサポート対象に含めない場合は、insetの部分など不要な処理が幾つか出ますのでそこは良しなにでお願いします。

やり方

1. viewDidLoadで UITableView と UITextView の作成

  • UITableViewのスタイルは、UITableViewStyleGrouped
  • UITextViewは、cellForRowAtIndexPathでセルに貼り付け
ViewController.m
// テーブル作成
CGRect tableViewFrame = CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height);
self.tableView = [[UITableView alloc] initWithFrame:tableViewFrame
                                              style:UITableViewStyleGrouped];
self.tableView.dataSource = self;
self.tableView.delegate = self;
[self.view addSubview:self.tableView];

// テキストビュー作成
CGRect textVieFrame = CGRectMake(0, 0,
                                 self.view.frame.size.width,
                                 [self textViewHeightWithText:nil
                                                        width:self.view.frame.size.width]);
self.textView = [[UITextView alloc] initWithFrame:textVieFrame];
self.textView.font = [UIFont preferredFontForTextStyle:UIFontTextStyleBody];
self.textView.scrollEnabled = NO;
self.textView.returnKeyType = UIReturnKeyDefault;
self.textView.delegate = self;

2. viewDidLoadでキーボードの通知登録

ViewController.m
// キーボード表示・非表示の通知登録
[[NSNotificationCenter defaultCenter] addObserver:self
                                         selector:@selector(keyboardWillShow:)
                                             name:UIKeyboardWillShowNotification
                                           object:nil];

[[NSNotificationCenter defaultCenter] addObserver:self
                                         selector:@selector(keyboardWillHide:)
                                             name:UIKeyboardWillHideNotification
                                           object:nil];

3. UITextView の高さを算出

  • [textView sizeToFit];で入力内容に応じて高さを合わせる
  • MAX関数でデフォルトの高さと、可変する高さを比較して状況に応じて返す
ViewController.m
- (CGFloat)textViewHeightWithText:(NSString *)text width:(CGFloat)width
{
    // 入力内容に応じて高さを算出
    UITextView *textView = [[UITextView alloc] init];
    textView.font = [UIFont preferredFontForTextStyle:UIFontTextStyleBody];
    textView.text = text;
    textView.frame = CGRectMake(0, 0, width, 0);
    [textView sizeToFit];

    return MAX(textView.frame.size.height, 80);
}

4. キーボードの表示・非表示の通知

  • - (void)setupInsetsが大事
    • inset.top にステータスバーとナビゲージョンバーの高さを設定する
    • inset.bottom にキーボードの高さを設定する
  • キーボードが表示・非表示されるタイミングで下記の値を切り替える
    • self.tableView.contentInset
    • self.tableView.scrollIndicatorInsets
ViewController.m
- (void)keyboardWillShow:(NSNotification *)notification
{
    CGRect keyboardBounds = [[notification.userInfo objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue];
    self.keyboardHeight = keyboardBounds.size.height;

    // 必要な高さを取得
    self.statusBarHeight = [UIApplication sharedApplication].statusBarFrame.size.height;
    self.barHeight = self.statusBarHeight + self.navigationController.navigationBar.frame.size.height;

    // キーボードでテキストビューが隠れないようにする
    [self setupInsets];
}

- (void)keyboardWillHide:(NSNotification *)notification
{
    self.keyboardHeight = 0;

    // 元に戻す
    [self setupInsets];
}

- (void)setupInsets
{
    UIEdgeInsets insets = UIEdgeInsetsMake(self.barHeight,0,self.keyboardHeight,0);
    self.tableView.contentInset = insets;
    self.tableView.scrollIndicatorInsets = insets;
}

5. UITableView のヘッダーの高さとセルの高さを指定

  • ヘッダーの高さは計算しやすいように固定にする
    • 固定にせずヘッダーの高さを算出できる方法があれば教えて欲しい
  • セルの高さはUITextViewの高さに合わせて更新していく
ViewController.m
#pragma mark - <UITableViewDelegate>
- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section
{
    return 60;
}

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
    return [self textViewHeightWithText:self.textView.text
                                  width:self.view.frame.size.width];
}

6. UITextView に文字が入力される度に高さを更新

  • デフォルトの高さを超えたとき、セルの高さを [self.tableView beginUpdates];[self.tableView endUpdates];で更新
  • キーボードで UITextView が隠れてしまうとき、増えた高さの分だけ下にスクロール
    • ここでステータスバーの高さを考慮しないと、ステータスバーの高さ分だけ上下にバインバインする
    • なぜステータスバーの高さを考慮しないといけないのか、まだよくわかってない
ViewController.m
#pragma mark - <UITextViewDelegate>
- (void)textViewDidChange:(UITextView *)textView
{
    [self updateTextViewHeight];
}

#pragma mark - 高さの調整
- (void)updateTextViewHeight
{
    CGFloat currentHeight = self.textView.frame.size.height;
    CGFloat newHeight = [self textViewHeightWithText:self.textView.text
                                               width:self.textView.frame.size.width];

    // デフォルトの高さを超えたとき
    if (self.textView.frame.size.height != newHeight) {
        CGRect newFrame = CGRectMake(self.textView.frame.origin.x,self.textView.frame.origin.y,
                                     self.textView.frame.size.width,newHeight);

        [self.textView setFrame:newFrame];
        [self.tableView beginUpdates];
        [self.tableView endUpdates];
    }

    // UITextViewがキーボードで隠れるとき
    CGFloat heightForHeaderInSection = 60;
    if ((self.barHeight + heightForHeaderInSection + newHeight) >
         self.view.frame.size.height - self.statusBarHeight - self.keyboardHeight) {

        // 増えた高さの分だけ下にスクロール
        [self.tableView setContentOffset:CGPointMake(0, self.tableView.contentOffset.y + (newHeight - currentHeight)) animated:YES];
    }
}

ソースコードはGistに貼った

改善点

  • 高さを更新するタイミングでUITableViewCellの底と、UITextViewの底が滑らかに表示されない
  • どうしたらいいのか分からんくて妥協

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
21