title: Is the a way to have a timing function work on multiple animations in Swift?
body: <p>I'm trying to recreate the Activity ring that Apple uses in their activity apps. Here is an image for those unaware.</p>
  <p><a href="https://i.stack.imgur.com/DjL7F.png" rel="nofollow noreferrer">Apple Progress Ring.</a></p>
  <p>I've done a decent job of recreating it, something I especially struggled with was the overlapping shadow. In the end, the workaround I used was to split the ring into two parts, the first 75% and the last 25% so I could have the last 25% have a shadow and appear to overlap with itself.</p>
  <p>Now that I have done this, the animation timing has become more difficult. I now have three animations that I need to take care of.</p>
  <ol>
  <li>The first 75% of the ring</li>
  <li>The last 25% of the ring</li>
  <li>Rotating the ring if it surpasses 100%</li>
  </ol>
  <p>Here is a video demonstrating this. <a href="https://streamable.com/wrwly7" rel="nofollow noreferrer">Streamable Link.</a></p>
  <p>For illustrative purposes, here is the last 25% coloured differently so you can <a href="https://i.stack.imgur.com/EbgnZ.png" rel="nofollow noreferrer">visualise it.</a></p>
  <p>As you can see, the timing of the animation is a bit janky. So I have my timings set as follows</p>
  <ol>
  <li>If the ring is filled at 75% or less, it takes 2.25 seconds to fill with a timing function of ease out</li>
  <li>If the ring is filled between 75 and 100%, the first 75% takes 1 second to fill and the last 25% takes 1.25 seconds to fill with a timing function of ease out.</li>
  <li>If the ring is filled over 100%, the first 75% takes 1 second, the last 25% takes 1 second and the rotation for the ring also takes 1 second with a timing function of ease out.</li>
  </ol>
  <p>My question is, is it possible to link these seperate CABasicAnimations so I can set a total time of 2.25 seconds as well as set a timing function for that group so timing is calculated dynamically for each animation and a timing function affects all three?</p>
  <p>Here is my code so far, it consists of 3 animation functions.</p>
  <p>percent = How much to fill the ring</p>
  <ul>
  <li><p>gradientMaskPart1 = first 75% ring layer</p>
  </li>
  <li><p>gradientMaskPart2 = last 25% ring layer</p>
  </li>
  <li><p>containerLayer = layer that holds both gradientMaskParts and is rotated to simute the ring overlapping itself.</p>
  <pre><code> private func animateRing() {
  
   let needsMultipleAnimations = percent &lt;= 0.75 ? false : true
  
   CATransaction.begin()
  
   if needsMultipleAnimations { CATransaction.setCompletionBlock(ringEndAnimation) }
  
   let basicAnimation = CABasicAnimation(keyPath: &quot;strokeEnd&quot;)
   basicAnimation.fromValue = currentFill
   basicAnimation.toValue = percent &gt; 0.75 ? 0.75 : percent
   currentFill = Double(percent)
   basicAnimation.fillMode = .forwards
   basicAnimation.isRemovedOnCompletion = false
   basicAnimation.duration = needsMultipleAnimations ? 1 : 2.25
   basicAnimation.timingFunction =
       needsMultipleAnimations ? .none : CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeOut)
  
  
   gradientMaskPart1.add(basicAnimation, forKey: &quot;basicAnimation&quot;)
  
   CATransaction.commit()
  
   }
  </code></pre>
  </li>
  <li>
  <pre><code>  private func ringEndAnimation() {
  </code></pre>
  </li>
  </ul>
  <pre><code>    let needsMultipleAnimations = percent &lt;= 1 ? false : true
  
      CATransaction.begin()
  
      if needsMultipleAnimations { CATransaction.setCompletionBlock(rotateRingAnimation) }
  
      let duration = needsMultipleAnimations ? 1 : 1.25
      let timingFunction: CAMediaTimingFunction? =
          needsMultipleAnimations ? .none : CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeOut)
  
      let basicAnimation = CABasicAnimation(keyPath: &quot;strokeEnd&quot;)
      basicAnimation.fromValue = 0
      basicAnimation.toValue = percent &lt;= 1 ? (percent-0.75)*4 : 1
  
      basicAnimation.duration = duration
      basicAnimation.fillMode = .forwards
      basicAnimation.isRemovedOnCompletion = false
      basicAnimation.timingFunction = timingFunction
  
  
      self.gradientMaskPart2.isHidden = false
      self.gradientMaskPart2.add(basicAnimation2, forKey: &quot;basicAnimation&quot;)
  
      CATransaction.commit()
      }
  </code></pre>
  <ul>
  <li>
  <pre><code>  private func rotateRingAnimation() {
  </code></pre>
  </li>
  </ul>
  <pre><code>    let rotationAnimation = CABasicAnimation(keyPath: &quot;transform.rotation.z&quot;)
      rotationAnimation.fromValue = 2*CGFloat.pi
      rotationAnimation.toValue = 2*CGFloat.pi+((2*(percent-1)*CGFloat.pi))
      rotationAnimation.duration = 1
      rotationAnimation.fillMode = CAMediaTimingFillMode.forwards
      rotationAnimation.isRemovedOnCompletion = false
      rotationAnimation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeOut)
      self.containerLayer.add(rotationAnimation, forKey: &quot;rotation&quot;)
      }
  </code></pre>