Shivam Bhalla
Shivam Bhalla

Reputation: 1919

How to add gradient to ASDisplayNode or ASButtonNode

I am looking to add a gradient background to an ASDisplayNode/ASButtonNode. I have tried creating a gradient layer and adding it as a sublayer like this -

CAGradientLayer *gradient = [CAGradientLayer layer];
gradient.frame = button.frame;
gradient.colors = @[[UIColor redColor], [UIColor blueColor]];
[button.view.layer insertSublayer:gradient atIndex:0];

where button is of type ASButtonNode, but this just gives a white background to the button. I haven't been able to find much documentation for achieving this either.

How can I go about adding a background given an array of UIColor and a CGFloat angle?

Thanks

Upvotes: 2

Views: 2130

Answers (4)

Leonif
Leonif

Reputation: 482

extension ASDisplayNode {
    func gradient(from color1: UIColor, to color2: UIColor) {
        DispatchQueue.main.async {

            let size = self.view.frame.size
            let width = size.width
            let height = size.height


            let gradient: CAGradientLayer = CAGradientLayer()
            gradient.colors = [color1.cgColor, color2.cgColor]
            gradient.locations = [0.0 , 1.0]
            gradient.startPoint = CGPoint(x: 0.0, y: height/2)
            gradient.endPoint = CGPoint(x: 1.0, y: height/2)
            gradient.cornerRadius = 30
            gradient.frame = CGRect(x: 0.0, y: 0.0, width: width, height: height)
            self.view.layer.insertSublayer(gradient, at: 0)
        }
    }
}

Example:

node.gradient(from: .white, to: .red)

Upvotes: 0

BiscottiGelato
BiscottiGelato

Reputation: 61

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

Upvotes: 1

Dmitry Molokov
Dmitry Molokov

Reputation: 185

Swift 3.

extension UIView {

       func gradient(color1: UIColor, color2: UIColor) -> CAGradientLayer {
             let gradient: CAGradientLayer = CAGradientLayer()
             gradient.colors = [color1.cgColor, color2.cgColor]
             gradient.locations = [0.0 , 1.0]
             gradient.startPoint = CGPoint(x: 0.0, y: 0.0)
             gradient.endPoint = CGPoint(x: 1.0, y: 1.0)
             gradient.frame = CGRect(x: 0.0, y: 0.0, width: frame.size.width, height: frame.size.height)
             return gradient
          } 
    }

Example:

let gradient = self.cardGradientNode.view.gradient(
    color1: gradient.start, 
    color2: gradient.end
)
self.cardGradientNode.view.layer.insertSublayer(gradient, at: 0)

Result: enter image description here

Upvotes: 3

Itai Spector
Itai Spector

Reputation: 652

You've asked the same question on AsyncDisplayKit's GitHub issues: https://github.com/facebookarchive/AsyncDisplayKit/issues/3253

You've been answered with 2 possibilities:

1: Create a CAGradientLayer and add it as sublayer of the node

2: Override the + (void)draw... method of the node and draw the gradient there.

The first option must be in the main thread, so that wouldn't grant us the mighty powers of ASDK. The second option is more complex, but I can post some example from the ASDK example projects.

based on https://github.com/facebookarchive/AsyncDisplayKit/blob/master/examples/VerticalWithinHorizontalScrolling/Sample/RandomCoreGraphicsNode.m

and

https://www.raywenderlich.com/124311/asyncdisplaykit-2-0-tutorial-getting-started

1. Subclass ASDisplayNode

#import <AsyncDisplayKit/AsyncDisplayKit.h>

@interface GradientNode : ASDisplayNode

@end

2. Override drawRect method (Basic gradient with 2 colors - black to transparent)

@implementation GradientNode

+(void)drawRect:(CGRect)bounds withParameters:(id)parameters isCancelled:
    (asdisplaynode_iscancelled_block_t)isCancelledBlock isRasterizing:
    (BOOL)isRasterizing{

        CGFloat locations[2];
        NSMutableArray *colors = [NSMutableArray arrayWithCapacity:2];
        [colors addObject:(id)[[UIColor clearColor] CGColor]];
        locations[0] = 0.0;
        [colors addObject:(id)[[UIColor blackColor] CGColor]];
        locations[1] = 1.0;

        CGContextRef ctx = UIGraphicsGetCurrentContext();
        CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
        CGGradientRef gradient = CGGradientCreateWithColors(colorSpace, 
        (CFArrayRef)colors, locations);

        CGContextDrawLinearGradient(ctx, gradient, CGPointZero, 
        CGPointMake(bounds.size.width, bounds.size.height), 0);

        CGGradientRelease(gradient);
        CGColorSpaceRelease(colorSpace);
    }

    @end

3. Setuping the Node (Important! when using gradient with clear colors, otherwise it draws an opaqued black view)

_gradientNode = [GradientNode new];
_gradientNode.layerBacked = YES;
_gradientNode.opaque = NO;

Upvotes: 0

Related Questions