27

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;
Vinoth Krishnan
  • 2,925
  • 6
  • 29
  • 34
remykits
  • 1,735
  • 4
  • 18
  • 20
  • See http://stackoverflow.com/questions/10200814/how-to-set-a-gradient-border-on-uiview?rq=1 – rmaddy Mar 04 '13 at 03:58

6 Answers6

25

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)
    }

}
17

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 = .init(x: 0.5, y: 0),
        endPoint: CGPoint = .init(x: 0.5, y: 1),
        andRoundCornersWithRadius cornerRadius: CGFloat = 0
    ) {
        let existingBorder = gradientBorderLayer()
        let border = existingBorder ?? .init()
        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 { $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 isAlreadyAdded = (existingBorder != nil)
        if !isAlreadyAdded {
            layer.addSublayer(border)
        }
    }

    private func gradientBorderLayer() -> CAGradientLayer? {
        let borderLayers = layer.sublayers?.filter {
            $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 {
        let x: CGFloat
        let y: CGFloat

        switch side {
        case .topLeft:      x = 0.0; y = 0.0
        case .top:          x = 0.5; y = 0.0
        case .topRight:     x = 1.0; y = 0.0
        case .right:        x = 0.0; y = 0.5
        case .bottomRight:  x = 1.0; y = 1.0
        case .bottom:       x = 0.5; y = 1.0
        case .bottomLeft:   x = 0.0; y = 1.0
        case .left:         x = 1.0; y = 0.5
        }
        return .init(x: x, y: y)
    }
}

So final usage is:

view.gradientBorder(
    width: 3, 
    colors: [.red, .orange], 
    startPoint: .unitCoordinate(.top),
    endPoint: .unitCoordinate(.bottom),
    andRoundCornersWithRadius: 12
)
eli7ah
  • 355
  • 2
  • 10
  • Briliiant ! thank you. it allowed me to make a border to the screen and fixed the radius corner for iphone x. +1 ! – Kashkashio Jun 28 '20 at 10:35
  • for the brilliant solution just put corner radius 40 for iPhone X series +1 – 9to5ios Jul 01 '20 at 11:15
  • 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 - https://apple.stackexchange.com/questions/313713/what-is-the-definitive-iphone-x-corner-radius – eli7ah Jul 31 '20 at 12:51
  • 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 – Master AgentX Aug 29 '21 at 17:04
  • Very cool! Upvoted. Question: how can we extend this to support external borders? (borders that grow outwards instead of inwards) – jonchoi Sep 09 '21 at 19:44
  • 1
    If you are using AutoLayout and the dimensions of the border are not accurate, first call view.superview?.layoutIfNeeded(), and / or try adding the gradientBorder after the layout has taken place (in viewDidLayoutSubviews for example) – l3bel Jan 11 '23 at 12:10
7

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

Rohit Khandelwal
  • 1,778
  • 15
  • 23
  • 2
    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. Nov 23 '16 at 21:51
  • 7
    Got it using the method `[UIBezierPath bezierPathWithRoundedRect:self.layer.bounds cornerRadius:].CGPath` I set this up as a class extension method on UIView, hence `self.layer.bounds`. – Jake T. Nov 23 '16 at 22:42
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)])

Tiago Mendes
  • 4,572
  • 1
  • 29
  • 35
2

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.

jverrijt
  • 696
  • 4
  • 10
1

objective-c version of Christos Hadjikyriacou's answer

  @implementation CALayer(Border)

-(void) addGradientBorder {

CAGradientLayer *gradientLayer =  [[CAGradientLayer alloc] init];
gradientLayer.frame = CGRectMake(0, 0, self.bounds.size.width, self.bounds.size.height);
gradientLayer.startPoint = CGPointMake(0.0, 0.5);
gradientLayer.endPoint = CGPointMake(1.0, 0.5);
gradientLayer.colors = @[(id)[UIColor redColor].CGColor, (id)[UIColor blueColor].CGColor];

CAShapeLayer *shapeLayer =[[CAShapeLayer alloc] init];
shapeLayer.lineWidth = 0.5;
shapeLayer.path = [UIBezierPath bezierPathWithRect:self.bounds].CGPath;
shapeLayer.fillColor = nil;
shapeLayer.strokeColor = [UIColor blackColor].CGColor;
gradientLayer.mask = shapeLayer;
[self addSublayer : gradientLayer];
}
@end
JAHelia
  • 6,934
  • 17
  • 74
  • 134