How to change CAShapeLayer alpha/opacity value? - ios

I can't seem to change the opacity of my CAShapeLayer whatever I do. I tried setting the opacity:
bottomProgressBar.strokeColor = UIColor.white.cgColor
bottomProgressBar.opacity = 0.1
Didn't work. Then I tried this:
bottomProgressBar.strokeColor = UIColor(displayP3Red: 255, green: 255, blue: 255, alpha: 0.1).cgColor
In both cases the alpha value is 1.
Is there another way to do this ? Or am I setting them wrong ?
edit:
Here is my code:
This is a class inheriting from UIView:
override init(frame: CGRect) {
super.init(frame: frame)
configure()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
configure()
}
func configure() {
bottomProgressBar.strokeColor = UIColor.white.cgColor
bottomProgressBar.opacity = 0.1
bottomProgressBar.lineWidth = self.frame.height
bottomProgressBar.strokeStart = 0
bottomProgressBar.strokeEnd = 1
self.layer.addSublayer(bottomProgressBar)
topProgressBar.strokeColor = UIColor.white.cgColor
topProgressBar.lineWidth = self.frame.height
topProgressBar.strokeStart = 0
topProgressBar.strokeEnd = 1
self.layer.addSublayer(topProgressBar)
}
edit 2:
override func layoutSubviews() {
super.layoutSubviews()
let path = UIBezierPath()
path.move(to: CGPoint(x: 0, y: 0))
path.addLine(to: CGPoint(x: self.frame.width, y: 0))
bottomProgressBar.path = path.cgPath
topProgressBar.path = path.cgPath
}
edit 3:

I think what's happening is that you have no path defined for the shape to be rendered.
func configure() {
let path = UIBezierPath(rect: CGRect(x: 0, y: 0, width: self.frame.width, height: self.frame.height))
bottomProgressBar.frame = self.frame
bottomProgressBar.bounds = self.bounds
bottomProgressBar.path = path.cgPath
bottomProgressBar.strokeColor = UIColor.red.cgColor
bottomProgressBar.opacity = 0.1
// no need for the following line as dimensions are already defined
// bottomProgressBar.lineWidth = self.frame.height
// bottomProgressBar.strokeStart = 0
// bottomProgressBar.strokeEnd = 200
self.layer.addSublayer(bottomProgressBar)
// path width divided by 2 for demo
let topPath = UIBezierPath(rect: CGRect(x: 0, y: 0, width: self.frame.width/2, height: self.frame.height))
topProgressBar.frame = self.frame
topProgressBar.bounds = self.bounds
topProgressBar.path = topPath.cgPath
topProgressBar.strokeColor = UIColor.green.cgColor
self.layer.addSublayer(topProgressBar)
}
Hope this helps.

Strange that it didn't work for you because .opacity is what changed the layer's alpha for me:
let circularPath = UIBezierPath(arcCenter: view.center,
radius: (100,
startAngle: -.pi/2,
endAngle: 3 * .pi/2,
clockwise: true)
let shapeLayer = CAShapeLayer()
shapeLayer.path = circularPath.cgPath
shapeLayer.strokeColor = UIColor.red.cgColor
shapeLayer.fillColor = UIColor.clear.cgColor
shapeLayer.lineWidth = 6
shapeLayer.opacity = 0.3 // *** fades out the shape layer ***

Related

UIView diagonal border on one corner

I have a view that needs to be displayed with a slanted corner on one side. I've already done it when the view has a background color like this:
But I also need it to be displayed with a clear background. After setting its background to clear and adding a border to it this is the output:
Here is the code for the custom view that I'm using to create the diagonal corner:
class PointedView: UIImageView {
#IBInspectable var borderColor: UIColor = UIColor.clear {
didSet {
layer.borderColor = borderColor.cgColor
}
}
#IBInspectable var borderWidth: CGFloat = 0 {
didSet {
layer.borderWidth = borderWidth
}
}
#IBInspectable
/// Percentage of the slant based on the width
var slopeFactor: CGFloat = 15 {
didSet {
updatePath()
}
}
private let shapeLayer: CAShapeLayer = {
let shapeLayer = CAShapeLayer()
shapeLayer.lineWidth = 0
// with masks, the color of the shape layer doesn’t matter;
// it only uses the alpha channel; the color of the view is
// dictate by its background color
shapeLayer.fillColor = UIColor.white.cgColor
return shapeLayer
}()
override func layoutSubviews() {
super.layoutSubviews()
updatePath()
}
private func updatePath() {
let path = UIBezierPath()
// Start from x = 0 but the mid point of y of the view
path.move(to: CGPoint(x: 0, y: bounds.midY*2))
// Create the top slanting line
path.addLine(to: CGPoint(x: bounds.minX, y: bounds.minY))
// Straight line from end of slant to the end of the view
path.addLine(to: CGPoint(x: bounds.maxX, y: bounds.minY))
// Straight line to come down to the bottom, perpendicular to view
path.addLine(to: CGPoint(x: bounds.maxX, y: ((bounds.maxY*3)/4) + 20))
// Go back to the slant end position but from the bottom
path.addLine(to: CGPoint(x: (bounds.maxX*3)/4, y: bounds.maxY))
// Close path back to where you started
path.close()
shapeLayer.path = path.cgPath
layer.mask = shapeLayer
}
}
Is there any possible solution to this?
class PointedView: UIView {
override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setup()
}
func setup() {
let shapeLayer = CAShapeLayer()
shapeLayer.path = createBezierPath().cgPath
shapeLayer.strokeColor = UIColor.blue.cgColor
shapeLayer.fillColor = UIColor.blue.cgColor
shapeLayer.lineWidth = 1.0
shapeLayer.position = CGPoint(x: 10, y: 10)
self.layer.addSublayer(shapeLayer)
}
func createBezierPath() -> UIBezierPath {
let path = UIBezierPath()
// Start from x = 0 but the mid point of y of the view
path.move(to: CGPoint(x: 0, y: bounds.midY*2))
// Create the top slanting line
path.addLine(to: CGPoint(x: bounds.minX, y: bounds.minY))
// Straight line from end of slant to the end of the view
path.addLine(to: CGPoint(x: bounds.maxX, y: bounds.minY))
// Straight line to come down to the bottom, perpendicular to view
path.addLine(to: CGPoint(x: bounds.maxX, y: ((bounds.maxY*3)/4) + 20))
// Go back to the slant end position but from the bottom
path.addLine(to: CGPoint(x: (bounds.maxX*3)/4, y: bounds.maxY))
// Close path back to where you started
path.close() // draws the final line to close the path
return path
}
}
Managed to solve it by drawing another CAShapeLayer() following the same path as the original shape.
let borderLayer = CAShapeLayer()
borderLayer.path = path.cgPath
borderLayer.lineWidth = 2
borderLayer.strokeColor = borderColor.cgColor
borderLayer.fillColor = UIColor.clear.cgColor
borderLayer.frame = bounds
layer.addSublayer(borderLayer)
if you are using StoryBoard with #IBInspectable you can try like thisenter image description here
Please use this method
func roundCorners(corners: UIRectCorner = .allCorners, radius: CGFloat = 0.0, borderColor: UIColor = .clear, borderWidth: CGFloat = 0.0, clipToBonds: Bool = true) {
clipsToBounds = clipToBonds
layer.cornerRadius = radius
layer.borderWidth = borderWidth
layer.borderColor = borderColor.cgColor
if corners.contains(.allCorners){
layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner, .layerMinXMaxYCorner, .layerMaxXMaxYCorner]
return
}
var maskedCorners = CACornerMask()
if corners.contains(.topLeft) { maskedCorners.insert(.layerMinXMinYCorner) }
if corners.contains(.topRight) { maskedCorners.insert(.layerMaxXMinYCorner) }
if corners.contains(.bottomLeft) { maskedCorners.insert(.layerMinXMaxYCorner) }
if corners.contains(.bottomRight) { maskedCorners.insert(.layerMaxXMaxYCorner) }
layer.maskedCorners = maskedCorners
}
and by using your UIVIEW
View.roundCorners(corners: [.topLeft, .bottomLeft], radius: 20, borderColor: .clear, borderWidth: 0, clipToBonds: true)

Add Colour Gradient to Circular Progress Bar iOS

I am having difficulty adding a colour gradient to my circular progress bar
My setup:
Storyboard: ViewController with an empty view designated as the Class CircularProgressBar
Custom class for the CircularProgressBar
Steps Taken:
I am able to load circular progress bar
In earlier attempts I created a gradient shape layer & set it to be masked by the bounds of the BarLayer, however the gradient shape layer draw a rectangle starting from the centre of my circle & extend to the bottom right so only that portion of the BarLayer would have a gradient
When I moved the origin of the gradient layer rectangle it shifted the Barlayer off the TrackLayer with it
How do I:
Have the gradient layer cover the entire circular progress bar & mask it to the BarLayer
class CircularProgressBar: UIView {
let shapeLayer = CAShapeLayer()
let trackLayer = CAShapeLayer()
override init(frame: CGRect) {
super.init(frame: frame)
addProgressBar(radius: 5, progress: 0)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
addProgressBar(radius: 5, progress: 0)
}
func addProgressBar(radius: CGFloat, progress: CGFloat) {
let lineWidth = radius*0.080
let circularPath = UIBezierPath(arcCenter: CGPoint(x: bounds.midX, y: bounds.midY), radius: radius, startAngle: 0, endAngle: CGFloat.pi*2, clockwise: true)
//TrackLayer
trackLayer.path = circularPath.cgPath
trackLayer.fillColor = UIColor.lightGray.cgColor
trackLayer.strokeColor = UIColor.clear.cgColor
trackLayer.opacity = 0.5
trackLayer.lineWidth = lineWidth
trackLayer.lineCap = CAShapeLayerLineCap.round
//BarLayer
shapeLayer.path = circularPath.cgPath
shapeLayer.fillColor = UIColor.clear.cgColor
shapeLayer.strokeColor = UIColor.systemGreen.cgColor
shapeLayer.lineWidth = lineWidth
shapeLayer.strokeEnd = 0
shapeLayer.lineCap = CAShapeLayerLineCap.round
//Rotate Shape Layer
shapeLayer.transform = CATransform3DMakeRotation(-CGFloat.pi/2, 0, 0, 1)
//Shape Shadow
shapeLayer.shadowColor = UIColor.black.cgColor
shapeLayer.shadowOffset = CGSize(width: -7, height: 7)
shapeLayer.shadowRadius = 1
shapeLayer.shadowOpacity = 0.5
//Animation
loadProgress(percentage: progress)
//LoadLayers
layer.addSublayer(trackLayer)
layer.addSublayer(shapeLayer)
}
func loadProgress(percentage: CGFloat) {
let basicAnimation = CABasicAnimation(keyPath: "strokeEnd")
basicAnimation.fromValue = 0
basicAnimation.duration = 2
basicAnimation.fillMode = CAMediaTimingFillMode.forwards
basicAnimation.isRemovedOnCompletion = false
shapeLayer.strokeEnd = percentage
shapeLayer.add(basicAnimation, forKey: "basicStroke")
}
ViewController
class HomeViewController: UIViewController {
#IBOutlet weak var containerView: UIView!
var circularProgressBar = CircularProgressBar()
var radius: CGFloat!
var progress: CGFloat!
var answeredCorrect = 0
var totalQuestions = 0
override func viewDidLoad() {
super.viewDidLoad()
answeredCorrect = 50
totalQuestions = 100
//Configure Progress Bar
radius = (containerView.frame.height)/2.60
progress = CGFloat(answeredCorrect) / CGFloat (totalQuestions)
circularProgressBar.addProgressBar(radius: radius, progress: progress)
circularProgressBar.center = containerView.center
//Adding view
containerView.addSubview(circularProgressBar)
}
}
If you are looking for something like this
You need to update your code
import UIKit
#IBDesignable
class CircularProgressBar: UIView {
let shapeLayer = CAShapeLayer()
let trackLayer = CAShapeLayer()
override init(frame: CGRect) {
super.init(frame: frame)
// addProgressBar(radius: 50, progress: 50)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
// addProgressBar(radius: 50, progress: 50)
}
override func layoutSubviews() {
addProgressBar(radius: 50, progress: 50)
}
func addProgressBar(radius: CGFloat, progress: CGFloat) {
let lineWidth = CGFloat(10.0)//radius*0.080
let circularPath = UIBezierPath(arcCenter: CGPoint(x: bounds.midX, y: bounds.midY), radius: radius, startAngle: 0, endAngle: CGFloat.pi*2, clockwise: true)
//TrackLayer
trackLayer.path = circularPath.cgPath
trackLayer.fillColor = UIColor.lightGray.cgColor
trackLayer.strokeColor = UIColor.clear.cgColor
trackLayer.lineWidth = lineWidth
trackLayer.lineCap = CAShapeLayerLineCap.round
//BarLayer
shapeLayer.path = circularPath.cgPath
shapeLayer.fillColor = UIColor.black.cgColor
shapeLayer.strokeColor = UIColor.systemGreen.cgColor
shapeLayer.lineWidth = lineWidth*2
//shapeLayer.strokeEnd = 0
shapeLayer.lineCap = CAShapeLayerLineCap.round
//Rotate Shape Layer
// shapeLayer.transform = CATransform3DMakeRotation(-CGFloat.pi/2, 0, 0, 1)
//Animation
// loadProgress(percentage: progress)
//LoadLayers
layer.addSublayer(trackLayer)
self.addGradient()
// layer.addSublayer(shapeLayer)
}
private func addGradient() {
let gradient = CAGradientLayer()
gradient.colors = [UIColor.red.cgColor,UIColor.purple.cgColor,UIColor.systemPink.cgColor,UIColor.blue.cgColor]
gradient.frame = bounds
gradient.mask = shapeLayer
layer.addSublayer(gradient)
}
}
And for Uniform gradient you can replace addGradient method
private func addGradient() {
let gradient = CAGradientLayer()
gradient.colors = [UIColor.red.cgColor,UIColor.cyan.cgColor,UIColor.brown.cgColor,UIColor.blue.cgColor]
gradient.frame = bounds
gradient.locations = [0.2,0.5,0.75,1]
gradient.startPoint = CGPoint(x: 0.5, y: 0.5)
gradient.endPoint = CGPoint(x: 0.5, y: 0)
gradient.type = .conic
gradient.mask = shapeLayer
layer.addSublayer(gradient)
}

circle with dash lines uiview

I am trying to make a circle with dash lines. I am able to make lines in rectangle but I don't know how to make these in circle. Here is answer I got but it's in Objective-C: UIView Draw Circle with Dotted Line Border
Here is my code which makes a rectangle with dashed lines.
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)
}
Certainly you can just render your circular UIBezierPath with the selected dash pattern:
class DashedCircleView: UIView {
private var shapeLayer: CAShapeLayer = {
let shapeLayer = CAShapeLayer()
shapeLayer.strokeColor = UIColor.red.cgColor
shapeLayer.fillColor = UIColor.clear.cgColor
shapeLayer.lineWidth = 10
shapeLayer.lineCap = .round
shapeLayer.lineDashPattern = [20, 60]
return shapeLayer
}()
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()
updatePath()
}
}
private extension DashedCircleView {
func configure() {
layer.addSublayer(shapeLayer)
}
func updatePath() {
let rect = bounds.insetBy(dx: shapeLayer.lineWidth / 2, dy: shapeLayer.lineWidth / 2)
let radius = min(rect.width, rect.height) / 2
let center = CGPoint(x: rect.midX, y: rect.midY)
let path = UIBezierPath(arcCenter: center, radius: radius, startAngle: 0, endAngle: .pi * 2, clockwise: true)
shapeLayer.path = path.cgPath
}
}
That yields:
The problem with that approach is that it’s hard to get the dashed pattern to line up (notice the awkward dashing at the “3 o’clock” position). You can fix that by making sure that the two values of lineDashPattern add up to some number that evenly divides into the circumference of the circle (e.g. 2π × radius):
let circumference: CGFloat = 2 * .pi * radius
let count = 30
let relativeDashLength: CGFloat = 0.25
let dashLength = circumference / CGFloat(count)
shapeLayer.lineDashPattern = [dashLength * relativeDashLength, dashLength * (1 - relativeDashLength)] as [NSNumber]
Alternatively, rather than using lineDashPattern at all, you can instead keep a solid stroke and make the path, itself, as a series of small arcs. That way I can achieve the desired dashed effect, but ensuring it’s evenly split into count little arcs as we rotate from 0 to 2π:
class DashedCircleView: UIView {
private var shapeLayer: CAShapeLayer = {
let shapeLayer = CAShapeLayer()
shapeLayer.strokeColor = UIColor.red.cgColor
shapeLayer.fillColor = UIColor.clear.cgColor
shapeLayer.lineWidth = 10
shapeLayer.lineCap = .round
return shapeLayer
}()
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()
updatePath()
}
}
private extension DashedCircleView {
func configure() {
layer.addSublayer(shapeLayer)
}
func updatePath() {
let rect = bounds.insetBy(dx: shapeLayer.lineWidth / 2, dy: shapeLayer.lineWidth / 2)
let radius = min(rect.width, rect.height) / 2
let center = CGPoint(x: rect.midX, y: rect.midY)
let path = UIBezierPath()
let count = 30
let relativeDashLength: CGFloat = 0.25 // a value between 0 and 1
let increment: CGFloat = .pi * 2 / CGFloat(count)
for i in 0 ..< count {
let startAngle = increment * CGFloat(i)
let endAngle = startAngle + relativeDashLength * increment
path.move(to: CGPoint(x: center.x + radius * cos(startAngle),
y: center.y + radius * sin(startAngle)))
path.addArc(withCenter: center, radius: radius, startAngle: startAngle, endAngle: endAngle, clockwise: true)
}
shapeLayer.path = path.cgPath
}
}
That yields:
You can use UIBezierPath(ovalIn:) to create a circle path in a square view.
extension UIView {
func addDashedCircle() {
let circleLayer = CAShapeLayer()
circleLayer.path = UIBezierPath(ovalIn: bounds).cgPath
circleLayer.lineWidth = 2.0
circleLayer.strokeColor = UIColor.red.cgColor//border of circle
circleLayer.fillColor = UIColor.white.cgColor//inside the circle
circleLayer.lineJoin = .round
circleLayer.lineDashPattern = [6,3]
layer.addSublayer(circleLayer)
}
}
Set view background color .clear and fill color of the layer .white
class View1: UIViewController {
#IBOutlet weak var circleView: UIView!
override func viewDidLoad() {
super.viewDidLoad()
circleView.backgroundColor = .clear//outside the circle
circleView.addDashedCircle()
}
}
Or using UIBezierPath(arcCenter:radius:startAngle:endAngle:clockwise:)
circleLayer.path = UIBezierPath(arcCenter: CGPoint(x: frame.size.width/2, y: frame.size.height/2),
radius: min(frame.size.height,frame.size.width)/2,
startAngle: 0,
endAngle: .pi * 2,
clockwise: true).cgPath
Draw the path using the circle path variant of UIBezierPath
shapeLayer.path = UIBezierPath(arcCenter: CGPoint(x: frame.size.width * 0.5, y: frame.size.height * 0.5), radius: frame.size.width * 0.5, startAngle: 0, endAngle: .pi * 2, clockwise: true)

Create CAGradient Layer with transparent hole in it?

Currently I do it like this:
final class BorderedButton: BottomNavigationButton {
private let gradient = CAGradientLayer()
private let shapeLayer = CAShapeLayer()
override func layoutSubviews() {
super.layoutSubviews()
let radius = bounds.size.height / 2
let outside = UIBezierPath(roundedRect: CGRect(x: 0.0, y: 0.0, width: bounds.width, height: bounds.height), cornerRadius: radius)
let inside = UIBezierPath(roundedRect: CGRect(x: 3.0, y: 3.0, width: bounds.width - 6, height: bounds.height - 6), cornerRadius: radius - 3)
outside.append(inside)
outside.usesEvenOddFillRule = true
shapeLayer.path = outside.cgPath
}
init(color: UIColor?) {
super.init(frame: .zero)
let isGradient = color == nil
shapeLayer.fillRule = kCAFillRuleEvenOdd
shapeLayer.fillColor = UIColor.red.cgColor
layer.insertSublayer(shapeLayer, at: 0)
//gradient part
gradient.colors = isGradient ? [Constants.gradientStart, Constants.gradientEnd] : [color!.cgColor, color!.cgColor]
gradient.startPoint = CGPoint(x: 0.2, y: 0.5)
gradient.endPoint = CGPoint(x: 1, y: 1)
}
}
How can I apply that gradient to my code?
Don't add the CAShapeLayer to the view's layer, rather set it as the mask of the CAGradientLayer. Also don't forget to set the bounds of the gradient layer.
I had to make some modifications to get it to run in a playground, but this works for me:
let gradientStart = UIColor.orange
let gradientEnd = UIColor.blue
final class BorderedButton: UIButton {
private let gradient = CAGradientLayer()
private let shapeLayer = CAShapeLayer()
override func layoutSubviews() {
super.layoutSubviews()
let radius = bounds.size.height / 2
let outside = UIBezierPath(roundedRect: CGRect(x: 0.0, y: 0.0, width: bounds.width, height: bounds.height), cornerRadius: radius)
let inside = UIBezierPath(roundedRect: CGRect(x: 3.0, y: 3.0, width: bounds.width - 6, height: bounds.height - 6), cornerRadius: radius - 3)
outside.append(inside)
outside.usesEvenOddFillRule = true
shapeLayer.path = outside.cgPath
gradient.frame = CGRect(x: 0, y: 0, width: bounds.size.width, height: bounds.size.height)
}
init(color: UIColor?, frame: CGRect = .zero) {
super.init(frame: frame)
let isGradient = color == nil
shapeLayer.fillRule = kCAFillRuleEvenOdd
//gradient part
gradient.colors = isGradient ? [gradientStart.cgColor, gradientEnd.cgColor] : [color!.cgColor, color!.cgColor]
gradient.startPoint = CGPoint(x: 0.2, y: 0.5)
gradient.endPoint = CGPoint(x: 1, y: 1)
gradient.mask = shapeLayer
layer.addSublayer(gradient)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}

How do i call functions of a class - Swift

Im still trying to learn this portion of things. I've looked around and read a few questions about it but truthfully I dont understand any of it.
I've got a circle class that creates and draws a circle
class CircleView: UIView {
var circleLayer: CAShapeLayer!
var isAnimating = false
override init(frame: CGRect) {
let fColor = UIColor.clear.cgColor
super.init(frame: frame)
self.backgroundColor = UIColor.clear
// Use UIBezierPath as an easy way to create the CGPath for the layer.
// The path should be the entire circle.
let circlePath = UIBezierPath(arcCenter: CGPoint(x: frame.size.width / 2.0, y: frame.size.height / 2.0), radius: (frame.size.width - 10)/2, startAngle: 0.0, endAngle: CGFloat(M_PI * 2.0), clockwise: true)
// Setup the CAShapeLayer with the path, colors, and line width
circleLayer = CAShapeLayer()
circleLayer.path = circlePath.cgPath
circleLayer.fillColor = UIColor.clear.cgColor
circleLayer.strokeColor = UIColor.init(rgbColorCodeRed: 230, green: 226, blue: 218, alpha: 1).cgColor
circleLayer.lineWidth = 9.0;
// Don't draw the circle initially
circleLayer.strokeEnd = 0.0
// Add the circleLayer to the view's layer's sublayers
layer.addSublayer(circleLayer)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func setCircleClockwise(){
let circlePath = UIBezierPath(arcCenter: CGPoint(x: frame.size.width / 2.0, y: frame.size.height / 2.0), radius: (frame.size.width - 10)/2, startAngle: 0.0, endAngle: CGFloat(M_PI * 2.0), clockwise: true)
self.circleLayer.removeFromSuperlayer()
self.circleLayer = formatCirle(circlePath: circlePath)
self.layer.addSublayer(self.circleLayer)
}
func setCircleCounterClockwise(){
let circlePath = UIBezierPath(arcCenter: CGPoint(x: frame.size.width / 2.0, y: frame.size.height / 2.0), radius: (frame.size.width - 10)/2, startAngle: 0.0, endAngle: CGFloat(M_PI * 2.0), clockwise: false)
self.circleLayer.removeFromSuperlayer()
self.circleLayer = formatCirle(circlePath: circlePath)
self.layer.addSublayer(self.circleLayer)
}
func formatCirle(circlePath: UIBezierPath) -> CAShapeLayer{
let circleShape = CAShapeLayer()
circleShape.path = circlePath.cgPath
circleShape.fillColor = UIColor.clear.cgColor
circleShape.strokeColor = UIColor.init(rgbColorCodeRed: 230, green: 226, blue: 218, alpha: 1).cgColor
circleShape.lineWidth = 9.0;
circleShape.strokeEnd = 0.0
return circleShape
}
func animate(duration: TimeInterval){
self.isAnimating = true
self.animateCircleFull(duration: 1)
}
func endAnimate(){
self.isAnimating = false
}
func animateCircleFull(duration: TimeInterval) {
if self.isAnimating{
CATransaction.begin()
let animation = CABasicAnimation(keyPath: "strokeEnd")
animation.duration = duration
animation.fromValue = 0
animation.toValue = 1
animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
circleLayer.strokeEnd = 1.0
CATransaction.setCompletionBlock {
self.setCircleCounterClockwise()
self.animateCircleEmpty(duration: duration)
}
// Do the actual animation
circleLayer.add(animation, forKey: "animateCircle")
CATransaction.commit()
}
}
func animateCircleEmpty(duration: TimeInterval){
if self.isAnimating{
CATransaction.begin()
let animation = CABasicAnimation(keyPath: "strokeEnd")
animation.duration = duration
animation.fromValue = 1
animation.toValue = 0
animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
circleLayer.strokeEnd = 0
CATransaction.setCompletionBlock {
self.setCircleClockwise()
self.animateCircleFull(duration: duration)
}
// Do the actual animation
circleLayer.add(animation, forKey: "animateCircle")
CATransaction.commit()
}
}
Which is being called from my viewController with the below function. It all works fine but what i cant work out is how do i go about calling the endAnimation function on the same circle?
func addCircleView() {
let diceRoll = CGFloat(Int(arc4random_uniform(7))*50)
var circleWidth = CGFloat(100)
var circleHeight = circleWidth
var bgColor: UIColor = UIColor.init(rgbColorCodeRed: 230, green: 226, blue: 218, alpha: 1)
// Create a new CircleView
let circleView = CircleView(frame: CGRect(x: self.view.frame.width/2, y: self.view.frame.height-110, width: circleWidth, height: circleHeight))
//let test = CircleView(frame: CGRect(x: diceRoll, y: 0, width: circleWidth, height: circleHeight))
cv = circleView
view.addSubview(circleView)
// Animate the drawing of the circle over the course of 1 second
circleView.animate(duration: 1)
let imageName = "ButtonBackground.png"
let image = UIImage(named: imageName)
let imageView = UIImageView(image: image!)
imageView.frame = CGRect(x: self.view.frame.width/2, y: self.view.frame.height-110, width: 100, height: 100)
view.addSubview(imageView)
view.bringSubview(toFront: circleView)
}
For calling endAnimation function on the same circle you have to declare property in your ViewController class.
import UIKit
class ViewController: UIViewController {
var circleView = CircleView()
override func viewDidLoad() {
super.viewDidLoad()
self.addCircleView()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func addCircleView() {
let diceRoll = CGFloat(Int(arc4random_uniform(7))*50)
var circleWidth = CGFloat(100)
var circleHeight = circleWidth
var bgColor: UIColor = UIColor.init(rgbColorCodeRed: 230, green: 226, blue: 218, alpha: 1)
// Create a new CircleView
self.circleView = CircleView(frame: CGRect(x: self.view.frame.width/2, y: self.view.frame.height-110, width: circleWidth, height: circleHeight))
//let test = CircleView(frame: CGRect(x: diceRoll, y: 0, width: circleWidth, height: circleHeight))
cv = circleView
view.addSubview(circleView)
// Animate the drawing of the circle over the course of 1 second
circleView.animate(duration: 1)
let imageName = "ButtonBackground.png"
let image = UIImage(named: imageName)
let imageView = UIImageView(image: image!)
imageView.frame = CGRect(x: self.view.frame.width/2, y: self.view.frame.height-110, width: 100, height: 100)
view.addSubview(imageView)
view.bringSubview(toFront: circleView)
//now you are able to call endAnimation function
self.circleView.endAnimate()
}
}

Resources