UICollectionViewCell（カスタムセル）で通知を受け取り、キーボードが出た時に良い感じにスクロールさせて戻る。

##以前の記事
[UICollectionViewのカスタムセル上にて、キーボードが出てきた時に画面を良い感じにスクロールさせる。](https://qiita.com/godaikun_method/items/6370dd7c3a8cc656ea03)

こちらの記事を書きましたが、セルでキーボード通知を受け取るなど改良しました。
ただし、実装の都合上画面上に1件しか表示されないセルという設計のみ適用可能です。
理由は**後述**いたします。


###手順
UICollectionViewDelegateを採用しているクラスに下記記述を行う。

```
- (void)collectionView:(UICollectionView *)collectionView willDisplayCell:(UICollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath{

    ///通知登録
    NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
    [center addObserver:cell
               selector:@selector(keyboardWillShow:)
                   name:UIKeyboardWillShowNotification
                 object:nil];
    
    [center addObserver:cell
               selector:@selector(keyboardWillHide:)
                   name:UIKeyboardWillHideNotification
                 object:nil];
}

- (void)collectionView:(UICollectionView *)collectionView didEndDisplayingCell:(UICollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath{
    NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
    
    ///通知登録解除
    [center removeObserver:cell];
}

```

UIViewControllerの`- (void)viewWillAppear:(BOOL)animated; `と`- (void)viewWillDisappear:(BOOL)animated;`で通知登録・解除しているのと同じ役割を持たせています。




先にUICollectionViewCell（カスタムセル）の記述から書きますね。

###UICollectionViewCell（カスタムセル）
先にUICollectionViewCell（カスタムセル）の記述から書きますね。

```
#import "CustomCollectionViewCell.h"

@interface CustomCollectionViewCell()<UITextFieldDelegate>
@property (weak, nonatomic) IBOutlet UITextField *topTextFiled;  //1個目
@property (weak, nonatomic) IBOutlet UITextField *bottomTextField;  //2個目
@property (weak, nonatomic) IBOutlet UITextField *endTextField;  //3個目
@property (weak, nonatomic) IBOutlet UIScrollView *scrollView;

///現在フォーカスされているTextField
@property (nonatomic) UITextField *selectedTextField;
@end


@implementation CustomCollectionViewCell

- (void)awakeFromNib {
    [super awakeFromNib];
    self.topTextFiled.delegate = self;
    self.bottomTextField.delegate = self;
    self.endTextField.delegate = self;

}

///テキストフィールドを編集する直前に呼び出される
- (BOOL)textFieldShouldBeginEditing:(UITextField *)textField{
    self.selectedTextField = textField;
    return YES;
}

///キーボードが表示された時に呼び出される
-(void)keyboardWillShow:(NSNotification *)notification{
    // キーボードの表示完了時の場所と大きさを取得します。
    CGRect keyboardFrameEnd = [notification.userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue];
    
    ///下からのキーボードの上部までの距離
    CGFloat keyBoardTopLine = keyboardFrameEnd.size.height;
    
    ///下からテキストフィールドの下部までの距離
    CGFloat textFieldBottomLine = self.scrollView.frame.size.height + self.scrollView.contentOffset.y - (self.selectedTextField.frame.origin.y + self.selectedTextField.frame.size.height);
    
    ///そのまま表示するとキーボードの上部とテキストフィールドの下部がぴったり重なるので10.0fの余裕を設けている。
    if(keyBoardTopLine > textFieldBottomLine){
        self.scrollView.contentOffset = CGPointMake(0, keyBoardTopLine - textFieldBottomLine + 10.0f + self.scrollView.contentOffset.y);
    }
}

///キーボードが隠れた時に呼び出される
-(void)keyboardWillHide:(NSNotification *)notification{
    
    
    if(self.selectedTextField.frame.origin.y < self.scrollView.frame.size.height){
        ///テキストフィールドが元々スクロールしなくても表示されている位置に配置されている場合はスクロール(0,0)にする。
        self.scrollView.contentOffset = CGPointMake(0,0);
    }else{
        ///テキストフィールドがスクロールしなければ表示されない位置に配置されているので、どれぐらいスクロールするか算出する。
        CGFloat scrollOffset = self.selectedTextField.frame.origin.y - self.scrollView.frame.size.height + (self.selectedTextField.frame.size.height);
        
        ///テキストフィールドの下部からスクロールビューの下部までの長さを取得する（余白を設けて良いか確認する為）
        CGFloat bottomPt = self.scrollView.contentSize.height - (self.selectedTextField.frame.origin.y + self.selectedTextField.frame.size.height);
        
        ///テキストフィールドの下部からスクロールビューの下部までの長さが50.0f以上存在するなら50.0f追加する。
        if(bottomPt > 50.0f){
            scrollOffset += 50.0f;
        }else{
            ///テキストフィールドの下部からスクロールビューの下部までの長さが50.0fよりも少ないなら、
            ///「テキストフィールドの下部からスクロールビューの下部までの長さ」を加算する。
            scrollOffset += bottomPt;
        }
        
        self.scrollView.contentOffset = CGPointMake(0,scrollOffset);

    }

}

@end

```

割と苦労しましたね。スクロールビューなのでキーボード出ている時に、
ユーザーが自由にスクロール出来てしまうんですよね。

なので、フォーカスが外れる都度、再計算して調整しました。
また、良い感じにスクロールして戻って欲しかったので コード内にあるように、
**「スクロールして戻した後に下部にスペースがあれば50.0f追加してスクロールする」**という処理を挟んでいます。
（戻されたテキストフィールドが一番下部にあるのは見栄え悪いですもんね。）

##なぜ表示1件のCollectionView(TableView）のみしか運用出来ないのか。
カスタムセル(UICollectionViewCell)で受け取る都合上、
CollectionViewのUICollectionViewDelegateにて通知を登録・解除しております。

もし2件のセルが表示されていたとすれば、
**Aセル-Bセル**と表示されていたら、
**Bセルでフォーカスした際にAセルも合わせてスクロール**します。


本当に対策するのであれば、
CollectionViewCellでテキストフィールドをフォーカスしたタイミングで
**デリゲートなどでCollectionViewに通知させ、
そのセル以外のセルの通知を解除して**、など必要かと思います。

テキストフィールドフォーカス時に初めて通知登録とかでも良いのかもしれませんね。

今回はそこまで不要でしたので必要になった際に更新しようと考えております。

###最後に
[UIScrollVIewの上でも、キーボードが現れたときにいい感じにスクロールする](https://qiita.com/akaneburyo/items/893c182db347cd5122f7)

最終的に実装の方法や考え方などは異なりましたが、
かなり参考になりました！岡崎様、ありがとうございます。