前回作ったチュートリアルの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することで背景色が変わったように見えるんだと思う
参考: https://thoughtbot.com/blog/building-ios-interfaces-custom-button
シュミレーターで確認すると無事グラデーションがかかって表示された
1画面目 | 2画面目 | 3画面目 |
---|---|---|
# 改善
毎回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に設定すれば完了
*SecondVC, ThirdVCにも同じように設定する
追記
storyboardContainerViewControllerで新しいViewを作ってその上にボタンをaddSubviewするように修正しました
参考
https://qiita.com/Gutenberg0x/items/ac737fb3305da3389bf9
https://thoughtbot.com/blog/building-ios-interfaces-custom-button