Question

I have a UIViewController where I am adding a CALayer subclass to the view's layer:

[self.view.layer addSublayer:myObject.backgroundLayer];

When I rotate the device, the view rotates, but the CALayer doesn't. It sort of gets shunted off to the left, still in portrait view.

Is there a way to make sublayers rotate automatically or do I need to apply a transform?

Was it helpful?

Solution

You need to manage the rotation of the CALayer yourself. I believe that 0,0 stays at the same place and that the size is changed to match the new orientation, so if you wanted to do something yourself, you'd need to manage the addition of the rotation transformation yourself.

OTHER TIPS

With Swift 4 / iOS 11, according to your needs, you may choose one of the 6 following examples in order to manage your CALayer / CAGradientLayer frame upon a device rotation.

The examples below use CAGradientLayer but can easily be mapped to CALayer or CAShapeLayer cases.


#1. Overriding UIViewController viewDidLayoutSubviews()

import UIKit

class ViewController: UIViewController {

    let gradientLayer: CAGradientLayer = {
        let layer = CAGradientLayer()
        layer.colors = [
            UIColor.blue.cgColor,
            UIColor.cyan.cgColor
        ]
        return layer
    }()

    override func viewDidLoad() {
        super.viewDidLoad()

        view.layer.addSublayer(gradientLayer)
        gradientLayer.frame = view.bounds
    }

    override func viewDidLayoutSubviews() {         
        gradientLayer.frame = view.bounds
    }

}

#2. Overriding UIViewController loadView(), subclassing UIView and overriding UIView layoutSubviews()

LayerView.swift

import UIKit

class LayerView: UIView {

    lazy var gradientLayer: CAGradientLayer = {
        let layer = CAGradientLayer()
        layer.colors = [
            UIColor.blue.cgColor,
            UIColor.cyan.cgColor
        ]
        self.layer.addSublayer(layer)
        return layer
    }()

    override func layoutSubviews() {
        gradientLayer.frame = bounds
    }

}

LayerView.swift (alternative)

import UIKit

class LayerView: UIView {

    var gradientLayer: CAGradientLayer!

    override func layoutSubviews() {
        if gradientLayer == nil {
            let gradientLayer = CAGradientLayer()
            gradientLayer.colors = [
                UIColor.blue.cgColor,
                UIColor.cyan.cgColor
            ]
            self.gradientLayer = gradientLayer
            layer.addSublayer(gradientLayer)
        }

        gradientLayer.frame = bounds
    }

}

ViewController.swift

import UIKit

class ViewController: UIViewController {

    let layerView = LayerView()

    override func loadView() {
        view = layerView
    }

}

#3. Using Key Value Observing (KVO)

import UIKit

class ViewController: UIViewController {

    var observation: NSKeyValueObservation?

    let gradientLayer: CAGradientLayer = {
        let layer = CAGradientLayer()
        layer.colors = [
            UIColor.blue.cgColor,
            UIColor.cyan.cgColor
        ]
        return layer
    }()

    override func viewDidLoad() {
        super.viewDidLoad()

        view.layer.addSublayer(gradientLayer)

        observation = view.observe(\.frame, options: [.new], changeHandler: { [unowned self] (object: UIView, change: NSKeyValueObservedChange<CGRect>) in
            guard let frame = change.newValue else { return }
            self.gradientLayer.frame = frame
        })

        // Also works
        /*
        observation = observe(\.view.frame, options: [.new], changeHandler: { [unowned self] (object: GradientViewController3, change: NSKeyValueObservedChange<CGRect>) in
            guard let frame = change.newValue else { return }
            self.gradientLayer.frame = frame
        })
        */
    }

}

#4. Overriding UIViewController loadView(), subclassing UIView and overriding UIView layerClass

LayerView.swift

import UIKit

class LayerView: UIView {

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

    required init() {
        super.init(frame: .zero)

        guard let gradientLayer = layer as? CAGradientLayer else { return }
        gradientLayer.colors = [
            UIColor.blue.cgColor,
            UIColor.cyan.cgColor
        ]
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

}

ViewController.swift

import UIKit

class ViewController: UIViewController {

    let layerView = LayerView()

    override func loadView() {
        view = layerView
    }

}

#5. Overriding UIViewController loadView(), subclassing UIView and overriding CALayerDelegate layoutSublayers(of:)

LayerView.swift

import UIKit

class LayerView: UIView {

    required init() {
        super.init(frame: .zero)

        let gradientLayer = CAGradientLayer()
        gradientLayer.colors = [
            UIColor.blue.cgColor,
            UIColor.cyan.cgColor
        ]

        layer.addSublayer(gradientLayer)
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    override func layoutSublayers(of layer: CALayer) {
        layer.sublayers?.forEach {
            $0.frame = layer.bounds
        }
    }

}

ViewController.swift

import UIKit

class ViewController: UIViewController {

    let layerView = LayerView()

    override func loadView() {
        view = layerView
    }

}

#6. Overriding UIViewController loadView(), subclassing UIView, overriding UIView layerClass, subclassing CALayer and overriding CALayer layoutSublayers()

Layer.swift

import UIKit

class Layer: CALayer {

    override init() {
        super.init()

        let gradientLayer = CAGradientLayer()
        gradientLayer.colors = [
            UIColor.blue.cgColor,
            UIColor.cyan.cgColor
        ]

        addSublayer(gradientLayer)
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    override func layoutSublayers() {
        sublayers?.forEach {
            $0.frame = bounds
        }
    }

}

LayerView.swift

import UIKit

class LayerView: UIView {

    override public class var layerClass: AnyClass {
        return Layer.self
    }

}

ViewController.swift

import UIKit

class ViewController: UIViewController {

    let layerView = LayerView()

    override func loadView() {
        view = layerView
    }

}

Sources:

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top