2

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

Shivam Bhalla
  • 1,879
  • 5
  • 35
  • 63
  • Generally editing the node's view should be in the main thread, If not it throws an exception - 'This method must be called on the main thread'. I'm trying to find a cool example that shows how to do it in a relatively easy way. – Itai Spector Jul 19 '17 at 09:51

4 Answers4

3

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

Kyle Williamson
  • 2,251
  • 6
  • 43
  • 75
1

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
  • Works perfectly except for corner radius. You can just replace context.clip(to: bounds) by let path = UIBezierPath(roundedRect: bounds, cornerRadius: parameters.cornerRadius) path.addClip() – antoine Oct 01 '18 at 15:54
0

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;
Community
  • 1
  • 1
Itai Spector
  • 652
  • 11
  • 24
0
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)
Leonif
  • 466
  • 4
  • 17