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

Code before:

  private let group: Group

  /// `ShapeItem`s that were listed in the parent's `items: [ShapeItem]` array
  ///   - This layer's parent is either the root `ShapeLayerModel` or some other `Group`
  private let inheritedItems: [ShapeItemLayer.Item]

  private func setupLayerHierarchy() {
    // Groups can contain other groups, so we may have to continue
    // recursively creating more `GroupLayer`s
    setupGroups(from: group.items, parentGroup: group)

    let nonGroupItems = group.items
      .filter { !($0 is Group) }
      .map { ShapeItemLayer.Item(item: $0, parentGroup: group) }
      + inheritedItems

    let pathDrawingItemsInGroup = nonGroupItems.filter { $0.item.drawsCGPath }
    let otherItemsInGroup = nonGroupItems.filter { !$0.item.drawsCGPath }

    // If all of the path-drawing `ShapeItem`s have keyframes with the same timing information,
    // we can combine the `[KeyframeGroup<BezierPath>]` (which have to animate in separate layers)
    // into a single `KeyframeGroup<[BezierPath]>`, which can be combined into a single CGPath animation.
    //
    // This is how Groups with multiple path-drawing items are supposed to be rendered,
    // because combing multiple paths into a single `CGPath` (instead of rendering them in separate layers)
    // allows `CAShapeLayerFillRule.evenOdd` to be applied if the paths overlap. We just can't do this
    // in all cases, due to limitations of Core Animation.
    if
      pathDrawingItemsInGroup.count > 1,
      let combinedShapeKeyframes = Keyframes.combinedIfPossible(pathDrawingItemsInGroup.map {
        ($0.item as? Shape)?.path
      }),
      // `Trim`s are currently only applied correctly using individual `ShapeItemLayer`s,
      // because each path has to be trimmed separately.
      !otherItemsInGroup.contains(where: { $0.item.type == .trim })
    {
      let combinedShape = CombinedShapeItem(
        shapes: combinedShapeKeyframes,
        name: group.name)

      let sublayer = ShapeItemLayer(
        shape: ShapeItemLayer.Item(item: combinedShape, parentGroup: group),
        otherItems: otherItemsInGroup)

      addSublayer(sublayer)
    }

    // Otherwise, if each `ShapeItem` that draws a `GGPath` animates independently,
    // we have to create a separate `ShapeItemLayer` for each one.
    else {

Review comment (suggestion):
```suggestion
    // because combining multiple paths into a single `CGPath` (instead of rendering them in separate layers)
```

Code after:

  private let group: Group

  /// `ShapeItem`s that were listed in the parent's `items: [ShapeItem]` array
  ///   - This layer's parent is either the root `ShapeLayerModel` or some other `Group`
  private let inheritedItems: [ShapeItemLayer.Item]

  private func setupLayerHierarchy() {
    // Groups can contain other groups, so we may have to continue
    // recursively creating more `GroupLayer`s
    setupGroups(from: group.items, parentGroup: group)

    let nonGroupItems = group.items
      .filter { !($0 is Group) }
      .map { ShapeItemLayer.Item(item: $0, parentGroup: group) }
      + inheritedItems

    let (pathDrawingItemsInGroup, otherItemsInGroup) = nonGroupItems.grouped(by: \.item.drawsCGPath)

    // If all of the path-drawing `ShapeItem`s have keyframes with the same timing information,
    // we can combine the `[KeyframeGroup<BezierPath>]` (which have to animate in separate layers)
    // into a single `KeyframeGroup<[BezierPath]>`, which can be combined into a single CGPath animation.
    //
    // This is how Groups with multiple path-drawing items are supposed to be rendered,
    // because combining multiple paths into a single `CGPath` (instead of rendering them in separate layers)
    // allows `CAShapeLayerFillRule.evenOdd` to be applied if the paths overlap. We just can't do this
    // in all cases, due to limitations of Core Animation.
    if
      pathDrawingItemsInGroup.count > 1,
      let combinedShapeKeyframes = Keyframes.combinedIfPossible(pathDrawingItemsInGroup.map {
        ($0.item as? Shape)?.path
      }),
      // `Trim`s are currently only applied correctly using individual `ShapeItemLayer`s,
      // because each path has to be trimmed separately.
      !otherItemsInGroup.contains(where: { $0.item.type == .trim })
    {
      let combinedShape = CombinedShapeItem(
        shapes: combinedShapeKeyframes,
        name: group.name)

      let sublayer = ShapeItemLayer(
        shape: ShapeItemLayer.Item(item: combinedShape, parentGroup: group),
        otherItems: otherItemsInGroup)

      addSublayer(sublayer)
    }

    // Otherwise, if each `ShapeItem` that draws a `GGPath` animates independently,
    // we have to create a separate `ShapeItemLayer` for each one.
    else {
      for pathDrawingItem in pathDrawingItemsInGroup {