75 lines
2.8 KiB
Swift
75 lines
2.8 KiB
Swift
|
import UIKit
|
||
|
|
||
|
extension UIScrollView {
|
||
|
private var topOffsetY: CGFloat {
|
||
|
return 0 - adjustedContentInset.top
|
||
|
}
|
||
|
|
||
|
public var bottomOffsetY: CGFloat {
|
||
|
return contentSize.height - bounds.size.height + adjustedContentInset.bottom
|
||
|
}
|
||
|
|
||
|
private var topOffset: CGPoint {
|
||
|
return CGPoint(x: contentOffset.x, y: topOffsetY)
|
||
|
}
|
||
|
|
||
|
private var bottomOffset: CGPoint {
|
||
|
return CGPoint(x: contentOffset.x, y: bottomOffsetY)
|
||
|
}
|
||
|
|
||
|
public var isAtTop: Bool {
|
||
|
// Rounded: Sometimes when we expect them to be equal, these are less than .2 different (due to rounding in earleir calculation) - and with multiple layout passes, it caused a large scrolling bug on a VC's launch.
|
||
|
return contentOffset.y.rounded(.up) <= topOffsetY.rounded(.up)
|
||
|
}
|
||
|
|
||
|
private var isAtBottom: Bool {
|
||
|
// Rounded: Sometimes when we expect them to be equal, these are less than .2 different (due to rounding in earleir calculation) - and with multiple layout passes, it caused a large scrolling bug on a VC's launch.
|
||
|
return contentOffset.y.rounded(.up) >= bottomOffsetY.rounded(.up)
|
||
|
}
|
||
|
|
||
|
public var verticalOffsetPercentage: CGFloat {
|
||
|
get {
|
||
|
let height = contentSize.height
|
||
|
guard height > 0 else {
|
||
|
return 0
|
||
|
}
|
||
|
return contentOffset.y / height
|
||
|
}
|
||
|
set {
|
||
|
let newOffsetY = contentSize.height * newValue
|
||
|
setContentOffset(CGPoint(x: contentOffset.x, y: newOffsetY), animated: false)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
@objc(wmf_setContentInset:verticalScrollIndicatorInsets:preserveContentOffset:preserveAnimation:)
|
||
|
public func setContentInset(_ updatedContentInset: UIEdgeInsets, verticalScrollIndicatorInsets updatedVerticalScrollIndicatorInsets: UIEdgeInsets, preserveContentOffset: Bool = true, preserveAnimation: Bool = false) -> Bool {
|
||
|
guard updatedContentInset != contentInset || updatedVerticalScrollIndicatorInsets != verticalScrollIndicatorInsets else {
|
||
|
return false
|
||
|
}
|
||
|
let wasAtTop = isAtTop
|
||
|
let wasAtBottom = isAtBottom
|
||
|
verticalScrollIndicatorInsets = updatedVerticalScrollIndicatorInsets
|
||
|
|
||
|
if preserveAnimation {
|
||
|
contentInset = updatedContentInset
|
||
|
} else {
|
||
|
let wereAnimationsEnabled = UIView.areAnimationsEnabled
|
||
|
UIView.setAnimationsEnabled(false)
|
||
|
contentInset = updatedContentInset
|
||
|
UIView.setAnimationsEnabled(wereAnimationsEnabled)
|
||
|
}
|
||
|
|
||
|
guard preserveContentOffset else {
|
||
|
return true
|
||
|
}
|
||
|
|
||
|
if wasAtTop {
|
||
|
contentOffset = topOffset
|
||
|
} else if contentSize.height > bounds.inset(by: adjustedContentInset).height && wasAtBottom {
|
||
|
contentOffset = bottomOffset
|
||
|
}
|
||
|
|
||
|
return true
|
||
|
}
|
||
|
}
|