title: How to provide maintain spacing between different CALayers
body: <h2>Heading ##I'm trying to learn the charts and having trouble</h2>
  
  <ol>
  <li>Adding consistent space between the slices.</li>
  <li>Start the animation in sequence.</li>
  </ol>
  
  <p>The reason, I didn't want the separator as a separate arch is to have both the edges <strong>rounded</strong>. Adding a separator as another layer overlaps the rounded corners.<a href="https://i.stack.imgur.com/r2rcV.png" rel="nofollow noreferrer"><img src="https://i.stack.imgur.com/r2rcV.png" alt="enter image description here"></a></p>
  
  <p>Any help or pointers is highly appreciated.</p>
  
  <pre><code>import UIKit
  
  import PlaygroundSupport
  
  
  var str = "Hello, playground"
  struct dataItem {
      var color: UIColor
      var percentage: CGFloat
  }
  
  typealias pieAngle = (start: CGFloat, end: CGFloat, color: UIColor)
  
  let pieDataToDisplay = [dataItem(color: .red, percentage: 10),
              dataItem(color: .blue, percentage: 20),
              dataItem(color: .green, percentage: 25),
              dataItem(color: .yellow, percentage: 25),
              dataItem(color: .orange, percentage: 10)]
  
  class USBCircleChart: UIView {
  
      private var piesToDisplay: [dataItem] = [] { didSet { setNeedsLayout() } }
  
      private var seperatorSpace: Double = 2.0 { didSet { setNeedsLayout() } }
  
          func fillDataForChart(with items: [dataItem] )  {
              self.piesToDisplay.append(contentsOf: items)
              print("getting data \(self.piesToDisplay)")
              layoutIfNeeded()
          }
  
          override func layoutSubviews() {
              super.layoutSubviews()
  
              guard piesToDisplay.count &gt; 0 else { return  }
  
              print("laying out data")
  
              let angles = calcualteStartAndEndAngle(items: piesToDisplay)
  
              for i in angles {
                  var dataItem = i
                  addSpace(data: &amp;dataItem)
                  addShapeToCircle(data: dataItem)
              }
  
          }
  
          func addSpace(data:inout pieAngle) -&gt; pieAngle {
              // If space is not added, then its collated at the end, we have to scatter it between each item.
              //data.end -= CGFloat(seperatorSpace)
              return data
          }
  
           func addShapeToCircle(data : pieAngle, percent: CGFloat) {
              let center = CGPoint(x: bounds.origin.x + bounds.size.width / 2, y: bounds.origin.y + bounds.size.height / 2)
              var  shapeLayer = CAShapeLayer()
  
              // radians = degrees * pi / 180
              // x*2 + y*2 = r*2
              //cos teta = x/r --&gt; x = r * cos teta
              // sinn teta = y/ r --&gt;  y = r * sin teta
      //        let x = 100 * cos(data.start)
      //        let y = 100 * sin(data.end)
  
              let radius = (bounds.origin.x + bounds.size.width / 2 - (sliceThickness)) / 2
  
              //This is the circle path drawn.
              let circularPath = UIBezierPath(arcCenter: .zero, radius: self.frame.width / 2, startAngle: data.start, endAngle: data.end, clockwise: true) //2*CGFloat.pi
  
              shapeLayer.path = circularPath.cgPath
  
              //Provide a bounding box for the shape layer to handle events
      //Removing the below line works but will not handle touch events :(
              shapeLayer.bounds = circularPath.cgPath.boundingBox
  
  
              //Start the angle from anyplace you need { + - of Pi} // {0, 0.5 pi, 1 pi, 1.5pi}
             // shapeLayer.transform = CATransform3DMakeRotation(-CGFloat.pi / 2 , 0, 0, 1)
              // color of the stroke
              shapeLayer.strokeColor = data.color.cgColor
  
              //Width of stoke
              shapeLayer.lineWidth = sliceThickness
  
              //Starts from the center of the view
              shapeLayer.position = center
  
              //To provide a rounded cap on the stroke
              shapeLayer.lineCap = .round
  
              //Fills the entire circle with this color
              shapeLayer.fillColor = UIColor.clear.cgColor
              shapeLayer.strokeEnd = 0
              basicAnim(shapeLayer: &amp;shapeLayer, percentage: percent)
              layer.addSublayer(shapeLayer)
          }
          func basicAnim(shapeLayer: inout CAShapeLayer)  {
              let basicAnimation = CABasicAnimation(keyPath: "strokeEnd")
              basicAnimation.toValue = 1
              basicAnimation.duration = 10
  
              //Forwards will hold the layer after completion
              basicAnimation.fillMode = .forwards
              basicAnimation.isRemovedOnCompletion = false
              shapeLayer.add(basicAnimation, forKey: "shapeLayerAniamtion")
          }
  
      //    //Calucate percentage based on given values
      //    public func calculateAngle(percentageVal:Double)-&gt; CGFloat {
      //        return CGFloat((percentageVal / 100) * 360)
      //        let val = CGFloat (percentageVal / 100.0)
      //        return val * 2 * CGFloat.pi
      //    }
  
          private func calcualteStartAndEndAngle(items : [dataItem])-&gt; [pieAngle] {
              var angle: pieAngle
              var angleToStart: CGFloat = 0.0
  
              //Add the total separator space to the circle so we can accurately measure the start point with space.
              var totalSeperatorSpace = Double(items.count) * separatorSpace
  
              var totalSum = items.reduce(CGFloat(totalSeperatorSpace)) { return $0 + $1.percentage }
  
              var angleList: [pieAngle] = []
              for item in items {
                  //Find the end angle based on the percentage in the total circle
                  let endAngle = (item.percentage / totalSum * 2 * .pi)  + angleToStart
                  angle.0 = angleToStart
                  angle.1 = endAngle
                  angle.2 = item.color
                  angleList.append(angle)
                  angleToStart = endAngle
                  //print(angle)
              }
              return angleList
          }
  
      }
  
  
      let container = UIView()
      container.frame.size = CGSize(width: 360, height: 360)
      container.backgroundColor = .white
      PlaygroundPage.current.liveView = container
      PlaygroundPage.current.needsIndefiniteExecution = true
  
      let m = USBCircleChart(frame: CGRect(x: 0, y: 0, width: 215, height: 215))
      m.center = CGPoint(x: container.bounds.size.width / 2, y: container.bounds.size.height / 2)
  
      m.fillDataForChart(with: pieDataToDisplay)
  
      container.addSubview(m)
  
  </code></pre>
  
  <p><strong>UPDATED :</strong></p>
  
  <p>Updated the code to include proper spacing irrespective of single/multiple items on the chart with equal distribution of total spacing, based on a suggestion from @jaferAli</p>
  
  <p><strong>Open Issue:</strong> Handling tap gesture on the layer so I can perform custom actions based on the category selected.</p>
  
  <p><strong>Screen 2</strong>
  <a href="https://i.stack.imgur.com/Tjmiq.jpg" rel="nofollow noreferrer"><img src="https://i.stack.imgur.com/Tjmiq.jpg" alt="enter image description here"></a></p>
  
  <p><strong>UPDATED CODE:</strong></p>
  
  <pre><code>import UIKit
  
  import PlaygroundSupport
  
  
  var str = "Hello, playground"
  struct dataItem {
      var color: UIColor
      var percentage: CGFloat
  }
  
  func hexStringToUIColor (hex:String) -&gt; UIColor {
      var cString:String = hex.trimmingCharacters(in: .whitespacesAndNewlines).uppercased()
  
      if (cString.hasPrefix("#")) {
          cString.remove(at: cString.startIndex)
      }
  
      if ((cString.count) != 6) {
          return UIColor.gray
      }
  
      var rgbValue:UInt64 = 0
      Scanner(string: cString).scanHexInt64(&amp;rgbValue)
  
      return UIColor(
          red: CGFloat((rgbValue &amp; 0xFF0000) &gt;&gt; 16) / 255.0,
          green: CGFloat((rgbValue &amp; 0x00FF00) &gt;&gt; 8) / 255.0,
          blue: CGFloat(rgbValue &amp; 0x0000FF) / 255.0,
          alpha: CGFloat(1.0)
      )
  }
  
  typealias pieAngle = (start: CGFloat, end: CGFloat, color: UIColor, percent: CGFloat)
  
  let pieDataToDisplay = [
      dataItem(color: hexStringToUIColor(hex: "#E61628"), percentage: 10),
      dataItem(color: hexStringToUIColor(hex: "#50B7FB"), percentage: 20),
              dataItem(color: hexStringToUIColor(hex: "#38BE72"), percentage: 25),
              dataItem(color: hexStringToUIColor(hex: "#FFAA4C"), percentage: 15),
              dataItem(color: hexStringToUIColor(hex: "#B6BE33"), percentage: 30)
  ]
  
  let pieDataToDisplayWhite = [dataItem(color: .white, percentage: 10),
  dataItem(color: .white, percentage: 20),
  dataItem(color: .white, percentage: 25),
  dataItem(color: .white, percentage: 25),
  dataItem(color: .orange, percentage: 10)]
  
  class USBCircleChart: UIView {
  
      private var piesToDisplay: [dataItem] = [] { didSet { setNeedsLayout() } }
  
      private var seperatorSpace: Double = 5.0 { didSet { setNeedsLayout() } }
  
      private var sliceThickness: CGFloat = 10.0 { didSet { setNeedsLayout() } }
  
      func fillDataForChart(with items: [dataItem] )  {
          self.piesToDisplay.append(contentsOf: items)
          print("getting data \(self.piesToDisplay)")
          layoutIfNeeded()
      }
  
      override func layoutSubviews() {
          super.layoutSubviews()
  
          guard piesToDisplay.count &gt; 0 else { return  }
  
          print("laying out data")
  
          let angles = calcualteStartAndEndAngle(items: piesToDisplay)
  
          for i in angles {
              var dataItem = i
              addSpace(data: &amp;dataItem)
              addShapeToCircle(data: dataItem, percent:i.percent)
          }
  
      }
  
      func addSpace(data:inout pieAngle) -&gt; pieAngle {
          // If space is not added, then its collated at the end, we have to scatter it between each item.
          //data.end -= CGFloat(seperatorSpace)
          return data
      }
  
      func addShapeToCircle(data : pieAngle, percent: CGFloat) {
          let center = CGPoint(x: bounds.origin.x + bounds.size.width / 2, y: bounds.origin.y + bounds.size.height / 2)
          var  shapeLayer = CAShapeLayer()
  
          // radians = degrees * pi / 180
          // x*2 + y*2 = r*2
          //cos teta = x/r --&gt; x = r * cos teta
          // sinn teta = y/ r --&gt;  y = r * sin teta
  //        let x = 100 * cos(data.start)
  //        let y = 100 * sin(data.end)
  
          let radius = (bounds.origin.x + bounds.size.width / 2 - (sliceThickness)) / 2
  
          //This is the circle path drawn.
          let circularPath = UIBezierPath(arcCenter: .zero, radius: self.frame.width / 2, startAngle: data.start, endAngle: data.end, clockwise: true) //2*CGFloat.pi
  
          shapeLayer.path = circularPath.cgPath
  
          //Provide a bounding box for the shape layer to handle events
          //shapeLayer.bounds = circularPath.cgPath.boundingBox
  
  
          //Start the angle from anyplace you need { + - of Pi} // {0, 0.5 pi, 1 pi, 1.5pi}
         // shapeLayer.transform = CATransform3DMakeRotation(-CGFloat.pi / 2 , 0, 0, 1)
          // color of the stroke
          shapeLayer.strokeColor = data.color.cgColor
  
          //Width of stoke
          shapeLayer.lineWidth = sliceThickness
  
          //Starts from the center of the view
          shapeLayer.position = center
  
          //To provide a rounded cap on the stroke
          shapeLayer.lineCap = .round
  
          //Fills the entire circle with this color
          shapeLayer.fillColor = UIColor.clear.cgColor
          shapeLayer.strokeEnd = 0
          basicAnim(shapeLayer: &amp;shapeLayer, percentage: percent)
          layer.addSublayer(shapeLayer)
      }
  
  
  
      func basicAnim(shapeLayer: inout CAShapeLayer)  {
          let basicAnimation = CABasicAnimation(keyPath: "strokeEnd")
          basicAnimation.toValue = 1
          basicAnimation.duration = 10
  
          //Forwards will hold the layer after completion
          basicAnimation.fillMode = .forwards
          basicAnimation.isRemovedOnCompletion = false
          shapeLayer.add(basicAnimation, forKey: "shapeLayerAniamtion")
      }
  
       private var timeOffset:CFTimeInterval = 0
       func basicAnim(shapeLayer: inout CAShapeLayer, percentage:CGFloat)  {
              let basicAnimation = CABasicAnimation(keyPath: "strokeEnd")
              basicAnimation.toValue = 1
              basicAnimation.duration = CFTimeInterval(percentage / 50)
              basicAnimation.beginTime = CACurrentMediaTime() + timeOffset
              print("timeOffset:\(timeOffset),")
              //Forwards will hold the layer after completion
              basicAnimation.fillMode = .forwards
              basicAnimation.isRemovedOnCompletion = false
              shapeLayer.add(basicAnimation, forKey: "shapeLayerAniamtion")
  
              timeOffset += CFTimeInterval(percentage / 50)
          }
      private func calcualteStartAndEndAngle(items : [dataItem])-&gt; [pieAngle] {
          var angle: pieAngle
          var angleToStart: CGFloat = 0.0
  
          //Add the total separator space to the circle so we can accurately measure the start point with space.
          let totalSeperatorSpace = Double(items.count)
  
          let totalSum = items.reduce(CGFloat(seperatorSpace)) { return $0 + $1.percentage }
  
          let spacing = CGFloat(seperatorSpace ) / CGFloat (totalSum)
          print("total Sum:\(spacing)")
  
          var angleList: [pieAngle] = []
  
          for item in items {
              //Find the end angle based on the percentage in the total circle
              let endAngle = (item.percentage / totalSum * 2 * CGFloat.pi)  + angleToStart
              print("start:\(angleToStart) end:\(endAngle)")
  
              angle.0 = angleToStart + spacing
              angle.1 = endAngle - spacing
              angle.2 = item.color
              angle.3 = item.percentage
              angleList.append(angle)
              angleToStart = endAngle + spacing
              //print(angle)
          }
          return angleList
      }
  }
  
  
  extension USBCircleChart  {
  
      @objc func handleTap() {
          print("getting tap action")
      }
  
      override func touchesBegan(_ touches: Set&lt;UITouch&gt;, with event: UIEvent?) {
          let touch = touches.first
  
          guard let loca = touch?.location(in: self) else { return }
          let point = self.convert(loca, from: nil)
          guard let sublayers = self.layer.sublayers as? [CAShapeLayer] else { return }
  
          for layer in sublayers {
              print("checking paths \(point) \(loca) \(layer.path) \n")
              if let path = layer.path, path.contains(point) {
                  print(layer)
              }
          }
      }
  
  }
  
  
  let container = UIView()
  container.frame.size = CGSize(width: 300, height: 300)
  container.backgroundColor = .white
  PlaygroundPage.current.liveView = container
  PlaygroundPage.current.needsIndefiniteExecution = true
  
  let m = USBCircleChart(frame: CGRect(x: 0, y: 0, width: 200, height: 200))
  //m.center = CGPoint(x: container.bounds.size.width / 2, y: container.bounds.size.height / 2)
  m.center = container.center
  m.fillDataForChart(with: pieDataToDisplay)
  container.addSubview(m)
  
  </code></pre>