I want to draw semi circle with gradient, but when i add gradient the semi circle does not look right
what i have done
let center = CGPoint(x: circleView.bounds.size.width / 2, y: circleView.bounds.size.height / 2)
let circleRadius = circleView.bounds.size.width / 2
let circlePath = UIBezierPath(arcCenter: center, radius: circleRadius, startAngle: CGFloat.pi, endAngle: CGFloat.pi * 2, clockwise: true)
let semiCircleLayer = CAShapeLayer()
semiCircleLayer.path = circlePath.cgPath
semiCircleLayer.lineCap = .round
semiCircleLayer.strokeColor = UIColor.white.cgColor
semiCircleLayer.fillColor = UIColor.clear.cgColor
semiCircleLayer.lineWidth = 6
semiCircleLayer.strokeStart = 0
semiCircleLayer.strokeEnd = 1
let gradient = CAGradientLayer()
gradient.colors = [UIColor.red.cgColor, UIColor.yellow.cgColor, UIColor.green.cgColor]
gradient.startPoint = CGPoint(x: 0.0, y: 0.5)
gradient.endPoint = CGPoint(x: 1.0, y: 0.5)
gradient.frame = circleView.bounds
gradient.mask = semiCircleLayer
circleView.layer.addSublayer(gradient)
Gradient Semi Circle:
Without Gradient Semi Circle:
Expected Result:
its because you're using full frame of initial circleView and lineWidth > 1
try to change this line
let circleRadius = circleView.bounds.size.width / 2 - 4
Related
need some help adding a drop shadow to a line created with a CAShapeLayer / UIBezierPath
This code snippet produces the following line shape as in the screenshot below
let bounds = UIScreen.main.bounds
let shapeLayer = CAShapeLayer()
shapeLayer.lineWidth = 10.0
shapeLayer.frame = CGRect(x: 0, y: 0 , width: bounds.width, height: bounds.width)
shapeLayer.lineWidth = 5.0
shapeLayer.strokeColor = UIColor.red.cgColor
let offset : CGFloat = 20
let arcCenter = shapeLayer.position
let radius = shapeLayer.bounds.size.width / 2.0 - offset
let startAngle = CGFloat(0.0)
let endAngle = CGFloat(1.0 * .pi)
let circlePath = UIBezierPath(arcCenter: arcCenter, radius: radius, startAngle: startAngle, endAngle: endAngle, clockwise: true)
shapeLayer.shadowOffset = CGSize(width: 0, height: 0)
shapeLayer.shadowColor = UIColor.red.cgColor
shapeLayer.shadowOpacity = 1.0
shapeLayer.shadowRadius = 25
shapeLayer.path = circlePath.cgPath
As you can see the shadow surrounds the outer part of the semi-circle shape.
Could anyone give me any hints on adding a drop shadow around the line only ?
Output:
UIView Extension:
extension UIView {
func renderCircle() {
let semiCircleLayer = CAShapeLayer()
let center = CGPoint (x: self.frame.size.width / 2, y: self.frame.size.height / 2)
let circleRadius = self.frame.size.width / 2
let circlePath = UIBezierPath(arcCenter: center, radius: circleRadius, startAngle: CGFloat(Double.pi * 2), endAngle: CGFloat(Double.pi), clockwise: true)
semiCircleLayer.path = circlePath.cgPath
semiCircleLayer.strokeColor = UIColor.red.cgColor
semiCircleLayer.fillColor = UIColor.clear.cgColor
semiCircleLayer.lineWidth = 8
semiCircleLayer.shadowColor = UIColor.red.cgColor
semiCircleLayer.shadowRadius = 25.0
semiCircleLayer.shadowOpacity = 1.0
semiCircleLayer.shadowPath = circlePath.cgPath.copy(strokingWithWidth: 25, lineCap: .round, lineJoin: .miter, miterLimit: 0)
semiCircleLayer.shadowOffset = CGSize(width: 1.0, height: 1.0)
self.layer.addSublayer(semiCircleLayer)
}
}
Usage:
semiCircleView.renderCircle()
I have a task to draw the line with a circular gradient (colour should change by the circle) and then add animation. Now I draw 360 layers with a certain interval and different colours.
var colours: [UIColor] = [UIColor]()
var startAngle = CGFloat(-0.5 * Double.pi)
var index = 0
func drawLayers() {
let smallAngle = (1.5 * CGFloat.pi - (-0.5 * CGFloat.pi)) / 360
if index < colours.count { //colours.count = 360
let endAngle = startAngle + smallAngle
let circlePath = UIBezierPath(arcCenter: .init(x: 100, y: 100), radius: 100, startAngle: startAngle, endAngle: endAngle, clockwise: true)
startAngle = endAngle
let shapeLayer = CAShapeLayer()
shapeLayer.path = circlePath.cgPath
shapeLayer.fillColor = UIColor.clear.cgColor
shapeLayer.strokeColor = colours[index].cgColor
shapeLayer.lineWidth = 8
view.layer.addSublayer(shapeLayer)
index += 1
Timer.scheduledTimer(
withTimeInterval: 0.004,
repeats: false) { (_) in
self.drawLayers()
}
}
}
Something like that but with linear animation
Can anyone tell me how to do it right?
iOS has circular (conic) gradients built in now. So I would just ask for the gradient, once, and then animate a single path used as a mask. That’s just two layers, much less work, true animation, and a true gradient.
Example:
Here's my test code; change the colors and numbers as desired:
let grad = CAGradientLayer()
grad.type = .conic
grad.colors = [UIColor.red.cgColor, UIColor.green.cgColor, UIColor.red.cgColor]
grad.startPoint = CGPoint(x: 0.5, y: 0.5)
grad.frame = CGRect(x: 100, y: 100, width: 200, height: 200)
self.view.layer.addSublayer(grad)
let c = CAShapeLayer()
let p = UIBezierPath(ovalIn: CGRect(x: 20, y: 20, width: 160, height: 160))
c.path = p.cgPath
c.fillColor = UIColor.clear.cgColor
c.strokeColor = UIColor.black.cgColor
c.lineWidth = 8
grad.mask = c
c.strokeEnd = 0
To make the animation happen, just say:
c.strokeEnd = 1
I've written following code to draw a rect with a whole in it:
fileprivate let strokeWidth: CGFloat = 5
let shapeLayer = CAShapeLayer()
shapeLayer.strokeColor = UIColor.white.cgColor
shapeLayer.lineWidth = strokeWidth
shapeLayer.fillColor = UIColor(white: 0.5, alpha: 0.9).cgColor
let p = UIBezierPath(rect: bounds.insetBy(dx: -strokeWidth, dy: -strokeWidth))
let radius = bounds.size.width / 3
p.move(to: CGPoint(x: bounds.midX + radius, y: bounds.midY))
p.addArc(withCenter: CGPoint(x: bounds.midX, y: bounds.midY), radius: radius, startAngle: 0, endAngle: CGFloat(2 * Double.pi), clockwise: false)
p.close()
shapeLayer.path = p.cgPath
layer.addSublayer(shapeLayer)
The problem here is this line:
p.addArc(withCenter: CGPoint(x: bounds.midX, y: bounds.midY), radius: radius, startAngle: 0, endAngle: CGFloat(2 * Double.pi), clockwise: false)
Printing out the description of the path:
UIBezierPath: 0x6000000b35c0; MoveTo {-5, -5}, LineTo {380, -5}, LineTo {380, 672}, LineTo {-5, 672}, Close, MoveTo {312.5, 333.5}, LineTo {312.5, 333.49999999999994}, Close
As you can see, the last two entries are lineTo and close, which gives me not the expected result (a full circle), I'll get nothing because the line is too short between 333.5 and 333.4999999.
This problem occurs since switching to Swift 3, in Objective-C this wasn't a problem.
Changing the end angle to 1.9 * Double.pi will also work, no idea why. But the full circle should have 2 * Double.pi.
Any idea or is it a Swift 3 bug?
Try like this:
let bounds = UIScreen.main.bounds
let strokeWidth: CGFloat = 5
let shapeLayer = CAShapeLayer()
shapeLayer.strokeColor = UIColor.white.cgColor
shapeLayer.lineWidth = strokeWidth
shapeLayer.fillColor = UIColor(white: 0.5, alpha: 0.9).cgColor
let p = UIBezierPath(rect: bounds.insetBy(dx: -strokeWidth, dy: -strokeWidth))
let radius = bounds.size.width / 3
p.move(to: CGPoint(x: bounds.midX + radius, y: bounds.midY))
p.addArc(withCenter: CGPoint(x: bounds.midX, y: bounds.midY), radius: radius, startAngle: 2 * .pi, endAngle: 0, clockwise: false)
p.close()
shapeLayer.fillColor = UIColor.red.cgColor
shapeLayer.strokeColor = UIColor.black.cgColor
shapeLayer.lineWidth = 5
shapeLayer.path = p.cgPath
let view = UIView(frame: bounds)
view.backgroundColor = .yellow
view.layer.addSublayer(shapeLayer)
view
How to apply gradient color to UIImageView border color. What i tried is
let gradient: CAGradientLayer = CAGradientLayer()
let color0 = UIColor(red:0.0/255, green:0.0/255, blue:0.0/255, alpha:0.0).CGColor
let color1 = UIColor(red:0.0/255, green:0.0/255, blue: 0.0/255, alpha:0.71).CGColor
gradient.colors = [color0, color1]
gradient.locations = [0.0 , 1.0]
gradient.frame = CGRect(x: 0.0, y: 80.0, width: self.imgProfile.frame.size.width, height: self.imgProfile.frame.size.height-35)
self.imgProfile.layer.insertSublayer(gradient, atIndex: 0)
This is what required
Please guide, thanks
Update:
Tried this way
let gradient = CAGradientLayer()
gradient.frame = CGRect(origin: CGPointZero, size: self.imgPeopleProfile.frame.size)
gradient.colors = [UIColor.blueColor().CGColor, UIColor.greenColor().CGColor]
let shape = CAShapeLayer()
shape.lineWidth = 2
shape.path = UIBezierPath(rect: self.imgPeopleProfile.bounds).CGPath
shape.strokeColor = UIColor.blackColor().CGColor
shape.fillColor = UIColor.clearColor().CGColor
gradient.mask = shape
imgPeopleProfile.layer.cornerRadius = imgPeopleProfile.frame.size.width / 2
imgPeopleProfile.clipsToBounds = true
self.imgPeopleProfile.layer.addSublayer(gradient)
It gives
Please take Imageview Size Equal width and height ex. Width=100 and Height = 100 So it will show proper Circle. If you want to change the Size then please amke also change in the below Line .
shape.path = UIBezierPath(arcCenter: CGPoint(x: 50, y: 50), radius: CGFloat(50), startAngle: CGFloat(0), endAngle:CGFloat(M_PI * 2), clockwise: true).CGPath
With the radius of the Circle in above example its 50.
let gradient = CAGradientLayer()
gradient.frame = CGRect(origin: CGPointZero, size: self.imageView.frame.size)
gradient.colors = [UIColor(red:87.0/255, green:206.0/255, blue: 172.0/255, alpha:0.71).CGColor,UIColor(red:44.0/255, green:192.0/255, blue:208.0/255, alpha:1.0).CGColor]
imageView.layer.cornerRadius = imageView.frame.size.width / 2
imageView.clipsToBounds = true
let shape = CAShapeLayer()
shape.lineWidth = 5
shape.path = UIBezierPath(arcCenter: CGPoint(x: 50, y: 50), radius: CGFloat(50), startAngle: CGFloat(0), endAngle:CGFloat(M_PI * 2), clockwise: true).CGPath
shape.strokeColor = UIColor.blackColor().CGColor
shape.fillColor = UIColor.clearColor().CGColor
gradient.mask = shape
self.imageView.layer.addSublayer(gradient)
Is there a way to add rounded corners to a CAShapeLayer? In my case I needed the shape layer to create a dashed border via lineDashPattern.
^ notice how the dashed line is not rounded
The answer is simple. Create a bézier path with rounded corners.
UPDATE for Swift
view.clipsToBounds = true
view.layer.cornerRadius = 10.0
let border = CAShapeLayer()
border.path = UIBezierPath(roundedRect:view.bounds, cornerRadius:10.0).cgPath
border.frame = view.bounds
border.fillColor = nil
border.strokeColor = UIColor.purple.cgColor
border.lineWidth = borderWidth * 2.0 // doubled since half will be clipped
border.lineDashPattern = [15.0]
view.layer.addSublayer(border)
Objective-C
// (This old code assumes this is within a view with a custom property "border".)
self.clipsToBounds = YES;
self.layer.cornerRadius = 10.0;
self.border = [CAShapeLayer layer];
self.border.fillColor = nil;
self.border.path = [UIBezierPath bezierPathWithRoundedRect:self.bounds cornerRadius:10.0].cgPath;
self.border.frame = self.bounds;
self.border.strokeColor = [UIColor purpleColor].CGColor;
self.border.lineWidth = borderWidth * 2; // double desired width as half will be clipped
self.border.lineDashPattern = #[#15];
[self.layer addSublayer:self.border];
In swift 4 I created a UIView category (UIView+Borders) 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;
}