Here's my completely Texture/AsyncDisplayKit based implementation for this. This allows for a completely generic Gradient Node class that does not fix any values for the entire class, where other solutions thus far forces, because drawRect is a class function, not an instance function.
class GradientNode: ASDisplayNode {
private let startUnitPoint: CGPoint
private let endUnitPoint: CGPoint
private let colors: [UIColor]
private let locations: [CGFloat]?
override class func draw(_ bounds: CGRect, withParameters parameters: Any?, isCancelled isCancelledBlock: () -> Bool, isRasterizing: Bool) {
guard let parameters = parameters as? GradientNode else {
CCLog.assert("Expected type SimpleGradientNode to be returned")
return
}
// Calculate the start and end points
let startUnitX = parameters.startUnitPoint.x
let startUnitY = parameters.startUnitPoint.y
let endUnitX = parameters.endUnitPoint.x
let endUnitY = parameters.endUnitPoint.y
let startPoint = CGPoint(x: bounds.width * startUnitX + bounds.minX, y: bounds.height * startUnitY + bounds.minY)
let endPoint = CGPoint(x: bounds.width * endUnitX + bounds.minX, y: bounds.height * endUnitY + bounds.minY)
let context = UIGraphicsGetCurrentContext()!
context.saveGState()
context.clip(to: bounds)
guard let gradient = CGGradient(colorsSpace: CGColorSpaceCreateDeviceRGB(),
colors: parameters.colors.map { $0.cgColor } as CFArray,
locations: parameters.locations) else {
CCLog.assert("Unable to create CGGradient")
return
}
context.drawLinearGradient(gradient,
start: startPoint,
end: endPoint,
options: CGGradientDrawingOptions.drawsAfterEndLocation)
context.restoreGState()
}
init(startingAt startUnitPoint: CGPoint, endingAt endUnitPoint: CGPoint, with colors: [UIColor], for locations: [CGFloat]? = nil) {
self.startUnitPoint = startUnitPoint
self.endUnitPoint = endUnitPoint
self.colors = colors
self.locations = locations
super.init()
}
override func drawParameters(forAsyncLayer layer: _ASDisplayLayer) -> NSObjectProtocol? {
return self
}
All one needs to do is to create a GradientNote and fill in the parameters in the init() function. And then treat it as a regular Node and let ASDK do the rest of the work!
coverTitleBackgroundNode = GradientNode(startingAt: CGPoint(x: 0.5, y: 1.0),
endingAt: CGPoint(x: 0.5, y: 0.0),
with: [UIColor.black.withAlphaComponent(Constants.CoverTitleBackgroundBlackAlpha), UIColor.clear])
coverTitleBackgroundNode!.isLayerBacked = true
coverTitleBackgroundNode!.isOpaque = false
automaticallyManagesSubnodes = true