[Swift4] カスタムViewを作って画面下からフェードイン・画面下へフェードアウトさせる方法

AppStoreで公開している[ShodoCAM](https://apps.apple.com/jp/app/shodocam/id1452049446)の開発をしていて，あるボタンを押すとカスタムViewが下から出てくるアニメーションを実装したかった．その後カスタムViewを下にフェードアウトさせる機能も実装する．


## 手順
1. カスタムViewのxibファイルを作る
2. カスタムViewクラスを作る
3. カスタムViewのxibとカスタムViewクラスを紐付け
4. カスタムViewのフェードイン・フェードアウト実装


### 1. カスタムViewのxibファイルを作る
xibとは(XML Interface Builder)の略で，アプリのUIとして使いまわせるカスタムViewを作るファイルです．StoryboradではアプリのあるView全体を作り，xibでUIのパーツを作って複数のViewで使い回すと覚えておけば良い．
xibファイルはコンパイルされてnibファイル(NeXTSTEP Interface Builder)に変換されるみたい．
[参考記事 (What’s a XIB and Why Would I Ever Use One?)](https://medium.com/@tjcarney89/whats-a-xib-and-why-would-i-ever-use-one-58d608cd5e9b)

⌘Nでファイルの新規作成ウィンドウを開き，User Interfaces中のViewを選択．

<img width="1551" alt="スクリーンショット 2020-02-02 17.10.17.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/517036/249037f1-3ead-fecf-bc13-c04097c708f4.png">

適当な名前をつけて保存．

<img width="1551" alt="スクリーンショット 2020-02-02 17.13.00.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/517036/ffa25662-e79c-3f1f-c24e-3822ac5209af.png">

Attributes InspectorからSizeをFreedomにして，適当にImageViewを6つ，背景をgreyにしている．Viewのwidthは414，heightを100に設定．
<img width="1551" alt="スクリーンショット 2020-02-02 17.14.24.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/517036/33d11f9b-21dc-01fb-be29-a8e05069d208.png">

### 2. カスタムViewクラスを作る
作成したxibに対応するカスタムViewクラスを作る．
⌘Nでファイルの新規作成ウィンドウを開き，Cocoa Touch Classを選択．

<img width="1551" alt="スクリーンショット 2020-02-02 17.17.53.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/517036/b21f3caf-4f9f-3ed9-6bf2-c127cd80912d.png">

適当なクラス名(FrameView)をつけて保存．

<img width="1551" alt="スクリーンショット 2020-02-02 17.35.49.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/517036/29a10dbc-8d82-5d82-d195-3dfc9a21c697.png">
FrameView.swiftにコード を追加．
- override init：コードでカスタムViewを初期化した時に実行
- required init：StoryboradでカスタムViewを初期化した時に実行
- func loadNib ：さっき作成したxibファイルを読み込んでUIとして表示させる関数
override init，required init両方の関数内にloadNib()と書いておく．

```swift:FrameView.swift
import UIKit

class FrameView: UIView {
  
  // コードで初期化されたときに実行
  override init(frame: CGRect) {
    super.init(frame: frame)
    loadNib()
  }
  
  // Storyboradで初期化された時に実行
  required init?(coder aDecoder: NSCoder) {
    super.init(coder: aDecoder)
    loadNib()
  }
  
  // xibファイルを読み込む処理
  func loadNib() {
    let view = Bundle.main.loadNibNamed("FrameView", owner: self, options: nil)?.first as! UIView
    view.frame = self.bounds
    self.addSubview(view)
  }
}

```



### 3. カスタムViewのxibとカスタムViewクラスを紐付け
xibファイルのFile's Ownerをクリックし，identity inspectorでCustom ClassのClassをFrameViewにします．これでxibをカスタムViewクラスが紐付けされる．
<img width="1551" alt="スクリーンショット 2020-02-02 17.39.49.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/517036/2686819d-1a6b-77bc-bcdf-02ba981a3961.png">

### 4. カスタムViewのフェードイン・フェードアウト実装
もともと下の画像のViewで，右下のFrameボタンを押すとカスタムViewが下からフェードインするようにしたかった．
その後にdissmissボタンを押すとカスタムViewを下にフェードアウトさせるようにする．この時に，ボタンを押された時に実行する関数内でカスタムViewにアクセスできなかった．そのためカスタムViewを保存するViewControllerのインスタンス変数を予め用意しておく．

<img width="1551" alt="スクリーンショット 2020-02-02 17.44.03.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/517036/90b93cad-9970-e922-e457-359241b7afa7.png">


```swift:ViewController.swift

var frameView: UIView?
var dismissButton: UIButton?

// FrameボタンのOutlet
@IBOutlet weak var frameButton: UIButton!
// Frameボタンが押されたら実行する処理
@IBAction func tapFrameButton(_ sender: UIButton) {
  let frameWidth: CGFloat = self.view.bounds.width
  let frameHeight: CGFloat = 100
  let x: CGFloat = self.view.bounds.origin.x
  let y: CGFloat = self.view.bounds.height
  
  // カスタムViewの左上頂点のx,y座標と幅・高さを決める
  let frame: CGRect = CGRect(x: x, y: y, width: frameWidth, height: frameHeight)
  self.frameView = FrameView(frame: frame)

  // カスタムViewをViewControllerに追加
  self.view.addSubview(frameView!)
	
  // 下からフェードインするアニメーション処理
  UIView.animate(withDuration: 0.2, delay: 0, options: .allowUserInteraction,     animations: {
    self.frameView?.frame.origin.y -= frameHeight
    }, completion: {_ in
      // カスタムViewを下にフェードアウトさせるためのボタン
      self.dismissButton = UIButton(type: .custom)
      // ボタンに適当な画像を貼る
      self.dismissButton?.setImage(UIImage(named: "dismiss"), for: .normal)

      // ボタンのframeを決める
      let dismissButtonFrame: CGRect = CGRect(x: 10, y:   (self.frameView?.frame.origin.y)! - 30, width: 40, height: 40)
      self.dismissButton?.frame = dismissButtonFrame
  				
      // ボタンが押されたら下のdissmissFrameView関数を実行するように設定
      self.dismissButton?.addTarget(self, action: #selector(FrameView.dismissFrameView(_:)), for: .touchUpInside)
				
      // ボタンをViewControllerに追加
      self.view.addSubview(self.dismissButton!)
    })
  }

// カスタムViewを下にフェードアウトさせるためのアニメーション処理
@objc func dismissFrameView(_ sender: UIButton){
  UIView.animate(withDuration: 0.2, delay: 0, options: .allowUserInteraction, animations: {
    self.frameView?.frame.origin.y += self.frameView?.frame.height ??   100
    self.dismissButton?.frame.origin.y += (self.dismissButton?.frame.height)! +   100
    }, completion: {_ in
      // アニメーションが終わったらカスタムViewとボタンを削除する
      self.frameView?.removeFromSuperview()
      self.dismissButton?.removeFromSuperview()
  })
}
```

カスタムViewを出し入れするところ．Frameボタンを押すとカスタムView(frameView)が下からフェードインする．下矢印のdismissボタンを押すとカスタムViewとボタンが下にフェードアウトする．
ここではScrollImageViewのスクロールによってカスタムViewの背景色を変える処理を別のところに書いている．

![ezgif-6-5355d4f1da4b.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/517036/f7beb803-9b7a-92ca-6117-eef29963e2d0.gif)