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

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

```objc: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`でキーボードの通知登録

```objc: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関数でデフォルトの高さと、可変する高さを比較して状況に応じて返す

```objc: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`

```objc: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の高さに合わせて更新していく 

```objc: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 が隠れてしまうとき、増えた高さの分だけ下にスクロール
 - **ここでステータスバーの高さを考慮しないと、ステータスバーの高さ分だけ上下にバインバインする**
 - なぜステータスバーの高さを考慮しないといけないのか、まだよくわかってない


```objc: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に貼った
- https://gist.github.com/masashi-sutou/0013575ed86ab638dd8a


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