how to make a gradient border of UIView?

14,381

Solution 1

This what i did and it worked perfectly

   extension CALayer {
    func addGradienBorder(colors:[UIColor],width:CGFloat = 1) {
        let gradientLayer = CAGradientLayer()
        gradientLayer.frame =  CGRect(origin: CGPointZero, size: self.bounds.size)
        gradientLayer.startPoint = CGPointMake(0.0, 0.5)
        gradientLayer.endPoint = CGPointMake(1.0, 0.5)
        gradientLayer.colors = colors.map({$0.CGColor})

        let shapeLayer = CAShapeLayer()
        shapeLayer.lineWidth = width
        shapeLayer.path = UIBezierPath(rect: self.bounds).CGPath
        shapeLayer.fillColor = nil
        shapeLayer.strokeColor = UIColor.blackColor().CGColor
        gradientLayer.mask = shapeLayer

        self.addSublayer(gradientLayer)
    }

}

Solution 2

Thanx Tiago Mendes for answer.

I improved functionality:

  • added corner radius.

And fixed some issues to ensure correct masking and given border width:

  • improved gradient layer frame;
  • improved mask path rounding rect.

Swift 5

public extension UIView {

    private static let kLayerNameGradientBorder = "GradientBorderLayer"

    func gradientBorder(width: CGFloat,
                        colors: [UIColor],
                        startPoint: CGPoint = CGPoint(x: 0.5, y: 0.0),
                        endPoint: CGPoint = CGPoint(x: 0.5, y: 1.0),
                        andRoundCornersWithRadius cornerRadius: CGFloat = 0) {

        let existingBorder = gradientBorderLayer()
        let border = existingBorder ?? CAGradientLayer()
        border.frame = CGRect(x: bounds.origin.x, y: bounds.origin.y,
                              width: bounds.size.width + width, height: bounds.size.height + width)
        border.colors = colors.map { return $0.cgColor }
        border.startPoint = startPoint
        border.endPoint = endPoint

        let mask = CAShapeLayer()
        let maskRect = CGRect(x: bounds.origin.x + width/2, y: bounds.origin.y + width/2,
                              width: bounds.size.width - width, height: bounds.size.height - width)
        mask.path = UIBezierPath(roundedRect: maskRect, cornerRadius: cornerRadius).cgPath
        mask.fillColor = UIColor.clear.cgColor
        mask.strokeColor = UIColor.white.cgColor
        mask.lineWidth = width

        border.mask = mask

        let exists = (existingBorder != nil)
        if !exists {
            layer.addSublayer(border)
        }
    }
    private func gradientBorderLayer() -> CAGradientLayer? {
        let borderLayers = layer.sublayers?.filter { return $0.name == UIView.kLayerNameGradientBorder }
        if borderLayers?.count ?? 0 > 1 {
            fatalError()
        }
        return borderLayers?.first as? CAGradientLayer
    }
}

And for more readable declaration of startPoint and endPoint of gradient layer I use this code:

public extension CGPoint {

    enum CoordinateSide {
        case topLeft, top, topRight, right, bottomRight, bottom, bottomLeft, left
    }

    static func unitCoordinate(_ side: CoordinateSide) -> CGPoint {
        switch side {
        case .topLeft:      return CGPoint(x: 0.0, y: 0.0)
        case .top:          return CGPoint(x: 0.5, y: 0.0)
        case .topRight:     return CGPoint(x: 1.0, y: 0.0)
        case .right:        return CGPoint(x: 0.0, y: 0.5)
        case .bottomRight:  return CGPoint(x: 1.0, y: 1.0)
        case .bottom:       return CGPoint(x: 0.5, y: 1.0)
        case .bottomLeft:   return CGPoint(x: 0.0, y: 1.0)
        case .left:         return CGPoint(x: 1.0, y: 0.5)
        }
    }
}

So final usage is:

view.gradientBorder(width: 3, colors: [.red, .orange], startPoint: .unitCoordinate(.top), endPoint: .unitCoordinate(.bottom), andRoundCornersWithRadius: 12)

Solution 3

You can make gradient border of view and corner radius(if you want) using this--

self.yourView.layer.cornerRadius=4;

self.yourView.layer.masksToBounds=YES;

CAGradientLayer *gradient = [CAGradientLayer layer];

gradient.frame = self.yourView.bounds;

gradient.colors = [NSArray arrayWithObjects:(id)[[UIColor colorWithRed:255/255.0 green:226/255.0 blue:138/255.0 alpha:1.0] CGColor], (id)[[UIColor colorWithRed:255/255.0 green:198/255.0 blue:91/255.0 alpha:0.9] CGColor],(id)[[UIColor colorWithRed:255/255.0 green:226/255.0 blue:138/255.0 alpha:1.0] CGColor], nil];

gradient.startPoint = CGPointMake(0.0, 0.0);

gradient.endPoint = CGPointMake(1, 1);

CAShapeLayer *shapeLayer =[[CAShapeLayer alloc] init];

shapeLayer.lineWidth = 15; // higher number higher border width

shapeLayer.path = [UIBezierPath bezierPathWithRect:self.yourView.bounds].CGPath;

shapeLayer.fillColor = nil;

shapeLayer.strokeColor = [UIColor blackColor].CGColor;

gradient.mask = shapeLayer;

[self.yourView.layer insertSublayer:gradient atIndex:0];

this will help you! Thanks

Solution 4

Here is another solution that is working on swift 4

import UIKit

public extension UIView {

    private static let kLayerNameGradientBorder = "GradientBorderLayer"

    func setGradientBorder(
        width: CGFloat,
        colors: [UIColor],
        startPoint: CGPoint = CGPoint(x: 0.5, y: 0),
        endPoint: CGPoint = CGPoint(x: 0.5, y: 1)
    ) {
        let existedBorder = gradientBorderLayer()
        let border = existedBorder ?? CAGradientLayer()
        border.frame = bounds
        border.colors = colors.map { return $0.cgColor }
        border.startPoint = startPoint
        border.endPoint = endPoint

        let mask = CAShapeLayer()
        mask.path = UIBezierPath(roundedRect: bounds, cornerRadius: 0).cgPath
        mask.fillColor = UIColor.clear.cgColor
        mask.strokeColor = UIColor.white.cgColor
        mask.lineWidth = width

        border.mask = mask

        let exists = existedBorder != nil
        if !exists {
            layer.addSublayer(border)
        }
    }


    private func gradientBorderLayer() -> CAGradientLayer? {
        let borderLayers = layer.sublayers?.filter { return $0.name == UIView.kLayerNameGradientBorder }
        if borderLayers?.count ?? 0 > 1 {
            fatalError()
        }
        return borderLayers?.first as? CAGradientLayer
    }

}

How to use:

view.setGradientBorder(width: 10, colors: [UIColor(red: 47, green: 198, blue: 176), UIColor(red: 67, green: 210, blue: 128)])

Solution 5

Here is how you would do it with Core Graphics. Create a UIView subclass and in its drawRect: create a gradient and overlay that with a black content rectangle:

- (void)drawRect:(CGRect)rect
{
    CGContextRef ctx = UIGraphicsGetCurrentContext();
    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();

    // Create and draw the gradient
    UIColor *gradientColorTop = [UIColor orangeColor];
    UIColor *gradientColorBottom = [UIColor yellowColor];
    NSArray *gradientColors = @[(id) gradientColorTop.CGColor, (id) gradientColorBottom.CGColor];
    CGFloat locations[] = {0.1, 0.80};

    CGGradientRef gradient = CGGradientCreateWithColors(colorSpace, (__bridge CFArrayRef)(gradientColors), locations);

    CGPoint startPoint = CGPointMake(CGRectGetMidX(rect), CGRectGetMinY(rect));
    CGPoint endPoint = CGPointMake(CGRectGetMidX(rect), CGRectGetMaxY(rect));

    UIBezierPath *gradientBorder = [UIBezierPath bezierPathWithRoundedRect:rect
    byRoundingCorners:UIRectCornerAllCorners cornerRadii:CGSizeMake(10.0, 10.0)];
    [gradientBorder addClip];

    CGContextDrawLinearGradient(ctx, gradient, startPoint, endPoint, 0);

    // Draw the inner content rectangle
    UIBezierPath *contentPath = [UIBezierPath bezierPathWithRect:CGRectInset(rect, 20.0, 20.0)];
    [[UIColor blackColor] setFill];
    [contentPath fill];

    CGGradientRelease(gradient);
    CGColorSpaceRelease(colorSpace);
}

That would produce a result similar to what you are trying to achieve.

Share:
14,381

Related videos on Youtube

remykits
Author by

remykits

Updated on September 15, 2022

Comments

  • remykits
    remykits over 1 year

    I want to make a gradient border of view like the following picture:

    Image

    but I don't know how do it exactly , i.e. what the gradient color I should use to do it? how to set my view to show a border like the image?

    I'm using the following code to get a border:

     self.view.layer.borderColor = [UIColor orangeColor].CGColor;
     self.view.layer.borderWidth = 2.0f;
    
  • Jake T.
    Jake T. over 7 years
    This solution is nearly working for me, but the border is getting cut off where the corners are rounded. Any idea how to make sure the border goes around the corners?
  • Jake T.
    Jake T. over 7 years
    Got it using the method [UIBezierPath bezierPathWithRoundedRect:self.layer.bounds cornerRadius:<radius>].CGPath I set this up as a class extension method on UIView, hence self.layer.bounds.
  • glm4
    glm4 over 6 years
    I noticed that the colors parameter is an UIColor array but the default value is a CGColor array. Great answer though :)
  • DawnSong
    DawnSong over 4 years
    Comparison of different methods give us much to think about.
  • Kashkashio
    Kashkashio almost 4 years
    Briliiant ! thank you. it allowed me to make a border to the screen and fixed the radius corner for iphone x. +1 !
  • 9to5ios
    9to5ios almost 4 years
    for the brilliant solution just put corner radius 40 for iPhone X series +1
  • eli7ah
    eli7ah almost 4 years
    Glad to help you with that! Actually if you want corner radius for iphone X series screens you should look at another way to draw bezier path - apple.stackexchange.com/questions/313713/…
  • Master AgentX
    Master AgentX almost 3 years
    How can I make this work for UIButton? Ideally it should work for any subclass of UIView, but it's not working for UIButton. I am just seeing a dot in the top left corner. @eli7ah
  • jonchoi
    jonchoi over 2 years
    Very cool! Upvoted. Question: how can we extend this to support external borders? (borders that grow outwards instead of inwards)
  • Ximena Flores de la Tijera
    Ximena Flores de la Tijera over 2 years
    Hmmm this is outdated