Dashed line border around UIView - ios

How do I add dashed line border around UIView.
Something Like this

Another method if you like sublayers. In your custom view's init, put this (_border is an ivar):
_border = [CAShapeLayer layer];
_border.strokeColor = [UIColor colorWithRed:67/255.0f green:37/255.0f blue:83/255.0f alpha:1].CGColor;
_border.fillColor = nil;
_border.lineDashPattern = #[#4, #2];
[self.layer addSublayer:_border];
And in your layoutsubviews, put this:
_border.path = [UIBezierPath bezierPathWithRect:self.bounds].CGPath;
_border.frame = self.bounds;

You can set the border with this pattern using Layer and Bezier path like below examples.
Objective-C
CAShapeLayer *yourViewBorder = [CAShapeLayer layer];
yourViewBorder.strokeColor = [UIColor blackColor].CGColor;
yourViewBorder.fillColor = nil;
yourViewBorder.lineDashPattern = #[#2, #2];
yourViewBorder.frame = yourView.bounds;
yourViewBorder.path = [UIBezierPath bezierPathWithRect:yourView.bounds].CGPath;
[yourView.layer addSublayer:yourViewBorder];
Swift 3.1
var yourViewBorder = CAShapeLayer()
yourViewBorder.strokeColor = UIColor.black.cgColor
yourViewBorder.lineDashPattern = [2, 2]
yourViewBorder.frame = yourView.bounds
yourViewBorder.fillColor = nil
yourViewBorder.path = UIBezierPath(rect: yourView.bounds).cgPath
yourView.layer.addSublayer(yourViewBorder)
You can also set different types of design using pattern image like below example.
[yourView.layer setBorderWidth:5.0];
[yourView.layer setBorderColor:[[UIColor colorWithPatternImage:[UIImage imageNamed:#"DotedImage.png"]] CGColor]];///just add image name and create image with dashed or doted drawing and add here
Here you've to add <QuartzCore/QuartzCore> framework in the project and import it with below line in YourViewController.m file.
#import <QuartzCore/QuartzCore.h>

For those of you working in Swift, this class extension on UIView makes it easy. This was based on sunshineDev's answer.
extension UIView {
func addDashedBorder() {
let color = UIColor.red.cgColor
let shapeLayer:CAShapeLayer = CAShapeLayer()
let frameSize = self.frame.size
let shapeRect = CGRect(x: 0, y: 0, width: frameSize.width, height: frameSize.height)
shapeLayer.bounds = shapeRect
shapeLayer.position = CGPoint(x: frameSize.width/2, y: frameSize.height/2)
shapeLayer.fillColor = UIColor.clear.cgColor
shapeLayer.strokeColor = color
shapeLayer.lineWidth = 2
shapeLayer.lineJoin = CAShapeLayerLineJoin.round
shapeLayer.lineDashPattern = [6,3]
shapeLayer.path = UIBezierPath(roundedRect: shapeRect, cornerRadius: 5).cgPath
self.layer.addSublayer(shapeLayer)
}
}
To use it:
anyView.addDashedBorder()

Here is a UIView subclass that can work for any project, it also works for round views:
import UIKit
class CustomDashedView: UIView {
#IBInspectable var cornerRadius: CGFloat = 0 {
didSet {
layer.cornerRadius = cornerRadius
layer.masksToBounds = cornerRadius > 0
}
}
#IBInspectable var dashWidth: CGFloat = 0
#IBInspectable var dashColor: UIColor = .clear
#IBInspectable var dashLength: CGFloat = 0
#IBInspectable var betweenDashesSpace: CGFloat = 0
var dashBorder: CAShapeLayer?
override func layoutSubviews() {
super.layoutSubviews()
dashBorder?.removeFromSuperlayer()
let dashBorder = CAShapeLayer()
dashBorder.lineWidth = dashWidth
dashBorder.strokeColor = dashColor.cgColor
dashBorder.lineDashPattern = [dashLength, betweenDashesSpace] as [NSNumber]
dashBorder.frame = bounds
dashBorder.fillColor = nil
if cornerRadius > 0 {
dashBorder.path = UIBezierPath(roundedRect: bounds, cornerRadius: cornerRadius).cgPath
} else {
dashBorder.path = UIBezierPath(rect: bounds).cgPath
}
layer.addSublayer(dashBorder)
self.dashBorder = dashBorder
}
}
This way you can edit from the Storyboard like this:
A pair of results:

Swift 3:
import UIKit
class UIViewWithDashedLineBorder: UIView {
override func draw(_ rect: CGRect) {
let path = UIBezierPath(roundedRect: rect, cornerRadius: 0)
UIColor.purple.setFill()
path.fill()
UIColor.orange.setStroke()
path.lineWidth = 5
let dashPattern : [CGFloat] = [10, 4]
path.setLineDash(dashPattern, count: 2, phase: 0)
path.stroke()
}
}
Use in a storyboard (as custom class) or directly in code:
let v = UIViewWithDashedLineBorder(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
Result:

Building upon what Prasad G has suggested I created a method inside a UIImage Extras class with the following:
- (CAShapeLayer *) addDashedBorderWithColor: (CGColorRef) color {
CAShapeLayer *shapeLayer = [CAShapeLayer layer];
CGSize frameSize = self.size;
CGRect shapeRect = CGRectMake(0.0f, 0.0f, frameSize.width, frameSize.height);
[shapeLayer setBounds:shapeRect];
[shapeLayer setPosition:CGPointMake( frameSize.width/2,frameSize.height/2)];
[shapeLayer setFillColor:[[UIColor clearColor] CGColor]];
[shapeLayer setStrokeColor:color];
[shapeLayer setLineWidth:5.0f];
[shapeLayer setLineJoin:kCALineJoinRound];
[shapeLayer setLineDashPattern:
[NSArray arrayWithObjects:[NSNumber numberWithInt:10],
[NSNumber numberWithInt:5],
nil]];
UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:shapeRect cornerRadius:15.0];
[shapeLayer setPath:path.CGPath];
return shapeLayer;
}
It's important to point out that if you define your shape's position as (0,0), the bottom corner of the border will be placed in the center of the image, that's why I set it to: (frameSize.width/2,frameSize.height/2)
I then use my method to get the dashed border using the UIImage of my UIImageView and add the CAShapeLayer as a sublayer of the UIImageView layer:
[myImageView.layer addSublayer:[myImageView.image addDashedBorderWithColor:[[UIColor whiteColor] CGColor]]];

Use CGContextSetLineDash() method.
CGFloat dashPattern[]= {3.0, 2};
context =UIGraphicsGetCurrentContext();
CGContextSetRGBStrokeColor(context, 1.0, 1.0, 1.0, 1.0);
// And draw with a blue fill color
CGContextSetRGBFillColor(context, 0.0, 0.0, 1.0, 1.0);
// Draw them with a 2.0 stroke width so they are a bit more visible.
CGContextSetLineWidth(context, 4.0);
CGContextSetLineDash(context, 0.0, dashPattern, 2);
CGContextAddRect(context, self.bounds);
// Close the path
CGContextClosePath(context);
CGContextStrokePath(context);
// Fill & stroke the path
CGContextDrawPath(context, kCGPathFillStroke);
I think it will be helpful to you.

For this you need add CAShapeLayer for that particular object
CAShapeLayer * dotborder = [CAShapeLayer layer];
dotborder.strokeColor = [UIColor redColor].CGColor;//your own color
dotborder.fillColor = nil;
dotborder.lineDashPattern = #[#4, #2];//your own patten
[codeBtn.layer addSublayer:dotborder];
dotborder.path = [UIBezierPath bezierPathWithRect:codeBtn.bounds].CGPath;
dotborder.frame = codeBtn.bounds;

Swift 4.2
Based off rmooney's answer as a UIView extension with configurable parameters that have default values set.
Note this does not work if the view has self.translatesAutoresizingMaskIntoConstraints = false
extension UIView {
func addDashedBorder(_ color: UIColor = UIColor.black, withWidth width: CGFloat = 2, cornerRadius: CGFloat = 5, dashPattern: [NSNumber] = [3,6]) {
let shapeLayer = CAShapeLayer()
shapeLayer.bounds = bounds
shapeLayer.position = CGPoint(x: bounds.width/2, y: bounds.height/2)
shapeLayer.fillColor = nil
shapeLayer.strokeColor = color.cgColor
shapeLayer.lineWidth = width
shapeLayer.lineJoin = CAShapeLayerLineJoin.round // Updated in swift 4.2
shapeLayer.lineDashPattern = dashPattern
shapeLayer.path = UIBezierPath(roundedRect: bounds, cornerRadius: cornerRadius).cgPath
self.layer.addSublayer(shapeLayer)
}
}

• Swift 5
• Works with autolayout
• Works with the corner radius
import UIKit
class DashedBorderView: UIView {
private let dashedLineColor = UIColor.black.cgColor
private let dashedLinePattern: [NSNumber] = [6, 3]
private let dashedLineWidth: CGFloat = 4
private let borderLayer = CAShapeLayer()
init() {
super.init(frame: CGRect.zero)
borderLayer.strokeColor = dashedLineColor
borderLayer.lineDashPattern = dashedLinePattern
borderLayer.backgroundColor = UIColor.clear.cgColor
borderLayer.fillColor = UIColor.clear.cgColor
borderLayer.lineWidth = dashedLineWidth
layer.addSublayer(borderLayer)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func draw(_ rect: CGRect) {
borderLayer.frame = bounds
borderLayer.path = UIBezierPath(roundedRect: rect, cornerRadius: layer.cornerRadius).cgPath
}
}

Swift version of the QuartzCore answer.
import QuartzCore
let dottedPattern = UIImage(named: "dottedPattern")
myView.layer.borderWidth = 1
myView.layer.borderColor = UIColor(patternImage: dottedPattern!).CGColor
The CAShapeLayer approach works, but the QuartzCore approach is better at handling a Table View reload, if the UIView is inside a cell.
For the image, you can use something like this (it's really small):
I tend to prefer vector over PNGs when I can get away with it:
Within Sketch, create a 4x4 pixel rectangle.
Make a total of four of these
Group them into a foursquare, alternating colors
Export the group as PDF
Within Images.xcassets, create a New Image Set called dottedPattern
Change the Scale Factors to Single Vector
Drop in your PDF

For Xamarin.iOS dashed/dotted border.
dottedLayer = new CAShapeLayer();
dottedLayer.StrokeColor = UIColor.FromRGB(202, 202, 208).CGColor;
dottedLayer.FillColor = null;
dottedLayer.LineDashPattern = new[] { new NSNumber(4), new NSNumber(2) };
dottedLayer.Path = UIBezierPath.FromRect(YourView.Bounds).CGPath; //for square
dottedLayer.Path = UIBezierPath.FromRoundedRect(YourView.Bounds, 5).CGPath; //for rounded corners
dottedLayer.Frame = YourView.Bounds;
YourView.Layer.AddSublayer(dottedLayer);

In Swift 3
let border = CAShapeLayer();
border.strokeColor = UIColor.black.cgColor;
border.fillColor = nil;
border.lineDashPattern = [4, 4];
border.path = UIBezierPath(rect: theView.bounds).cgPath
border.frame = theView.bounds;
theView.layer.addSublayer(border);

For Swift 5
extension UIView {
func addDashBorder() {
let color = UIColor.white.cgColor
let shapeLayer:CAShapeLayer = CAShapeLayer()
let frameSize = self.frame.size
let shapeRect = CGRect(x: 0, y: 0, width: frameSize.width, height: frameSize.height)
shapeLayer.bounds = shapeRect
shapeLayer.name = "DashBorder"
shapeLayer.position = CGPoint(x: frameSize.width/2, y: frameSize.height/2)
shapeLayer.fillColor = UIColor.clear.cgColor
shapeLayer.strokeColor = color
shapeLayer.lineWidth = 1.5
shapeLayer.lineJoin = .round
shapeLayer.lineDashPattern = [2,4]
shapeLayer.path = UIBezierPath(roundedRect: shapeRect, cornerRadius: 10).cgPath
self.layer.masksToBounds = false
self.layer.addSublayer(shapeLayer)
}
}
How to add
vw.addDashBorder()
How to remove border again
let _ = vw.layer.sublayers?.filter({$0.name == "DashBorder"}).map({$0.removeFromSuperlayer()})

This is if you wanted it in Swift 2
func addDashedLineBorderWithColor(color:UIColor) {
let _ = self.sublayers?.filter({$0.name == "DashedBorder"}).map({$0.removeFromSuperlayer()})
let border = CAShapeLayer();
border.name = "DashedBorder"
border.strokeColor = color.CGColor;
border.fillColor = nil;
border.lineDashPattern = [4, 4];
border.path = UIBezierPath(rect: self.bounds).CGPath
border.frame = self.bounds;
self.addSublayer(border);
}

try bellow code
- (void)drawRect:(CGRect)rect {
//// Color Declarations
UIColor* fillColor = [UIColor colorWithRed: 1 green: 1 blue: 1 alpha: 1];
UIColor* strokeColor = [UIColor colorWithRed: 0.29 green: 0.565 blue: 0.886 alpha: 1];
//// Rectangle Drawing
UIBezierPath* rectanglePath = [UIBezierPath bezierPathWithRoundedRect:rect cornerRadius: 6];
[fillColor setFill];
[rectanglePath fill];
[strokeColor setStroke];
rectanglePath.lineWidth = 1;
CGFloat rectanglePattern[] = {6, 2, 6, 2};
[rectanglePath setLineDash: rectanglePattern count: 4 phase: 0];
[rectanglePath stroke];
[super drawRect:rect];
}
for one like bellow

Swift solution with custom class worked with autolayout
customized from #Iain Smith
class DashedBorderView: UIView {
#IBInspectable var cornerRadius: CGFloat = 4
#IBInspectable var borderColor: UIColor = UIColor.black
#IBInspectable var dashPaintedSize: Int = 2
#IBInspectable var dashUnpaintedSize: Int = 2
let dashedBorder = CAShapeLayer()
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}
private func commonInit() {
//custom initialization
self.layer.addSublayer(dashedBorder)
applyDashBorder()
}
override func layoutSublayers(of layer: CALayer) {
super.layoutSublayers(of: layer)
applyDashBorder()
}
func applyDashBorder() {
dashedBorder.strokeColor = borderColor.cgColor
dashedBorder.lineDashPattern = [NSNumber(value: dashPaintedSize), NSNumber(value: dashUnpaintedSize)]
dashedBorder.fillColor = nil
dashedBorder.cornerRadius = cornerRadius
dashedBorder.path = UIBezierPath(rect: self.bounds).cgPath
dashedBorder.frame = self.bounds
}
}

extension UIView{
func addDashedLineBorder() {
let color = UIColor.black.cgColor
let shapeLayer:CAShapeLayer = CAShapeLayer()
let frameSize = (self.frame.size)
let shapeRect = CGRect(x: 0, y: 0, width: self.frame.width, height: self.frame.height)
shapeLayer.bounds = shapeRect
shapeLayer.position = CGPoint(x: frameSize.width/2, y: frameSize.height/2)
shapeLayer.fillColor = UIColor.clear.cgColor
shapeLayer.strokeColor = color
shapeLayer.lineWidth = 1
shapeLayer.lineJoin = kCALineJoinRound
shapeLayer.lineDashPattern = [2,2]
shapeLayer.path = UIBezierPath(rect: shapeRect).cgPath
self.layer.addSublayer(shapeLayer)
}
}
and call this function in viewdidLoad() with delay:
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
// Your code with delay
self.YourView.addDashedBorder()
}

Swift 4,5 :- addDashedBorder
extension UIView {
func setCellDeshBorder(color: UIColor = .appBlue?.withAlphaComponent(0.50) ?? UIColor()) {
let shapeLayer:CAShapeLayer = CAShapeLayer()
let frameSize = self.frame.size
let shapeRect = CGRect(x: 0, y: 0, width: frameSize.width, height: frameSize.height)
shapeLayer.bounds = shapeRect
shapeLayer.name = "DashBorder"
shapeLayer.position = CGPoint(x: frameSize.width/2, y: frameSize.height/2)
shapeLayer.fillColor = UIColor.clear.cgColor
shapeLayer.strokeColor = color.cgColor
shapeLayer.lineWidth = 1.5
shapeLayer.lineJoin = .round
shapeLayer.lineDashPattern = [2,4]
shapeLayer.path = UIBezierPath(roundedRect: shapeRect, cornerRadius: 10).cgPath
self.layer.masksToBounds = false
self.layer.addSublayer(shapeLayer)
}
func removeCellDeshBorder() {
_ = self.layer.sublayers?.filter({$0.name == "DashBorder"}).map({$0.removeFromSuperlayer()})
}
}

I ended up creating a IB Designable using some of #Chris implementation:
CurvedDashedBorderUIVIew.h:
#import <UIKit/UIKit.h>
IB_DESIGNABLE
#interface CurvedDashedBorderUIVIew : UIView
#property (nonatomic) IBInspectable CGFloat cornerRadius;
#property (nonatomic) IBInspectable UIColor *borderColor;
#property (nonatomic) IBInspectable int dashPaintedSize;
#property (nonatomic) IBInspectable int dashUnpaintedSize;
#property (strong, nonatomic) CAShapeLayer *border;
#end
CurvedDashedBorderUIVIew.m:
#import "CurvedDashedBorderUIVIew.h"
#implementation CurvedDashedBorderUIVIew
- (instancetype)init
{
self = [super init];
if (self) {
[self setup];
}
return self;
}
- (instancetype)initWithCoder:(NSCoder *)coder
{
self = [super initWithCoder:coder];
if (self) {
[self setup];
}
return self;
}
- (instancetype)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
[self setup];
}
return self;
}
-(void)setup
{
_border = [CAShapeLayer layer];
[self.layer addSublayer:_border];
}
-(void)layoutSubviews {
[super layoutSubviews];
self.layer.cornerRadius = self.cornerRadius;
_border.strokeColor = self.borderColor.CGColor;
_border.fillColor = nil;
_border.lineDashPattern = #[[NSNumber numberWithInt:_dashPaintedSize],
[NSNumber numberWithInt:_dashUnpaintedSize]];
_border.path = [UIBezierPath bezierPathWithRoundedRect:self.bounds cornerRadius:self.cornerRadius].CGPath;
_border.frame = self.bounds;
}
#end
then just set it up in the xib/storyboard:

You can simply create a IBDesignable class like this:
import UIKit
#IBDesignable
class BorderedView: UIView {
#IBInspectable var cornerRadius: CGFloat = 0
#IBInspectable var borderWidth: CGFloat = 0
#IBInspectable var borderColor: UIColor = UIColor.clear
override func draw(_ rect: CGRect) {
let path = UIBezierPath(roundedRect: rect, cornerRadius: cornerRadius)
path.lineWidth = borderWidth
borderColor.setStroke()
let dashPattern : [CGFloat] = [10, 4]
path.setLineDash(dashPattern, count: 2, phase: 0)
path.stroke()
}
}
Then just subclass your view with BorderedView from Xcode.
This way you can set the border color and border width very easily from the interface builder!

Swift 5+
import UIKit
class DashedBorderView: UIView {
private let borderLayer = CAShapeLayer()
init(color: UIColor, width: CGFloat = 1) {
super.init(frame: CGRect.zero)
let pattern: [NSNumber] = [NSNumber(value: Float(5 * width)), NSNumber(value: Float(3 * width))]
borderLayer.backgroundColor = nil
borderLayer.fillColor = nil
borderLayer.lineDashPattern = pattern
borderLayer.lineWidth = width
borderLayer.strokeColor = color.cgColor
layer.addSublayer(borderLayer)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func draw(_ rect: CGRect) {
borderLayer.frame = bounds
borderLayer.path = UIBezierPath(roundedRect: rect, cornerRadius: layer.cornerRadius).cgPath
}
}
How to use:
// f.e. inside UIViewController
let viewWithDashedBorder = DashedBorderView(color: .red, width: 2)
view.addSubview(viewWithDashedBorder)

In swift 4 I created an UIView extension with the following function:
func borderDash(withRadius cornerRadius: Float, borderWidth: Float, borderColor: UIColor, dashSize: Int) {
let currentFrame = self.bounds
let shapeLayer = CAShapeLayer()
let path = CGMutablePath()
let radius = CGFloat(cornerRadius)
// Points - Eight points that define the round border. Each border is defined by two points.
let topLeftPoint = CGPoint(x: radius, y: 0)
let topRightPoint = CGPoint(x: currentFrame.size.width - radius, y: 0)
let middleRightTopPoint = CGPoint(x: currentFrame.size.width, y: radius)
let middleRightBottomPoint = CGPoint(x: currentFrame.size.width, y: currentFrame.size.height - radius)
let bottomRightPoint = CGPoint(x: currentFrame.size.width - radius, y: currentFrame.size.height)
let bottomLeftPoint = CGPoint(x: radius, y: currentFrame.size.height)
let middleLeftBottomPoint = CGPoint(x: 0, y: currentFrame.size.height - radius)
let middleLeftTopPoint = CGPoint(x: 0, y: radius)
// Points - Four points that are the center of the corners borders.
let cornerTopRightCenter = CGPoint(x: currentFrame.size.width - radius, y: radius)
let cornerBottomRightCenter = CGPoint(x: currentFrame.size.width - radius, y: currentFrame.size.height - radius)
let cornerBottomLeftCenter = CGPoint(x: radius, y: currentFrame.size.height - radius)
let cornerTopLeftCenter = CGPoint(x: radius, y: radius)
// Angles - The corner radius angles.
let topRightStartAngle = CGFloat(Double.pi * 3 / 2)
let topRightEndAngle = CGFloat(0)
let bottomRightStartAngle = CGFloat(0)
let bottmRightEndAngle = CGFloat(Double.pi / 2)
let bottomLeftStartAngle = CGFloat(Double.pi / 2)
let bottomLeftEndAngle = CGFloat(Double.pi)
let topLeftStartAngle = CGFloat(Double.pi)
let topLeftEndAngle = CGFloat(Double.pi * 3 / 2)
// Drawing a border around a view.
path.move(to: topLeftPoint)
path.addLine(to: topRightPoint)
path.addArc(center: cornerTopRightCenter,
radius: radius,
startAngle: topRightStartAngle,
endAngle: topRightEndAngle,
clockwise: false)
path.addLine(to: middleRightBottomPoint)
path.addArc(center: cornerBottomRightCenter,
radius: radius,
startAngle: bottomRightStartAngle,
endAngle: bottmRightEndAngle,
clockwise: false)
path.addLine(to: bottomLeftPoint)
path.addArc(center: cornerBottomLeftCenter,
radius: radius,
startAngle: bottomLeftStartAngle,
endAngle: bottomLeftEndAngle,
clockwise: false)
path.addLine(to: middleLeftTopPoint)
path.addArc(center: cornerTopLeftCenter,
radius: radius,
startAngle: topLeftStartAngle,
endAngle: topLeftEndAngle,
clockwise: false)
// Path is set as the shapeLayer object's path.
shapeLayer.path = path;
shapeLayer.backgroundColor = UIColor.clear.cgColor
shapeLayer.frame = currentFrame
shapeLayer.masksToBounds = false
shapeLayer.setValue(0, forKey: "isCircle")
shapeLayer.fillColor = UIColor.clear.cgColor
shapeLayer.strokeColor = borderColor.cgColor
shapeLayer.lineWidth = CGFloat(borderWidth)
shapeLayer.lineDashPattern = [NSNumber(value: dashSize), NSNumber(value: dashSize)]
shapeLayer.lineCap = kCALineCapRound
self.layer.addSublayer(shapeLayer)
self.layer.cornerRadius = radius;
}

If you want this to work with cornerRadius then try this
tagView.clipsToBounds = YES;
tagView.layer.cornerRadius = 20.0f;
tagView.backgroundColor = [UIColor groupTableViewBackgroundColor];
CAShapeLayer *yourViewBorder = [CAShapeLayer layer];
yourViewBorder.strokeColor = [UIColor blackColor].CGColor;
yourViewBorder.fillColor = nil;
yourViewBorder.lineDashPattern = #[#2, #2];
yourViewBorder.frame = tagView.bounds;
// Create the path for to make circle
UIBezierPath *maskPath = [UIBezierPath bezierPathWithRoundedRect:tagView.bounds
byRoundingCorners:UIRectCornerAllCorners
cornerRadii:CGSizeMake(20, 20)];
yourViewBorder.path = maskPath.CGPath;
[tagView.layer addSublayer:yourViewBorder];

Improvement for #Chris's answer.
extension UIView {
func dashLinedBorder() -> CAShapeLayer {
let viewBorder = CAShapeLayer()
viewBorder.strokeColor = UIColor.black.cgColor
viewBorder.lineDashPattern = [4, 2]
viewBorder.fillColor = nil
self.layer.addSublayer(viewBorder)
return viewBorder
}
}
Define your CAShapeLayer inside the ViewController,
var viewBillingProofCAShapeLayer: CAShapeLayer!
override func viewDidLoad() {
self.viewBillingProofCAShapeLayer = self.viewBillingProofInner.dashLinedBorder()
}
then override viewDidLayoutSubviews
override func viewDidLayoutSubviews() {
self.viewBillingProofCAShapeLayer.path = UIBezierPath(roundedRect: self.viewBillingProofInner.bounds, byRoundingCorners: .allCorners, cornerRadii: CGSize(width: 4, height: 4)).cgPath
self.viewBillingProofCAShapeLayer.frame = self.viewBillingProofInner.bounds
}

SwiftUI
var body: some View {
Rectangle()
.strokeBorder(style: StrokeStyle(lineWidth: 4, dash: [10]))
}

Related

Draw dotted line at bottom of UITextField

I am trying to draw a dotted line at bottom of UITextField, but not got success. Below is what i tried so far. Please guide.
func addDashedBorder() {
let color = UIColor.white.cgColor
let width = CGFloat(2.0)
let shapeLayer:CAShapeLayer = CAShapeLayer()
let frameSize = self.frame.size
let shapeRect = CGRect(x: 0, y: frameSize.height, width: frameSize.width, height: 2.0)
shapeLayer.bounds = shapeRect
shapeLayer.position = CGPoint(x: frameSize.width/2, y: frameSize.height - width)
shapeLayer.fillColor = UIColor.darkGray.cgColor
shapeLayer.strokeColor = color
shapeLayer.lineWidth = 2.0
// shapeLayer.lineJoin = kCALineJoinRound
shapeLayer.lineDashPattern = [6,3]
shapeLayer.path = UIBezierPath(rect: shapeRect).cgPath//UIBezierPath(roundedRect: shapeRect, cornerRadius: 0).cgPath
self.layer.addSublayer(shapeLayer)
}
}
Output getting is:
Expected is:
Finally fixed:
extension UIView {
func addDashedLine(strokeColor: UIColor, lineWidth: CGFloat) {
backgroundColor = .clear
let shapeLayer = CAShapeLayer()
shapeLayer.name = "DashedTopLine"
shapeLayer.bounds = bounds
shapeLayer.position = CGPoint(x: frame.width / 2, y: frame.height * 1.2)
shapeLayer.fillColor = UIColor.clear.cgColor
shapeLayer.strokeColor = strokeColor.cgColor
shapeLayer.lineWidth = lineWidth
shapeLayer.lineJoin = kCALineJoinRound
shapeLayer.lineDashPattern = [6, 4]
let path = CGMutablePath()
path.move(to: CGPoint.zero)
path.addLine(to: CGPoint(x: frame.width, y: 0))
shapeLayer.path = path
layer.addSublayer(shapeLayer)
}
}
Got help from here: [drawing dashed line using CALayer
]1
Try this
Use dot image :
self.textField.layer.borderWidth = 3
self.textField.layer.borderColor = (UIColor(patternImage: UIImage(named: "dot")!)).CGColor
Updated:
Use extension below
mytextfield.addDashedLine(strokeColor:.red,lineWidth:1)
extension UIView {
func addDashedLine(strokeColor: UIColor, lineWidth: CGFloat) {
backgroundColor = .clear
let shapeLayer = CAShapeLayer()
shapeLayer.name = "DashedTopLine"
shapeLayer.bounds = bounds
shapeLayer.position = CGPoint(x:30, y: 40)
shapeLayer.fillColor = UIColor.clear.cgColor
shapeLayer.strokeColor = strokeColor.cgColor
shapeLayer.lineWidth = lineWidth
shapeLayer.lineJoin = kCALineJoinRound
shapeLayer.lineDashPattern = [4, 4]
let path = CGMutablePath()
path.move(to: CGPoint.zero)
path.addLine(to: CGPoint(x:500, y: 0))
shapeLayer.path = path
layer.addSublayer(shapeLayer)
}
}

How to add a dashed border for any particular edge of a UIView?

How can I add a dashed border in certain side of the UIView only?Have referred to Dashed line border around UIView link, but I am kind of confused while giving the path for all the edges of a view.ok so, this is my code for adding a straight line border in particular side of a view:
func addRightBorder(with color: UIColor, andWidth borderWidth: CGFloat,view:UIView) {
let border = UIView()
border.backgroundColor = color
border.autoresizingMask = [.flexibleHeight, .flexibleLeftMargin]
border.frame = CGRect(x: view.frame.size.width - borderWidth, y: 0, width: borderWidth, height: view.frame.size.height)
//border.addDashedLine(color: UIColor.red)
border.addDashedLine(color: UIColor.red)
view.addSubview(border)
}*
How can I achieve the same thing but for dashed/dotted lines?
In one of my app, I also needed a similar thing and I created UIView Extension.
Take a look at my code snippet,
extension UIView {
func removeDashedLine() {
_ = layer.sublayers?.filter({ $0.name == "DashedTopLine" }).map({ $0.removeFromSuperlayer() })
}
func addDashedLine(_ path: UIBezierPath ,pattern : [NSNumber] = [1, 5], color: UIColor = UIColor.red) {
self.backgroundColor = UIColor.clear
let shapeLayer: CAShapeLayer = CAShapeLayer()
let frameSize = self.frame.size
let shapeRect = CGRect(x: 0, y: 0, width: frameSize.width, height: frameSize.height)
shapeLayer.name = "DashedTopLine"
shapeLayer.frame = shapeRect
//shapeLayer.position = CGPoint(x: frameSize.width / 2, y: frameSize.height / 2)
shapeLayer.fillColor = UIColor.clear.cgColor
shapeLayer.strokeColor = color.cgColor
shapeLayer.lineWidth = 1
shapeLayer.lineJoin = kCALineJoinRound
shapeLayer.lineDashPattern = pattern
shapeLayer.path = nil
shapeLayer.path = path.cgPath
self.layer.insertSublayer(shapeLayer, at: 0)
}
}
It may not do exactly what you want but should help you

iOS - Draw a donut in an view controller

This is really simple, but I can't figure out what I'm doing wrong.
I have a view controller with one view in the center. I want to draw something like the circle below:
The main problem I'm having, is I can't get anytihng to show up on the view. I'm just trying to draw a line right now, but am obviously missing something key. Here is my code:
#interface ViewController ()
#property (weak, nonatomic) IBOutlet UIView *centerView;
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
[[UIColor brownColor] set];
CGContextRef currentContext =UIGraphicsGetCurrentContext();
CGContextSetLineWidth(currentContext,5.0f);
CGContextMoveToPoint(currentContext,50.0f, 10.0f);
CGContextAddLineToPoint(currentContext,100.0f, 200.0f);
CGContextStrokePath(currentContext);
}
You can do it with CAShapeLayer. Here is a playground that illustrates how to do it. As an added bonus, I threw in the animation.
import UIKit
import PlaygroundSupport
class AnimatedRingView: UIView {
private static let animationDuration = CFTimeInterval(2)
private let π = CGFloat.pi
private let startAngle = 1.5 * CGFloat.pi
private let strokeWidth = CGFloat(8)
var proportion = CGFloat(0.5) {
didSet {
setNeedsLayout()
}
}
private lazy var circleLayer: CAShapeLayer = {
let circleLayer = CAShapeLayer()
circleLayer.strokeColor = UIColor.gray.cgColor
circleLayer.fillColor = UIColor.clear.cgColor
circleLayer.lineWidth = self.strokeWidth
self.layer.addSublayer(circleLayer)
return circleLayer
}()
private lazy var ringlayer: CAShapeLayer = {
let ringlayer = CAShapeLayer()
ringlayer.fillColor = UIColor.clear.cgColor
ringlayer.strokeColor = self.tintColor.cgColor
ringlayer.lineCap = kCALineCapRound
ringlayer.lineWidth = self.strokeWidth
self.layer.addSublayer(ringlayer)
return ringlayer
}()
override func layoutSubviews() {
super.layoutSubviews()
let radius = (min(frame.size.width, frame.size.height) - strokeWidth - 2)/2
let circlePath = UIBezierPath(arcCenter: center, radius: radius, startAngle: startAngle, endAngle: startAngle + 2 * π, clockwise: true)
circleLayer.path = circlePath.cgPath
ringlayer.path = circlePath.cgPath
ringlayer.strokeEnd = proportion
}
func animateRing(From startProportion: CGFloat, To endProportion: CGFloat, Duration duration: CFTimeInterval = animationDuration) {
let animation = CABasicAnimation(keyPath: "strokeEnd")
animation.duration = duration
animation.fromValue = startProportion
animation.toValue = endProportion
animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut)
ringlayer.strokeEnd = endProportion
ringlayer.strokeStart = startProportion
ringlayer.add(animation, forKey: "animateRing")
}
}
let v = AnimatedRingView(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
PlaygroundPage.current.liveView = v
v.animateRing(From: 0, To: 0.5)

How to draw a circle path with color gradient stroke

I want to draw a circle with color gradient stroke like the following picture, on both iOS and macOS:
Is it possible to implement with CAShapeLayer or NSBezierPath/CGPath? Or any other ways?
In macOS 10.14 and later (as well as in iOS 12 and later), you can create a CAGradientLayer with a type of .conic, and then mask it with a circular arc. For example, for macOS:
class GradientArcView: NSView {
var startColor: NSColor = .white { didSet { setNeedsDisplay(bounds) } }
var endColor: NSColor = .blue { didSet { setNeedsDisplay(bounds) } }
var lineWidth: CGFloat = 3 { didSet { setNeedsDisplay(bounds) } }
private let gradientLayer: CAGradientLayer = {
let gradientLayer = CAGradientLayer()
gradientLayer.type = .conic
gradientLayer.startPoint = CGPoint(x: 0.5, y: 0.5)
gradientLayer.endPoint = CGPoint(x: 1, y: 0.5)
return gradientLayer
}()
override init(frame: CGRect = .zero) {
super.init(frame: frame)
configure()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
configure()
}
override func layout() {
super.layout()
updateGradient()
}
}
private extension GradientArcView {
func configure() {
wantsLayer = true
layer?.addSublayer(gradientLayer)
}
func updateGradient() {
gradientLayer.frame = bounds
gradientLayer.colors = [startColor, endColor].map { $0.cgColor }
let radius = (min(bounds.width, bounds.height) - lineWidth) / 2
let path = CGPath(ellipseIn: bounds.insetBy(dx: bounds.width / 2 - radius, dy: bounds.height / 2 - radius), transform: nil)
let mask = CAShapeLayer()
mask.fillColor = NSColor.clear.cgColor
mask.strokeColor = NSColor.white.cgColor
mask.lineWidth = lineWidth
mask.path = path
gradientLayer.mask = mask
}
}
Or, in iOS:
#IBDesignable
class GradientArcView: UIView {
#IBInspectable var startColor: UIColor = .white { didSet { setNeedsLayout() } }
#IBInspectable var endColor: UIColor = .blue { didSet { setNeedsLayout() } }
#IBInspectable var lineWidth: CGFloat = 3 { didSet { setNeedsLayout() } }
private let gradientLayer: CAGradientLayer = {
let gradientLayer = CAGradientLayer()
gradientLayer.type = .conic
gradientLayer.startPoint = CGPoint(x: 0.5, y: 0.5)
gradientLayer.endPoint = CGPoint(x: 1, y: 0.5)
return gradientLayer
}()
override init(frame: CGRect = .zero) {
super.init(frame: frame)
configure()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
configure()
}
override func layoutSubviews() {
super.layoutSubviews()
updateGradient()
}
}
private extension GradientArcView {
func configure() {
layer.addSublayer(gradientLayer)
}
func updateGradient() {
gradientLayer.frame = bounds
gradientLayer.colors = [startColor, endColor].map { $0.cgColor }
let center = CGPoint(x: bounds.midX, y: bounds.midY)
let radius = (min(bounds.width, bounds.height) - lineWidth) / 2
let path = UIBezierPath(arcCenter: center, radius: radius, startAngle: 0, endAngle: 2 * .pi, clockwise: true)
let mask = CAShapeLayer()
mask.fillColor = UIColor.clear.cgColor
mask.strokeColor = UIColor.white.cgColor
mask.lineWidth = lineWidth
mask.path = path.cgPath
gradientLayer.mask = mask
}
}
In earlier OS versions you have to do something manual, such as stroking a series of arcs in different colors. For example, in macOS:
import Cocoa
/// This draws an arc, of length `maxAngle`, ending at `endAngle. This is `#IBDesignable`, so if you
/// put this in a separate framework target, you can use this class in Interface Builder. The only
/// property that is not `#IBInspectable` is the `lineCapStyle` (as IB doesn't know how to show that).
///
/// If you want to make this animated, just use a `CADisplayLink` update the `endAngle` property (and
/// this will automatically re-render itself whenever you change that property).
#IBDesignable
class GradientArcView: NSView {
/// Width of the stroke.
#IBInspectable var lineWidth: CGFloat = 3 { didSet { setNeedsDisplay(bounds) } }
/// Color of the stroke (at full alpha, at the end).
#IBInspectable var strokeColor: NSColor = .blue { didSet { setNeedsDisplay(bounds) } }
/// Where the arc should end, measured in degrees, where 0 = "3 o'clock".
#IBInspectable var endAngle: CGFloat = 0 { didSet { setNeedsDisplay(bounds) } }
/// What is the full angle of the arc, measured in degrees, e.g. 180 = half way around, 360 = all the way around, etc.
#IBInspectable var maxAngle: CGFloat = 360 { didSet { setNeedsDisplay(bounds) } }
/// What is the shape at the end of the arc.
var lineCapStyle: NSBezierPath.LineCapStyle = .square { didSet { setNeedsDisplay(bounds) } }
override func draw(_ dirtyRect: NSRect) {
super.draw(dirtyRect)
let gradations = 255
let startAngle = -endAngle + maxAngle
let center = NSPoint(x: bounds.midX, y: bounds.midY)
let radius = (min(bounds.width, bounds.height) - lineWidth) / 2
var angle = startAngle
for i in 1 ... gradations {
let percent = CGFloat(i) / CGFloat(gradations)
let endAngle = startAngle - percent * maxAngle
let path = NSBezierPath()
path.lineWidth = lineWidth
path.lineCapStyle = lineCapStyle
path.appendArc(withCenter: center, radius: radius, startAngle: angle, endAngle: endAngle, clockwise: true)
strokeColor.withAlphaComponent(percent).setStroke()
path.stroke()
angle = endAngle
}
}
}
Here is some code that worked for me. There's animations in it, but you can use the same principle to make a strokeEnd with a gradient.
A. Created a custom view 'Donut' and put this in the header:
#interface Donut : UIView
#property UIColor * fromColour;
#property UIColor * toColour;
#property UIColor * baseColour;
#property float lineWidth;
#property float duration;
-(void)layout;
-(void)animateTo:(float)percentage;
B. Then did the basic view setup and wrote these two methods:
-(void)layout{
//vars
float dimension = self.frame.size.width;
//1. layout views
//1.1 layout base track
UIBezierPath * donut = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(lineWidth/2, lineWidth/2, dimension-lineWidth, dimension-lineWidth)];
CAShapeLayer * baseTrack = [CAShapeLayer layer];
baseTrack.path = donut.CGPath;
baseTrack.lineWidth = lineWidth;
baseTrack.fillColor = [UIColor clearColor].CGColor;
baseTrack.strokeStart = 0.0f;
baseTrack.strokeEnd = 1.0f;
baseTrack.strokeColor = baseColour.CGColor;
baseTrack.lineCap = kCALineCapButt;
[self.layer addSublayer:baseTrack];
//1.2 clipView has mask applied to it
UIView * clipView = [UIView new];
clipView.frame = self.bounds;
[self addSubview:clipView];
//1.3 rotateView transforms with strokeEnd
rotateView = [UIView new];
rotateView.frame = self.bounds;
[clipView addSubview:rotateView];
//1.4 radialGradient holds an image of the colours
UIImageView * radialGradient = [UIImageView new];
radialGradient.frame = self.bounds;
[rotateView addSubview:radialGradient];
//2. create colours fromColour --> toColour and add to an array
//2.1 holds all colours between fromColour and toColour
NSMutableArray * spectrumColours = [NSMutableArray new];
//2.2 get RGB values for both colours
double fR, fG, fB; //fromRed, fromGreen etc
double tR, tG, tB; //toRed, toGreen etc
[fromColour getRed:&fR green:&fG blue:&fB alpha:nil];
[toColour getRed:&tR green:&tG blue:&tB alpha:nil];
//2.3 determine increment between fromRed and toRed etc.
int numberOfColours = 360;
double dR = (tR-fR)/(numberOfColours-1);
double dG = (tG-fG)/(numberOfColours-1);
double dB = (tB-fB)/(numberOfColours-1);
//2.4 loop through adding incrementally different colours
//this is a gradient fromColour --> toColour
for (int n = 0; n < numberOfColours; n++){
[spectrumColours addObject:[UIColor colorWithRed:(fR+n*dR) green:(fG+n*dG) blue:(fB+n*dB) alpha:1.0f]];
}
//3. create a radial image using the spectrum colours
//go through adding the next colour at an increasing angle
//3.1 setup
float radius = MIN(dimension, dimension)/2;
float angle = 2 * M_PI/numberOfColours;
UIBezierPath * bezierPath;
CGPoint center = CGPointMake(dimension/2, dimension/2);
UIGraphicsBeginImageContextWithOptions(CGSizeMake(dimension, dimension), true, 0.0);
UIRectFill(CGRectMake(0, 0, dimension, dimension));
//3.2 loop through pulling the colour and adding
for (int n = 0; n<numberOfColours; n++){
UIColor * colour = spectrumColours[n]; //colour for increment
bezierPath = [UIBezierPath bezierPathWithArcCenter:center radius:radius startAngle:n * angle endAngle:(n + 1) * angle clockwise:YES];
[bezierPath addLineToPoint:center];
[bezierPath closePath];
[colour setFill];
[colour setStroke];
[bezierPath fill];
[bezierPath stroke];
}
//3.3 create image, add to the radialGradient and end
[radialGradient setImage:UIGraphicsGetImageFromCurrentImageContext()];
UIGraphicsEndImageContext();
//4. create a dot to add to the rotating view
//this covers the connecting line between the two colours
//4.1 set up vars
float containsDots = (M_PI * dimension) /*circumference*/ / lineWidth; //number of dots in circumference
float colourIndex = roundf((numberOfColours / containsDots) * (containsDots-0.5f)); //the nearest colour for the dot
UIColor * closestColour = spectrumColours[(int)colourIndex]; //the closest colour
//4.2 create dot
UIImageView * dot = [UIImageView new];
dot.frame = CGRectMake(dimension-lineWidth, (dimension-lineWidth)/2, lineWidth, lineWidth);
dot.layer.cornerRadius = lineWidth/2;
dot.backgroundColor = closestColour;
[rotateView addSubview:dot];
//5. create the mask
mask = [CAShapeLayer layer];
mask.path = donut.CGPath;
mask.lineWidth = lineWidth;
mask.fillColor = [UIColor clearColor].CGColor;
mask.strokeStart = 0.0f;
mask.strokeEnd = 0.0f;
mask.strokeColor = [UIColor blackColor].CGColor;
mask.lineCap = kCALineCapRound;
//5.1 apply the mask and rotate all by -90 (to move to the 12 position)
clipView.layer.mask = mask;
clipView.transform = CGAffineTransformMakeRotation(DEGREES_TO_RADIANS(-90.0f));
}
-(void)animateTo:(float)percentage {
float difference = fabsf(fromPercentage - percentage);
float fixedDuration = difference * duration;
//1. animate stroke End
CABasicAnimation * strokeEndAnimation = [CABasicAnimation animationWithKeyPath:#"strokeEnd"];
strokeEndAnimation.duration = fixedDuration;
strokeEndAnimation.fromValue = #(fromPercentage);
strokeEndAnimation.toValue = #(percentage);
strokeEndAnimation.fillMode = kCAFillModeForwards;
strokeEndAnimation.removedOnCompletion = false;
strokeEndAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
[mask addAnimation:strokeEndAnimation forKey:#"strokeEndAnimation"];
//2. animate rotation of rotateView
CABasicAnimation * viewRotationAnimation = [CABasicAnimation animationWithKeyPath:#"transform.rotation.z"];
viewRotationAnimation.duration = fixedDuration;
viewRotationAnimation.fromValue = #(DEGREES_TO_RADIANS(360 * fromPercentage));
viewRotationAnimation.toValue = #(DEGREES_TO_RADIANS(360 * percentage));
viewRotationAnimation.fillMode = kCAFillModeForwards;
viewRotationAnimation.removedOnCompletion = false;
viewRotationAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
[rotateView.layer addAnimation:viewRotationAnimation forKey:#"viewRotationAnimation"];
//3. update from percentage
fromPercentage = percentage;
}
C. Create view:
Donut * donut = [Donut new];
donut.frame = CGRectMake(20, 100, 140, 140);
donut.baseColour = [[UIColor blackColor] colorWithAlphaComponent:0.2f];
donut.fromColour = [UIColor redColor];
donut.toColour = [UIColor blueColor];
donut.lineWidth = 20.0f;
donut.duration = 2.0f;
[donut layout];
[tasteView addSubview:donut];
D. Animate view:
[donut animateTo:0.5f];
E. Explanation:
The Donut view starts off by creating a base track, clipView, rotateView and a radialGradient imageView. It then calculates 360 colours between the two colours you want to use in the donut. It does so by incrementing the rgb values between the colours. Then a radial gradient image is created using those colours and added to the imageView. Because I wanted to use kCALineCapRound, I added a dot to cover up where the two colours meet. The whole thing needs to be rotated by -90 degrees to put it in the 12 O'Clock position. Then a mask is applied to the view, giving it the donut shape.
As the strokeEnd of the mask is changed, the view beneath it 'rotateView' is rotated as well. This gives the impression that the line is growing / shrinking as long as they in are in sync.
You might also need this:
#define DEGREES_TO_RADIANS(x) (M_PI * (x) / 180.0)
Since your path is a circle, what you are asking for amounts to an angular gradient, that is, a sort of pie that changes color as we sweep a radius round the pie. There is no built-in way to do that, but there's a great library that does it for you:
https://github.com/paiv/AngleGradientLayer
The trick is that you draw your angular gradient with its center at the center of your circle, and then put a mask over it so that it appears only where your circle stroke is supposed to be.
Use below Code. Tested and works in iOS10+
import UIKit
class MMTGradientArcView: UIView {
var lineWidth: CGFloat = 3 { didSet { setNeedsDisplay(bounds) } }
var startColor = UIColor.green { didSet { setNeedsDisplay(bounds) } }
var endColor = UIColor.clear { didSet { setNeedsDisplay(bounds) } }
var startAngle:CGFloat = 0 { didSet { setNeedsDisplay(bounds) } }
var endAngle:CGFloat = 360 { didSet { setNeedsDisplay(bounds) } }
override func draw(_ rect: CGRect) {
let gradations = 289 //My School Number
var startColorR:CGFloat = 0
var startColorG:CGFloat = 0
var startColorB:CGFloat = 0
var startColorA:CGFloat = 0
var endColorR:CGFloat = 0
var endColorG:CGFloat = 0
var endColorB:CGFloat = 0
var endColorA:CGFloat = 0
startColor.getRed(&startColorR, green: &startColorG, blue: &startColorB, alpha: &startColorA)
endColor.getRed(&endColorR, green: &endColorG, blue: &endColorB, alpha: &endColorA)
let startAngle:CGFloat = 0
let endAngle:CGFloat = 270
let center = CGPoint(x: bounds.midX, y: bounds.midY)
let radius = (min(bounds.width, bounds.height) - lineWidth) / 2
var angle = startAngle
for i in 1 ... gradations {
let extraAngle = (endAngle - startAngle) / CGFloat(gradations)
let currentStartAngle = angle
let currentEndAngle = currentStartAngle + extraAngle
let currentR = ((endColorR - startColorR) / CGFloat(gradations - 1)) * CGFloat(i - 1) + startColorR
let currentG = ((endColorG - startColorG) / CGFloat(gradations - 1)) * CGFloat(i - 1) + startColorG
let currentB = ((endColorB - startColorB) / CGFloat(gradations - 1)) * CGFloat(i - 1) + startColorB
let currentA = ((endColorA - startColorA) / CGFloat(gradations - 1)) * CGFloat(i - 1) + startColorA
let currentColor = UIColor.init(red: currentR, green: currentG, blue: currentB, alpha: currentA)
let path = UIBezierPath()
path.lineWidth = lineWidth
path.lineCapStyle = .round
path.addArc(withCenter: center, radius: radius, startAngle: currentStartAngle * CGFloat(Double.pi / 180.0), endAngle: currentEndAngle * CGFloat(Double.pi / 180.0), clockwise: true)
currentColor.setStroke()
path.stroke()
angle = currentEndAngle
}
}
}

how to make a gradient border of UIView?

I want to make a gradient border of view like the following picture:
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;
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)
}
}
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)
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
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)])
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.
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

Resources