LoginSignup
3
3

More than 3 years have passed since last update.

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

Last updated at Posted at 2020-03-29

前回作ったチュートリアルの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://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 Simulator Screen Shot - iPhone 11 Pro Max - 2020-03-29 at 16.33.44.png Simulator Screen Shot - iPhone 11 Pro Max - 2020-03-29 at 16.33.48.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
*SecondVC, ThirdVCにも同じように設定する

追記

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

参考

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

3
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
3