UIViewの背景にグラデーションを設定する

[前回](https://qiita.com/yukichiii/items/ca4374254cdb0b53b642)作ったチュートリアルのFirstVC,SecondVC,ThirdVCの各画面の背景にグラデーションを設定してみます

## 実装

グラデーションされたCAGradientLayerを返すメソッドを実装

```
import UIKit

extension CAGradientLayer {
    static func layerForView() -> CAGradientLayer {
        let gradientLayer = CAGradientLayer()
        gradientLayer.colors =  [UIColor.red.cgColor,
                                 UIColor.blue.cgColor]
        // 設定した各グラデーションカラーのstopポイントを決めている？
        // これを設定しないとおそらく設定したカラーが画面全体に均一の割合で表示される
        gradientLayer.locations = [0.0, 0.6]
        // startPointとendPointを決めることでグラデーションをかける方向を決めている？
        gradientLayer.startPoint = CGPoint(x: 0, y: 0)
        gradientLayer.endPoint = CGPoint(x: 0, y: 1)
        return gradientLayer
    }
}
```


前回作ったチュートリアルの全3画面にグラデーションをかける

```
import UIKit

class TutorialContainerViewController: UIViewController {
    enum Page:Int {
        case first
        case second
        case third
    }
    
    // MARK: - IBOutlet
    @IBOutlet weak var nextButton: UIButton!
    
    // MARK: - internal
    var didSelectButtonToHome: (() -> Void)?
    
    // MARK: - private
    var currentPage = Page.first
    private lazy var pageViewController: UIPageViewController = {
        // このViewControllerが持つpageViewControllerを取得
        let pageVC = children.first as! UIPageViewController
        return pageVC
    }()

    override func viewDidLoad() {
        super.viewDidLoad()
        pageViewController.setViewControllers([getFirst()], direction: .forward, animated: true, completion: nil)
    }

    @IBAction func tappedButton(_ sender: UIButton) {
        if currentPage == .first {
            pageViewController.setViewControllers([getSecond()], direction: .forward, animated: true, completion: nil)
            currentPage = .second
            nextButton.isEnabled = false
            return
        }
        
        if currentPage == .third {
            didSelectButtonToHome?()
        }
    }
}

extension TutorialContainerViewController {
    private func getFirst() -> FirstViewController {
        let vc: FirstViewController = storyboard!.instantiateViewController(identifier: "FirstViewController")
        let gradientLayer = CAGradientLayer.layerForView()
        // gradientLayerは生成されただけだとまだフレームサイズが決まってなくてx:0, y:0 ,height:0 ,width: 0 のままなのでinsertSublayerしても表示されないためフレームを決めてあげる必要がある
        gradientLayer.frame = vc.view.frame
        // layerのsublayersno0番目にgradientLayerを差し込む
        vc.view.layer.insertSublayer(gradientLayer, at: 0) ← NEW
        return vc
    }
    
    private func getSecond() -> SecondViewController {
        let gradientLayer =  CAGradientLayer.layerForView()
        let vc: SecondViewController = storyboard!.instantiateViewController(identifier: "SecondViewController")
        gradientLayer.frame = vc.view.frame
        vc.view.layer.insertSublayer(gradientLayer, at: 0) ← NEW
        // SecondViewControllerでボタンがタップされた通知が飛んでくる
        // {}の中身はその通知が飛んできた時に呼ばれる内容だから、getSecond()が呼ばれても{}の中身は呼ばれない
        vc.selectedButtonToThird = {
            self.nextButton.isEnabled = true
            self.pageViewController.setViewControllers([self.getThird()], direction: .forward, animated: true, completion: nil)
        }
        return vc
    }
    
    private func getThird() -> ThirdViewController {
        let vc: ThirdViewController = storyboard!.instantiateViewController(identifier: "ThirdViewController")
        let gradientLayer =  CAGradientLayer.layerForView()
        gradientLayer.frame = vc.view.frame
        vc.view.layer.insertSublayer(gradientLayer, at: 0) ← NEW
        return vc
    }
}


```

gradientLayerのフレームを決めないで表示されなかったり、insertSublayerではなくてaddSublayerしてLabel等のオブジェクトが見えなくなるのはありがちな気がする
*insertSublayerについてはよくわかってなかったけど、下の画像をみてなんとなく理解した.UIViewは必ずCALayerを持っていて、CALayerはその下にさらにsublayerを持つことができる。UIButtonやUILabelといったオブジェクトはaddSubされるものなのでレイヤー的にはSublayerの下にあるみたい。だからaddSublayerすることで背景色が変わったように見えるんだと思う

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/602389/74e85582-1e33-b0dd-086d-dc1a9fb0ba60.png)
参考: https://thoughtbot.com/blog/building-ios-interfaces-custom-button


シュミレーターで確認すると無事グラデーションがかかって表示された

|1画面目|2画面目|3画面目|
|-------|-------|--------|
| ![Simulator Screen Shot - iPhone 11 Pro Max - 2020-03-29 at 16.33.42.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/602389/ccd5d053-2591-3d69-bb35-7bbbcbafd366.png) | ![Simulator Screen Shot - iPhone 11 Pro Max - 2020-03-29 at 16.33.44.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/602389/d1037923-63dd-0fbd-7b08-2802cba6bb6f.png) | ![Simulator Screen Shot - iPhone 11 Pro Max - 2020-03-29 at 16.33.48.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/602389/07fec424-bc03-2c34-3675-9e2280e6e17e.png) |


#　改善
毎回CAGradientLayerインスタンスを生成するコードを書いて、毎回gradientLayerのフレーム決めるコード書いて、毎回addSublayerするコードを書くのは面倒なので
表示する各画面をcontentsVCsみたいなUIViewControllerの配列にあらかじめ入れておいてforEachで回して設定するやり方もできると思います

```
contentsVCs.forEach {
    let gradientLayer = CAGradientLayer.layerForView()
    gradientLayer.frame = $0.view.frame
    $0.view.layer.insertSublayer(gradientLayer, at: 0)
}
```

ただこのやり方も最初のやり方もそうですが、親であるTutorialContainerViewControllerがこねくりましたり、TutorialContainerViewController内で生成された各VCのプロパティに設定したりとTutorialContainerViewController内でViewのグラデーションの設定を行なっています
でも実際はViewの描画はView自体の責務であり、ViewControllerは知る必要がありません
なので、こういう時はカスタムViewを作ってそこでグラデーションをかける。親であるTutorialContainerViewControllerではただそのViewを使うだけにした方がいいかと思うので修正します
まずは先ほど追加したコードの削除
新しくUIViewのカスタムクラスとしてGradationViewを作成

```
import UIKit

class GradationView: UIView {
    // オートレイアウトでレイアウトが決まってからgradientLayerのフレームの位置・サイズを決めないとgradation.frameの各値が0になるからlayoutSubview内で設定する必要がある
    override func layoutSubviews() {
        super.layoutSubviews()
        let gradientLayer = CAGradientLayer.layerForView()
        gradientLayer.frame = self.frame
        self.layer.insertSublayer(gradientLayer, at: 0)
    }
}
```

あとはこのカスタムViewのStoryboard上でFirstVC,SecondVC,ThirdVCのそれぞれのViewに設定すれば完了
![スクリーンショット 2020-03-29 16.42.09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/602389/ba8ea9fd-687e-ea17-b8f7-977065dab030.png)
*SecondVC, ThirdVCにも同じように設定する


# 追記
storyboardContainerViewControllerで新しいViewを作ってその上にボタンをaddSubviewするように修正しました


# 参考
https://qiita.com/Gutenberg0x/items/ac737fb3305da3389bf9
https://thoughtbot.com/blog/building-ios-interfaces-custom-button