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
}
}
}