Custom scrolling animation in UIScrollView that powered by Advance

Updated
Apr 29, 2021 7:18 AM
Created
Apr 29, 2021 7:15 AM
Tags
SwiftUIKit.framework
Keywords
Advance

UIScrollView's scrolling is not CoreAnimation based Animation.

To achieve custom scrolling animation, we need to update content-offset every frame by calculation and displaylink.

import Advance
import UIKit

private var contentInsetAnimatorKey: Void?
private var contentOffsetAnimatorKey: Void?

extension UIScrollView {

  private var currentContentInsetAnimator: Animator<CGFloat>? {
    get {
      objc_getAssociatedObject(self, &contentInsetAnimatorKey) as? Animator<CGFloat>
    }
    set {
      objc_setAssociatedObject(self, &contentInsetAnimatorKey, newValue, .OBJC_ASSOCIATION_RETAIN)
    }
  }

  private var currentContentOffsetAnimator: Animator<CGFloat>? {
    get {
      objc_getAssociatedObject(self, &contentOffsetAnimatorKey) as? Animator<CGFloat>
    }
    set {
      objc_setAssociatedObject(self, &contentOffsetAnimatorKey, newValue, .OBJC_ASSOCIATION_RETAIN)
    }
  }

  /// With dynamic animation
  /// [Using Advance] Prototyping
  public func removeCurrentContentInsetAnimation() {
    currentContentInsetAnimator?.cancelRunningAnimation()
  }

  /// With dynamic animation
  /// [Using Advance] Prototyping
  public func setContentInsetTop(_ topInset: CGFloat, animatedDynamically: Bool) {

    if animatedDynamically {

      let animator = Advance.Animator<CGFloat>(
        initialValue: contentInset.top
      )

      animator.onChange = { [weak self] value in
        guard let self = self else { return }

        guard self.isTracking == false else {
          animator.cancelRunningAnimation()
          self.contentInset.top = topInset
          return
        }

        self.contentInset.top = value
      }

      animator.simulate(
        using: SpringFunction(
          target: topInset,
          tension: 1200,
          damping: 120
        )
      )

      currentContentInsetAnimator = animator
    } else {
      contentInset.top = topInset
    }

  }

  /// With dynamic animation
  /// [Using Advance] Prototyping
  public func removeCurrentContentOffsetAnimation() {
    currentContentInsetAnimator?.cancelRunningAnimation()
  }

  /// With dynamic animation
  /// [Using Advance] Prototyping
  public func setContentOffsetY(_ offsetY: CGFloat, animatedDynamically: Bool) {

    if animatedDynamically {

      let animator = Advance.Animator<CGFloat>(
        initialValue: contentOffset.y
      )

      animator.onChange = { [weak self] value in
        guard let self = self else { return }

        guard self.isTracking == false else {
          animator.cancelRunningAnimation()
          return
        }

        self.contentOffset.y = value
      }

      animator.simulate(
        using: SpringFunction(
          target: offsetY,
          tension: 1200,
          damping: 120
        )
      )

      currentContentInsetAnimator = animator
    } else {
      contentInset.top = offsetY
    }

  }


}