CAGradientLayer, not resizing nicely, tearing on rotation

When you create a layer (like your gradient layer), there's no view managing the layer (even when you add it as a sublayer of some view's layer). A standalone layer like this doesn't participate in the UIView animation system.

So when you update the frame of the gradient layer, the layer animates the change with its own default animation parameters. (This is called “implicit animation”.) These default parameters don't match the animation parameters used for interface rotation, so you get a weird result.

I didn't look at your project but it's trivial to reproduce your problem with this code:

@interface ViewController ()

@property (nonatomic, strong) CAGradientLayer *gradientLayer;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    self.gradientLayer = [CAGradientLayer layer];
    self.gradientLayer.colors = @[ (__bridge id)[UIColor blueColor].CGColor, (__bridge id)[UIColor blackColor].CGColor ];
    [self.view.layer addSublayer:self.gradientLayer];
}

- (void)viewDidLayoutSubviews {
    [super viewDidLayoutSubviews];
    self.gradientLayer.frame = self.view.bounds;
}

@end

Here's what that looks like, with slow motion enabled in the simulator:

bad rotation

Fortunately, this is an easy problem to fix. You need to make your gradient layer be managed by a view. You do that by creating a UIView subclass that uses a CAGradientLayer as its layer. The code is tiny:

// GradientView.h

@interface GradientView : UIView

@property (nonatomic, strong, readonly) CAGradientLayer *layer;

@end

// GradientView.m

@implementation GradientView

@dynamic layer;

+ (Class)layerClass {
    return [CAGradientLayer class];
}

@end

Then you need to change your code to use GradientView instead of CAGradientLayer. Since you're using a view now instead of a layer, you can set the autoresizing mask to keep the gradient sized to its superview, so you don't have to do anything later to handle rotation:

@interface ViewController ()

@property (nonatomic, strong) GradientView *gradientView;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    self.gradientView = [[GradientView alloc] initWithFrame:self.view.bounds];
    self.gradientView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
    self.gradientView.layer.colors = @[ (__bridge id)[UIColor blueColor].CGColor, (__bridge id)[UIColor blackColor].CGColor ];
    [self.view addSubview:self.gradientView];
}

@end

Here's the result:

good rotation


The best part about @rob's answer is that the view controls the layer for you. Here is the Swift code that properly overrides the layer class and sets the gradient.

import UIKit

class GradientView: UIView {

    override init(frame: CGRect) {
        super.init(frame: frame)
        setupView()
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        setupView()
    }

    private func setupView() {
        autoresizingMask = [.flexibleWidth, .flexibleHeight]

        guard let theLayer = self.layer as? CAGradientLayer else {
            return;
        }

        theLayer.colors = [UIColor.whiteColor.cgColor, UIColor.lightGrayColor.cgColor]
        theLayer.locations = [0.0, 1.0]
        theLayer.frame = self.bounds
    }

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

You can then add the view in two lines wherever you want.

override func viewDidLoad() {
    super.viewDidLoad()

    let gradientView = GradientView(frame: self.view.bounds)
    self.view.insertSubview(gradientView, atIndex: 0)
}