Repository: airbnb/lottie-ios
File: Sources/Public/Animation/AnimationView.swift (Swift)

Code before:
        animationLayer.position = position
        animationLayer.transform = xform
        CATransaction.commit()
      }
    }

    if shouldForceUpdates {
      animationLayer.forceDisplayUpdate()
    }
  }

  func updateRasterizationState() {
    if isAnimationPlaying {
      animationLayer?.shouldRasterize = false
    } else {
      animationLayer?.shouldRasterize = shouldRasterizeWhenIdle
    }
  }

  /// Updates the animation frame. Does not affect any current animations
  func updateAnimationFrame(_ newFrame: CGFloat) {
    // In performance tests, we have to wrap the animation view setup
    // in a `CATransaction` in order for the layers to be deallocated at
    // the correct time. The `CATransaction`s in this method interfere
    // with the ones managed by the performance test, and aren't actually
    // necessary in a headless environment, so we disable them.
    if TestHelpers.performanceTestsAreRunning {
      animationLayer?.currentFrame = newFrame
      animationLayer?.forceDisplayUpdate()
      return
    }

    CATransaction.begin()
    CATransaction.setCompletionBlock {
      self.animationLayer?.forceDisplayUpdate()
    }
    CATransaction.setDisableActions(true)
    animationLayer?.currentFrame = newFrame
    CATransaction.commit()
  }

  @objc
  override func animationWillMoveToBackground() {
    updateAnimationForBackgroundState()
  }

  @objc
  override func animationWillEnterForeground() {
    updateAnimationForForegroundState()
  }


Review comment (performance):
I also had to do this in https://github.com/airbnb/lottie-ios/pull/1518/files#diff-b8031f1d50b5687c758122fa8aa487e09cb9068eee0db0d2ecff606ba944e9d3R834. I must have missed this on the last PR, or changed the code path just enough to start hitting this in the performance tests.

Code after:
      positionAnimation.toValue = position
      positionAnimation.isRemovedOnCompletion = true

      let xformAnimation = animation.copy() as? CABasicAnimation ?? CABasicAnimation(keyPath: "transform")
      xformAnimation.keyPath = "transform"
      xformAnimation.isAdditive = false
      xformAnimation.fromValue = (animationLayer.presentation() ?? animationLayer).transform
      xformAnimation.toValue = xform
      xformAnimation.isRemovedOnCompletion = true

      animationLayer.position = position
      animationLayer.transform = xform
      #if os(OSX)
      animationLayer.anchorPoint = layer?.anchorPoint ?? CGPoint.zero
      #else
      animationLayer.anchorPoint = layer.anchorPoint
      #endif
      animationLayer.add(positionAnimation, forKey: positionKey)
      animationLayer.add(xformAnimation, forKey: transformKey)
    } else {
      // In performance tests, we have to wrap the animation view setup
      // in a `CATransaction` in order for the layers to be deallocated at
      // the correct time. The `CATransaction`s in this method interfere
      // with the ones managed by the performance test, and aren't actually
      // necessary in a headless environment, so we disable them.
      if TestHelpers.performanceTestsAreRunning {
        animationLayer.position = position
        animationLayer.transform = xform
      } else {
        CATransaction.begin()
        CATransaction.setAnimationDuration(0.0)
        CATransaction.setAnimationTimingFunction(CAMediaTimingFunction(name: .linear))
        animationLayer.position = position
        animationLayer.transform = xform
        CATransaction.commit()
      }
    }

    if shouldForceUpdates {
      animationLayer.forceDisplayUpdate()
    }
  }

  func updateRasterizationState() {
    if isAnimationPlaying {
      animationLayer?.shouldRasterize = false
    } else {
      animationLayer?.shouldRasterize = shouldRasterizeWhenIdle
    }
  }