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

Code before:
    }

    /// Called by CoreAnimation to create a shadow copy of this layer
    /// More details: https://developer.apple.com/documentation/quartzcore/calayer/1410842-init
    override init(layer: Any) {
      guard let typedLayer = layer as? Self else {
        fatalError("\(Self.self).init(layer:) incorrectly called with \(type(of: layer))")
      }

      maskModel = typedLayer.maskModel
      super.init(layer: typedLayer)
    }

    // MARK: Private

    private let maskModel: Mask

  }
}

// MARK: - MaskCompositionLayer.MaskLayer + AnimationLayer

extension MaskCompositionLayer.MaskLayer: AnimationLayer {
  func setupAnimations(context: LayerAnimationContext) throws {
    let shouldInvertMask = (maskModel.mode == .subtract && !maskModel.inverted)
      || (maskModel.mode == .add && maskModel.inverted)

    try addAnimations(
      for: maskModel.shape,
      context: context,
      transformPath: { maskPath in
        // If the mask is using `MaskMode.subtract` or has `inverted: true`,
        // we have to invert the area filled by the path. We can do that by
        // drawing a rectangle, and then adding a path (which is subtracted
        // from the rectangle based on the .evenOdd fill mode).
        if shouldInvertMask {
          let path = CGMutablePath()
          path.addRect(bounds)
          path.addPath(maskPath)
          return path
        } else {
          return maskPath
        }
      })
  }
}


Review comment (suggestion):
Pulled this logic from the main thread engine: https://github.com/airbnb/lottie-ios/blob/167812e8ce59698144714b81a4132d3d188f2a39/Sources/Private/MainThread/LayerContainers/CompLayers/MaskContainerLayer.swift#L144

Code after:

extension MaskCompositionLayer {
  final class MaskLayer: CAShapeLayer {

    // MARK: Lifecycle

    init(mask: Mask) {
      maskModel = mask
      super.init()

      fillRule = .evenOdd
    }

    required init?(coder _: NSCoder) {
      fatalError("init(coder:) has not been implemented")
    }

    /// Called by CoreAnimation to create a shadow copy of this layer
    /// More details: https://developer.apple.com/documentation/quartzcore/calayer/1410842-init
    override init(layer: Any) {
      guard let typedLayer = layer as? Self else {
        fatalError("\(Self.self).init(layer:) incorrectly called with \(type(of: layer))")
      }

      maskModel = typedLayer.maskModel
      super.init(layer: typedLayer)
    }

    // MARK: Internal

    let maskModel: Mask

  }
}

// MARK: - MaskCompositionLayer.MaskLayer + AnimationLayer

extension MaskCompositionLayer.MaskLayer: AnimationLayer {
  func setupAnimations(context: LayerAnimationContext) throws {
    let shouldInvertMask = (maskModel.mode.usableMode == .subtract && !maskModel.inverted)
      || (maskModel.mode.usableMode == .add && maskModel.inverted)

    try addAnimations(
      for: maskModel.shape,
      context: context,
      transformPath: { maskPath in
        // If the mask is using `MaskMode.subtract` or has `inverted: true`,
        // we have to invert the area filled by the path. We can do that by
        // drawing a rectangle, and then adding a path (which is subtracted
        // from the rectangle based on the .evenOdd fill mode).
        if shouldInvertMask {