UITableViewの最大スクロール時に余白を作る実装パターン

#まずはじめに
UITableViewの下部に空間が欲しいと思ったことはないでしょうか。
その理由としてボタンやウインドウが被る。しかしfooterを設定するとタップ判定が無くなる。
Tableの高さを変更する処理にするとCellが一瞬消滅して見える。。などなど
簡単そうに見えて意外と容易くないことが今回調べていてわかったので４つの処理パターンに分けてまとめました。

#検証するUITableViewの例
[ Version 11.1 Swift5.1 ]
例えば下の画像のようなUITableViewを実装したとします。
右下のボタンをタップするとモーダルウィンドウが上がってきます。
![グループ 42小.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/487744/a43cf713-2900-363e-a051-ede9f281de86.png)![グループ 41小.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/487744/def97304-83b7-81af-ea4c-02244d5ca1c2.png)

# 問題
このままだと少し問題があります。
モーダルが一番下のCell約２つ分に被ってしまい、モーダルが出ている間は２つ分が見えなくなってしまい、UI的に優しくありません。
## 処理１：TableViewをキーボードの高さ分上げる

```swift
        //キーボード表示時画面を持ち上げる
        NotificationCenter.default.addObserver(self, selector: #selector(NewTaskViewController.keyboardWillShow(_ :)), name: UIResponder.keyboardWillShowNotification, object: nil)
        //キーボード閉じる時に画面を下げる
        NotificationCenter.default.addObserver(self, selector: #selector(NewTaskViewController.keyboardWillHide(_ :)), name: UIResponder.keyboardWillHideNotification, object: nil)
```
###処理１の問題点
スクロールポジションを任意に設定したい場合に限りますが、
tableの天井部分が画面外に出ているため、
モーダルが出ている間は適切なスクロールポジションに移動しない。

##処理２：footerを設ける
```swift
    //フッターの色を透明に
    func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? {
        let footerView: UIView = UIView()
        footerView.backgroundColor = UIColor.clear
        return footerView
    }
    //フッターの高さ
    func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
        return 400 //高さ
    }
```
すると下のように最大スクロール時footerの高さ分余白ができるためモーダル出現時であってもCellを全て確認できます。
![グループ 40小.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/487744/a6749334-d7a2-3321-6d15-1ea9eb80c1fa.png)

### 処理２の問題点
それはfooter範囲内はCellが存在している場合でもタップやスワイプが認識しないという点です。
バグか仕様か判断つかないですし、ユーザーに明らかに優しくありません。
![グループ 42小 2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/487744/2628b283-6712-1dd1-d45e-7011e35e9664.png)

## 処理３：UITableViewの高さを変更する
ボタンタップ時の処理として以下のように実装すると良いでしょう。

```swift
UIView.animate(withDuration: 0.5, delay: 0.5, options: .オプション, animations: {
        //tableの高さを変更
        self.tableView.fs_height = self.tableHeight - 400 //高さを縮小
}, completion: nil)
```
今回delay（アニメーション発生遅延）を設定しています。
理由は次で下でお伝えします。
### 処理３の問題点
こちらの問題。ボタンをタップした場合、高さ変更のアニメーション”開始時”にCellが消えて見えます。
（上のコードのようにdelayを設定してもCellの消えるタイミングは遅れません。）
機能には問題ないですし、閉じた後にスクロールして消えたCellを表示することはできますが、ユーザー目線に立つと気持ちが悪いです。

![グループ 40小.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/487744/3f55527e-8134-3732-a7a1-18f69831e2f4.png)

## 処理４：最後尾に透明なCellを追加して高さを設定する
この方法は少し煩雑化しますが、モーダルを閉じた状態でも、最大スクロールした時に下に空白を自由に設定でき、同様に擬似headerも作れそうです。
####CellForRowAt内で

```swift
        if array[indexPath.row] == "footerKey"{
            //footerはモーダルと同じ高さを設定
            footerIndexPath = indexPath.row
            tableView.rowHeight = 400
        }
```
#### WillDisplayForRowAt内で

```swift
//Cell内アイテムにtagを１〜３設定していた場合の例
if array[indexPath.row] == "footerKey" {
            for i in 1...3{//Cellがfooterの場合は非表示
                (cell.contentView.viewWithTag(i))!.isHidden = true
            }
```
上記以外にfooterは透明なCellであることから、スワイプおよびタップの認識を無視するコードを書く必要があります。
（こちらは別記事にまとめたいと思います。）

### 処理４の問題点
処理が煩雑化します。配列にダミーのデータを入れて、要素をClearにして擬似的にスペースを作っているためです。
しかし、処理１,２,３のような問題点が無いため、私は処理４を採用いたしました。

### 19.11.18追記(カクツキ問題の解決)
処理４でfooterCellの高さを

```swift
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
```
内で動的に変更していた場合、画面遷移直後下から上へスクロール時
少しカクツキが発生します。
（スクロール開始直後に高さが描写されるからだと思われます。)
footerのCellを他のCellと差異を作らず、同一にし、必要分だけダミーのfooterCellを作成すれば
この欠点は解決いたしました！


#まとめ
記事は以上となります！

私はSwiftを学習し始めて50日です。現在初の自作アプリリリースに向けて開発を進めているのですが、インプットやアウトプットもまだまだ足りておらず、日々実装しながら検証の繰り返しです。
Qiitaに記事が無かった物に関しては積極的に記事にして行きたいと思っています！
ご覧いただきありがとうございました。
初学者の方々にお役に立てたら幸いです。