Repository: airbnb/lottie-ios
File: Sources/Private/Experimental/Animations/ShapeAnimation.swift (Swift)

Code before:

      addAnimation(
        for: .lineDashPhase,
        keyframes: dashPhase,
        value: \.cgFloatValue,
        context: context)
    }
  }

  /// Adds animations for `strokeStart` and `strokeEnd` from the given `Trim` object
  @nonobjc
  func addAnimations(for trim: Trim, context: LayerAnimationContext) {
    // `CAShapeLayer` requires that `strokeStart` be less than `strokeEnd`.
    //  - Since this isn't a requirement in the Lottie schema, there are
    //    some animations that have `strokeStart` and `strokeEnd` flipped.
    //  - If we detect that this is the case for this specific `Trim`, then
    //    we swap the start/end keyframes to match what `CAShapeLayer` expects.
    let strokeStartKeyframes: KeyframeGroup<Vector1D>
    let strokeEndKeyframes: KeyframeGroup<Vector1D>
    if trim.startValueIsAlwaysGreaterThanEndValue() {
      strokeStartKeyframes = trim.end
      strokeEndKeyframes = trim.start
    } else {
      strokeStartKeyframes = trim.start
      strokeEndKeyframes = trim.end
    }

    addAnimation(
      for: .strokeStart,
      keyframes: strokeStartKeyframes.keyframes,
      value: { strokeStart in
        // Lottie animation files express stoke trims as a numerical percentage value
        // (e.g. 25%, 50%, 100%) so we divide by 100 to get the decimal values
        // expected by Core Animation (e.g. 0.25, 0.5, 1.0).
        CGFloat(strokeStart.cgFloatValue) / 100
      }, context: context)

    addAnimation(
      for: .strokeEnd,
      keyframes: strokeEndKeyframes.keyframes,
      value: { strokeEnd in
        // Lottie animation files express stoke trims as a numerical percentage value
        // (e.g. 25%, 50%, 100%) so we divide by 100 to get the decimal values
        // expected by Core Animation (e.g. 0.25, 0.5, 1.0).
        CGFloat(strokeEnd.cgFloatValue) / 100
      }, context: context)
  }
}

extension Trim {
  /// Checks whether or not the value for `trim.start` is greater

Review comment (suggestion):
nit: might be simpler to reason about if this method returns a tuple of the start/end keyframes:
```suggestion
    let (strokeStartKeyframes, strokeEndKeyframes) = trim.caShapeLayerKeyframes()
```

Code after:

      addAnimation(
        for: .lineDashPhase,
        keyframes: dashPhase,
        value: \.cgFloatValue,
        context: context)
    }
  }

  /// Adds animations for `strokeStart` and `strokeEnd` from the given `Trim` object
  @nonobjc
  func addAnimations(for trim: Trim, context: LayerAnimationContext) {
    let (strokeStartKeyframes, strokeEndKeyframes) = trim.caShapeLayerKeyframes()

    addAnimation(
      for: .strokeStart,
      keyframes: strokeStartKeyframes.keyframes,
      value: { strokeStart in
        // Lottie animation files express stoke trims as a numerical percentage value
        // (e.g. 25%, 50%, 100%) so we divide by 100 to get the decimal values
        // expected by Core Animation (e.g. 0.25, 0.5, 1.0).
        CGFloat(strokeStart.cgFloatValue) / 100
      }, context: context)

    addAnimation(
      for: .strokeEnd,
      keyframes: strokeEndKeyframes.keyframes,
      value: { strokeEnd in
        // Lottie animation files express stoke trims as a numerical percentage value
        // (e.g. 25%, 50%, 100%) so we divide by 100 to get the decimal values
        // expected by Core Animation (e.g. 0.25, 0.5, 1.0).
        CGFloat(strokeEnd.cgFloatValue) / 100
      }, context: context)
  }
}

extension Trim {
  /// The `strokeStart` and `strokeEnd` keyframes to apply to a `CAShapeLayer`
  ///  - `CAShapeLayer` requires that `strokeStart` be less than `strokeEnd`.
  ///  - Since this isn't a requirement in the Lottie schema, there are
  ///    some animations that have `strokeStart` and `strokeEnd` flipped.
  ///  - If we detect that this is the case for this specific `Trim`, then
  ///    we swap the start/end keyframes to match what `CAShapeLayer` expects.
  fileprivate func caShapeLayerKeyframes()
    -> (strokeStart: KeyframeGroup<Vector1D>, strokeEnd: KeyframeGroup<Vector1D>)
  {
    if startValueIsAlwaysGreaterThanEndValue() {
      return (strokeStart: end, strokeEnd: start)
    } else {
      return (strokeStart: start, strokeEnd: end)
    }