22
Help us understand the problem. What are the problem?

More than 3 years have passed since last update.

posted at

addSublayerで追加されたCALayerはビューのサイズ変更に追従しない問題の対応

それはグラデーションを扱ってるときに起きました:fearful:
どうもIBで作ったサイズのまま変わらないでグラデーションがかかっているのです・・・:bug:

どんな状況?

まずは下の記事を参考にGradationViewなるものを作りました。
Swiftで背景にグラデーションを設定する

GradationView
import UIKit

class GradationView: UIView {

    @IBInspectable var topColor: UIColor = .clear
    @IBInspectable var bottomColor: UIColor = .white

    func setGradation() {
        let gradientLayer = CAGradientLayer()
        gradientLayer.frame.size = self.frame.size
        gradientLayer.colors = [self.topColor.cgColor, self.bottomColor.cgColor]
        self.layer.addSublayer(gradientLayer)
    }
}

そしてIB上でGradationViewを配置し、IBOutletでVCに繋いで、setGradation()が呼ばれるようにします。
制約は画面幅いっぱいになるようにつけています。高さは適当に固定しています。
スクリーンショット 2018-07-04 12.24.03.png

繋いだVC
import UIKit

class ViewController: UIViewController {

    @IBOutlet weak var gradationView: GradationView! {
        willSet {
            newValue.topColor = .red
            newValue.bottomColor = .blue
            newValue.setGradation()
        }
    }
    ...
}

これをまずはIB上のVCと同じ画面サイズの端末で実行してみます。
スクリーンショット 2018-07-04 12.33.17.png
想定通り表示されました。

次に、IB上のVCより大きい画面サイズの端末で実行してみます。
スクリーンショット 2018-07-04 12.35.09.png
グラデーションが途中で切れてしまいました!View Hierarchyを見てみるとビュー自体は幅が伸びていることがわかります。
スクリーンショット 2018-07-04 12.36.22.png

なぜ起こるの?

タイトルの通りですが、addSublayerで追加されたCALayerはビューのサイズ変更に追従してくれないみたいです。
ビューはAutoLayoutによって端末サイズに合わせてサイズが変更されますが、CALayerはそのまま残ります。

対応策

ググってみると3通り対応策がありました。
CALayer And Auto Layout With Swift
詳細は上の記事を見てもらいたいのですが、ざっくりまとめると以下の通りです。
1. AutoLayoutの制約が効いたあとでCALayerのframeを更新する
2. ビューのboundsをKVOで監視し、変更があったらCALayerのframeを更新する
3. ビューのlayerをCAGradientLayerにしてしまう

今回私は#3の対応策を取ることにしました。
関心のあるCALayerが1つの場合はこれで対応できそうです。もし、複数ある場合は#1または#2で対応することになると思います。

改良したGradationView
import UIKit

class GradationView: UIView {

    @IBInspectable var topColor: UIColor = .clear
    @IBInspectable var bottomColor: UIColor = .white

    override class var layerClass: AnyClass {
        return CAGradientLayer.self
    }

    func setGradation() {
        guard let gradientLayer = self.layer as? CAGradientLayer else {
            return
        }
        gradientLayer.colors = [self.topColor.cgColor, self.bottomColor.cgColor]
    }
}

これで、無事グラデーションがビューのサイズ変更に追従するようになってくれました。

まとめ

addSublayerで追加されたCALayerはビューのサイズ変更に追従してくれない。
関心のあるCALayerが1つだけの場合は、ビューのlayer自体を関心のあるCALayerに置き換えてしまうと良い。
関心のあるCALayerが複数の場合は、ビューのサイズ変更が起きた後のタイミングでCALayerのサイズ変更を行う。

終わりに

最初はなんで自動で変わってくれないんだよ、と思いましたが、よくよく考えてみればaddSubViewしたビューが親ビューにサイズ追従しないのと同じような話なんですかね:thinking:
しかしframeをいじるしかサイズ変更できないというのはイカしてないと思うので、レイヤーにもAutoLayoutがつけられるようになったらいいんじゃないかなーと思います:thumbsup:
なんだかAutoLayoutを使い始めた頃を思い出させる現象でした:smile_cat:

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Sign upLogin
22
Help us understand the problem. What are the problem?