You have a few problems.
First, you should think of the gradient as a lot of stops, where the stop consists of two parts: color and location. You should have the same number of colors and places, because each stop has one. You can see this if, for example, you check the CGGradientCreateWithColorComponents documentation for the components argument:
The number of elements in this array should be the product of count and the number of components in the color space.
This is a product (the result of multiplication), because your count stops, and you need a full set of color components for each stop.
You do not provide enough color components. Your GradientLayer can have any number of places (and you give it two), but it has only one color. You get this one color component and pass it as an array of components in the CGGradientCreateWithColorComponents , but the array is too short. Swift will not catch this error - note that the type of your colorComponents is UnsafePointer<CGFloat> . The Unsafe part tells you that you are in a dangerous area. (You can see the colorComponents type by clicking on the icon in Xcode.)
Since you are not providing a sufficiently large array for components , iOS uses any random values ββthat are in memory after the components of your same color. They can change from run to run and are often not what you want.
In fact, you should not even use CGGradientCreateWithColorComponents . You should use CGGradientCreateWithColors , which accepts a CGColor array, so it is not only easier to use, but also safer because it floats around less than UnsafePointer .
Here GradientLayer should look like this:
class RadialGradientLayer: CALayer { struct Stop { var location: CGFloat var color: UIColor } var stops: [Stop] { didSet { self.setNeedsDisplay() } } var origin: CGPoint { didSet { self.setNeedsDisplay() } } var radius: CGFloat { didSet { self.setNeedsDisplay() } } init(stops: [Stop], origin: CGPoint, radius: CGFloat) { self.stops = stops self.origin = origin self.radius = radius super.init() needsDisplayOnBoundsChange = true } override init(layer other: AnyObject) { guard let other = other as? RadialGradientLayer else { fatalError() } stops = other.stops origin = other.origin radius = other.radius super.init(layer: other) } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func drawInContext(ctx: CGContext) { let locations = stops.map { $0.location } let colors = stops.map { $0.color.CGColor } locations.withUnsafeBufferPointer { pointer in let gradient = CGGradientCreateWithColors(nil, colors, pointer.baseAddress) CGContextDrawRadialGradient(ctx, gradient, origin, 0, origin, radius, [.DrawsAfterEndLocation]) } } }
The next problem. You add more gradient layers each time the system calls viewWillLayoutSubviews . It can call this function several times! For example, it will call it if your application supports rotating the interface, or if a call comes in, and iOS makes the status bar taller. (You can check this in the simulator by choosing Equipment> Switch Status Bar During A Call.)
You need to create gradient layers once, saving them in the property. If they are already created, you need to update their frames and not create new layers:
class ViewController: UIViewController { private var gradientLayers = [RadialGradientLayer]() override func viewWillLayoutSubviews() { super.viewWillLayoutSubviews() if gradientLayers.isEmpty { createGradientLayers() } for layer in gradientLayers { layer.frame = view.bounds } } private func createGradientLayers() { let bounds = view.bounds let mid = CGPointMake(bounds.midX, bounds.midY) typealias Stop = RadialGradientLayer.Stop for (point, radius, color) in [ (mid, 100, UIColor(white:1, alpha:0.2)), (CGPointMake(mid.x - 20, mid.y + 20), 160, UIColor(white:1, alpha:0.2)), (CGPointMake(mid.x + 30, mid.y - 30), 300, UIColor(white:1, alpha:0.2)) ] as [(CGPoint, CGFloat, UIColor)] { let stops: [RadialGradientLayer.Stop] = [ Stop(location: 0, color: color), Stop(location: 1, color: color.colorWithAlphaComponent(0))] let layer = RadialGradientLayer(stops: stops, origin: point, radius: radius) view.layer.addSublayer(layer) gradientLayers.append(layer) } } }