Repository: airbnb/lottie-ios
File: Sources/Private/CoreAnimation/Animations/CALayer+addAnimation.swift (Swift)

Code before:
    }

    // Otherwise, we still need to create a `CAAnimation`, but we can
    // create a simple `CABasicAnimation` that is still less expensive
    // than computing a `CAKeyframeAnimation`.
    let animation = CABasicAnimation(keyPath: property.caLayerKeypath)
    animation.fromValue = keyframeValue
    animation.toValue = keyframeValue
    return animation
  }

  /// Creates a `CAAnimationGroup` that wraps a `CAKeyframeAnimation` for each
  /// of the given `animationSegments`
  private func animationGroup<KeyframeValue, ValueRepresentation>(
    for property: LayerProperty<ValueRepresentation>,
    animationSegments: [[Keyframe<KeyframeValue>]],
    value keyframeValueMapping: (KeyframeValue) throws -> ValueRepresentation,
    context: LayerAnimationContext)
    throws
    -> CAAnimationGroup
  {
    // Build the `CAKeyframeAnimation` for each segment of keyframes
    // with the same `CAAnimationCalculationMode`.
    //  - Here we have a non-zero number of animation segments,
    //    all of which have a non-zero number of keyframes.
    let segmentAnimations: [CAKeyframeAnimation] = try animationSegments.enumerated().map { index, animationSegment in
      var segmentStartTime = context.time(for: animationSegment.first!.time)
      var segmentEndTime = context.time(for: animationSegment.last!.time)

      // Every portion of the animation timeline has to be covered by a `CAKeyframeAnimation`,
      // so if this is the first or last segment then the start/end time should be exactly
      // the start/end time of the animation itself.
      let isFirstSegment = (index == animationSegments.indices.first!)
      let isLastSegment = (index == animationSegments.indices.last!)

      if isFirstSegment {
        segmentStartTime = context.time(for: context.animation.startFrame)
      }

      if isLastSegment {
        segmentEndTime = context.time(for: context.animation.endFrame)
      }

      let segmentDuration = segmentEndTime - segmentStartTime

      // We're building `CAKeyframeAnimation`s, so the `keyTimes` are expressed
      // relative to 0 (`segmentStartTime`) and 1 (`segmentEndTime`). This is different
      // from the default behavior of the `keyframeAnimation` method, where times
      // are expressed relative to the entire animation duration.
      let customKeyTimes = animationSegment.map { keyframeModel -> NSNumber in
        let keyframeTime = context.time(for: keyframeModel.time)

Review comment (refactor):
nit: suggest mapping over the indices and subscripting into `animationSegments` since that's robust against non-zero indexed types in the future e.g. if this is refactored to support other types of collections like an array slice

Code after:
    }

    // Otherwise, we still need to create a `CAAnimation`, but we can
    // create a simple `CABasicAnimation` that is still less expensive
    // than computing a `CAKeyframeAnimation`.
    let animation = CABasicAnimation(keyPath: property.caLayerKeypath)
    animation.fromValue = keyframeValue
    animation.toValue = keyframeValue
    return animation
  }

  /// Creates a `CAAnimationGroup` that wraps a `CAKeyframeAnimation` for each
  /// of the given `animationSegments`
  private func animationGroup<KeyframeValue, ValueRepresentation>(
    for property: LayerProperty<ValueRepresentation>,
    animationSegments: [[Keyframe<KeyframeValue>]],
    value keyframeValueMapping: (KeyframeValue) throws -> ValueRepresentation,
    context: LayerAnimationContext)
    throws
    -> CAAnimationGroup
  {
    // Build the `CAKeyframeAnimation` for each segment of keyframes
    // with the same `CAAnimationCalculationMode`.
    //  - Here we have a non-zero number of animation segments,
    //    all of which have a non-zero number of keyframes.
    let segmentAnimations: [CAKeyframeAnimation] = try animationSegments.indices.map { index in
      let animationSegment = animationSegments[index]
      var segmentStartTime = context.time(for: animationSegment.first!.time)
      var segmentEndTime = context.time(for: animationSegment.last!.time)

      // Every portion of the animation timeline has to be covered by a `CAKeyframeAnimation`,
      // so if this is the first or last segment then the start/end time should be exactly
      // the start/end time of the animation itself.
      let isFirstSegment = (index == animationSegments.indices.first!)
      let isLastSegment = (index == animationSegments.indices.last!)

      if isFirstSegment {
        segmentStartTime = context.time(for: context.animation.startFrame)
      }

      if isLastSegment {
        segmentEndTime = context.time(for: context.animation.endFrame)
      }

      let segmentDuration = segmentEndTime - segmentStartTime

      // We're building `CAKeyframeAnimation`s, so the `keyTimes` are expressed
      // relative to 0 (`segmentStartTime`) and 1 (`segmentEndTime`). This is different
      // from the default behavior of the `keyframeAnimation` method, where times
      // are expressed relative to the entire animation duration.
      let customKeyTimes = animationSegment.map { keyframeModel -> NSNumber in