mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-12-07 06:43:43 +00:00
Improved ListView scroll indicator
Fixed PresentationContext ordering
This commit is contained in:
parent
7a58ac1bfc
commit
44a6e284e4
@ -2523,7 +2523,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel
|
||||
|
||||
self.updateItemHeaders(leftInset: listInsets.left, rightInset: listInsets.right, transition: headerNodesTransition, animateInsertion: animated || !requestItemInsertionAnimationsIndices.isEmpty)
|
||||
|
||||
if let offset = offset , abs(offset) > CGFloat.ulpOfOne {
|
||||
if let offset = offset, !offset.isZero {
|
||||
let lowestNodeToInsertBelow = self.lowestNodeToInsertBelow()
|
||||
for itemNode in temporaryPreviousNodes {
|
||||
itemNode.frame = itemNode.frame.offsetBy(dx: 0.0, dy: offset)
|
||||
@ -2550,6 +2550,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel
|
||||
}
|
||||
|
||||
let animation: CABasicAnimation
|
||||
let reverseAnimation: CABasicAnimation
|
||||
switch scrollToItem.curve {
|
||||
case let .Spring(duration):
|
||||
let springAnimation = makeSpringAnimation("sublayerTransform")
|
||||
@ -2566,7 +2567,17 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel
|
||||
}
|
||||
springAnimation.speed = speed * Float(springAnimation.duration / duration)
|
||||
|
||||
let reverseSpringAnimation = makeSpringAnimation("sublayerTransform")
|
||||
reverseSpringAnimation.fromValue = NSValue(caTransform3D: CATransform3DMakeTranslation(0.0, offset, 0.0))
|
||||
reverseSpringAnimation.toValue = NSValue(caTransform3D: CATransform3DIdentity)
|
||||
reverseSpringAnimation.isRemovedOnCompletion = true
|
||||
reverseSpringAnimation.isAdditive = true
|
||||
reverseSpringAnimation.fillMode = kCAFillModeForwards
|
||||
|
||||
reverseSpringAnimation.speed = speed * Float(reverseSpringAnimation.duration / duration)
|
||||
|
||||
animation = springAnimation
|
||||
reverseAnimation = reverseSpringAnimation
|
||||
case let .Default(duration):
|
||||
if let duration = duration {
|
||||
let basicAnimation = CABasicAnimation(keyPath: "sublayerTransform")
|
||||
@ -2576,17 +2587,36 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel
|
||||
basicAnimation.toValue = NSValue(caTransform3D: CATransform3DIdentity)
|
||||
basicAnimation.isRemovedOnCompletion = true
|
||||
basicAnimation.isAdditive = true
|
||||
|
||||
let reverseBasicAnimation = CABasicAnimation(keyPath: "sublayerTransform")
|
||||
reverseBasicAnimation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
|
||||
reverseBasicAnimation.duration = duration * UIView.animationDurationFactor()
|
||||
reverseBasicAnimation.fromValue = NSValue(caTransform3D: CATransform3DMakeTranslation(0.0, offset, 0.0))
|
||||
reverseBasicAnimation.toValue = NSValue(caTransform3D: CATransform3DIdentity)
|
||||
reverseBasicAnimation.isRemovedOnCompletion = true
|
||||
reverseBasicAnimation.isAdditive = true
|
||||
|
||||
animation = basicAnimation
|
||||
reverseAnimation = reverseBasicAnimation
|
||||
} else {
|
||||
let basicAnimation = CABasicAnimation(keyPath: "sublayerTransform")
|
||||
basicAnimation.timingFunction = CAMediaTimingFunction(controlPoints: 0.33, 0.52, 0.25, 0.99)
|
||||
//basicAnimation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut)
|
||||
basicAnimation.duration = (duration ?? 0.3) * UIView.animationDurationFactor()
|
||||
basicAnimation.fromValue = NSValue(caTransform3D: CATransform3DMakeTranslation(0.0, -offset, 0.0))
|
||||
basicAnimation.toValue = NSValue(caTransform3D: CATransform3DIdentity)
|
||||
basicAnimation.isRemovedOnCompletion = true
|
||||
basicAnimation.isAdditive = true
|
||||
|
||||
let reverseBasicAnimation = CABasicAnimation(keyPath: "sublayerTransform")
|
||||
reverseBasicAnimation.timingFunction = CAMediaTimingFunction(controlPoints: 0.33, 0.52, 0.25, 0.99)
|
||||
reverseBasicAnimation.duration = (duration ?? 0.3) * UIView.animationDurationFactor()
|
||||
reverseBasicAnimation.fromValue = NSValue(caTransform3D: CATransform3DMakeTranslation(0.0, offset, 0.0))
|
||||
reverseBasicAnimation.toValue = NSValue(caTransform3D: CATransform3DIdentity)
|
||||
reverseBasicAnimation.isRemovedOnCompletion = true
|
||||
reverseBasicAnimation.isAdditive = true
|
||||
|
||||
animation = basicAnimation
|
||||
reverseAnimation = reverseBasicAnimation
|
||||
}
|
||||
}
|
||||
animation.completion = { _ in
|
||||
@ -2610,6 +2640,9 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel
|
||||
}
|
||||
}
|
||||
self.layer.add(animation, forKey: nil)
|
||||
if let verticalScrollIndicator = self.verticalScrollIndicator {
|
||||
verticalScrollIndicator.layer.add(reverseAnimation, forKey: nil)
|
||||
}
|
||||
} else {
|
||||
if useBackgroundDeallocation {
|
||||
assertionFailure()
|
||||
@ -2969,100 +3002,86 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel
|
||||
var topIndexAndBoundary: (Int, CGFloat, CGFloat)?
|
||||
var bottomIndexAndBoundary: (Int, CGFloat, CGFloat)?
|
||||
for itemNode in self.itemNodes {
|
||||
if itemNode.apparentFrame.maxY > 0.0, let index = itemNode.index {
|
||||
if itemNode.apparentFrame.maxY > self.insets.top, let index = itemNode.index {
|
||||
topIndexAndBoundary = (index, itemNode.apparentFrame.minY, itemNode.apparentFrame.height)
|
||||
break
|
||||
}
|
||||
}
|
||||
for itemNode in self.itemNodes.reversed() {
|
||||
if itemNode.apparentFrame.minY <= self.visibleSize.height, let index = itemNode.index {
|
||||
if itemNode.apparentFrame.minY <= self.visibleSize.height - self.insets.bottom, let index = itemNode.index {
|
||||
bottomIndexAndBoundary = (index, itemNode.apparentFrame.maxY, itemNode.apparentFrame.height)
|
||||
break
|
||||
}
|
||||
}
|
||||
if let topIndexAndBoundary = topIndexAndBoundary, let bottomIndexAndBoundary = bottomIndexAndBoundary {
|
||||
let rangeItemCount = max(1, bottomIndexAndBoundary.0 - topIndexAndBoundary.0 + 1)
|
||||
//let visibleRangeHeight = max(0.0, bottomIndexAndBoundary.1 - topIndexAndBoundary.1)
|
||||
//let averageRangeItemHeight = visibleRangeHeight / CGFloat(rangeItemCount)
|
||||
let averageRangeItemHeight: CGFloat = 44.0
|
||||
let averageRangeItemHeight: CGFloat = 44.0 //(bottomIndexAndBoundary.1 - topIndexAndBoundary.1) / CGFloat(bottomIndexAndBoundary.0 - topIndexAndBoundary.0 + 1)
|
||||
|
||||
let visibleRangeHeight = CGFloat(rangeItemCount) * averageRangeItemHeight
|
||||
let upperItemsHeight = floor(averageRangeItemHeight * CGFloat(topIndexAndBoundary.0))
|
||||
let lowerItemsHeight = floor(averageRangeItemHeight * CGFloat(self.items.count - bottomIndexAndBoundary.0))
|
||||
let approximateContentHeight = upperItemsHeight + visibleRangeHeight + lowerItemsHeight
|
||||
let approximateContentHeight = CGFloat(self.items.count) * averageRangeItemHeight
|
||||
|
||||
let convertedTopBoundary: CGFloat
|
||||
if topIndexAndBoundary.1 < 0.0 {
|
||||
convertedTopBoundary = topIndexAndBoundary.1 * averageRangeItemHeight / topIndexAndBoundary.2
|
||||
var convertedTopBoundary: CGFloat
|
||||
if topIndexAndBoundary.1 < self.insets.top {
|
||||
convertedTopBoundary = (topIndexAndBoundary.1 - self.insets.top) * averageRangeItemHeight / topIndexAndBoundary.2
|
||||
} else {
|
||||
convertedTopBoundary = topIndexAndBoundary.1
|
||||
convertedTopBoundary = topIndexAndBoundary.1 - self.insets.top
|
||||
}
|
||||
convertedTopBoundary -= upperItemsHeight
|
||||
|
||||
let approximateOffset = -convertedTopBoundary
|
||||
|
||||
var convertedBottomBoundary: CGFloat = 0.0
|
||||
if bottomIndexAndBoundary.1 > self.visibleSize.height {
|
||||
convertedBottomBoundary = (bottomIndexAndBoundary.1 - self.visibleSize.height) * averageRangeItemHeight / bottomIndexAndBoundary.2
|
||||
if bottomIndexAndBoundary.1 > self.visibleSize.height - self.insets.bottom {
|
||||
convertedBottomBoundary = ((self.visibleSize.height - self.insets.bottom) - bottomIndexAndBoundary.1) * averageRangeItemHeight / bottomIndexAndBoundary.2
|
||||
} else {
|
||||
convertedBottomBoundary = (self.visibleSize.height - self.insets.bottom) - bottomIndexAndBoundary.1
|
||||
}
|
||||
convertedBottomBoundary += convertedTopBoundary + CGFloat(rangeItemCount) * averageRangeItemHeight
|
||||
convertedBottomBoundary += CGFloat(bottomIndexAndBoundary.0 + 1) * averageRangeItemHeight
|
||||
|
||||
let approximateFirstItemOffset = convertedTopBoundary - upperItemsHeight
|
||||
let approximateLastItemOffset = convertedBottomBoundary
|
||||
let approximateVisibleHeight = max(0.0, convertedBottomBoundary - approximateOffset)
|
||||
|
||||
let approximateScrollingProgress = approximateOffset / (approximateContentHeight - approximateVisibleHeight)
|
||||
/*#if targetEnvironment(simulator)
|
||||
print("approximateOffset = \(approximateOffset), convertedBottomBoundary = \(convertedBottomBoundary) / \(vanillaBoundary), approximateScrollingProgress = \(approximateScrollingProgress)")
|
||||
#endif*/
|
||||
|
||||
let approximateOffset = -approximateFirstItemOffset + self.insets.top
|
||||
let approximateBottomOffset = -approximateLastItemOffset + self.insets.top
|
||||
let indicatorInsets: CGFloat = 3.0
|
||||
let minIndicatorContentHeight: CGFloat = 12.0
|
||||
let minIndicatorHeight: CGFloat = 6.0
|
||||
|
||||
//print("convertedTopBoundary = \(convertedTopBoundary), topIndexAndBoundary.1 = \(topIndexAndBoundary.1), upperItemsHeight = \(upperItemsHeight), approximateOffset = \(approximateOffset), approximateBottomOffset = \(approximateBottomOffset)")
|
||||
let visibleHeightWithoutIndicatorInsets = self.visibleSize.height - self.scrollIndicatorInsets.top - self.scrollIndicatorInsets.bottom - indicatorInsets * 2.0
|
||||
let indicatorHeight: CGFloat = max(minIndicatorContentHeight, floor(visibleHeightWithoutIndicatorInsets * (self.visibleSize.height - self.insets.top - self.insets.bottom) / approximateContentHeight))
|
||||
|
||||
let visibleHeightWithoutInsets = self.visibleSize.height - self.insets.top - self.insets.bottom
|
||||
let visibleHeightWithoutIndicatorInsets = visibleHeightWithoutInsets - indicatorInsets * 2.0
|
||||
let upperBound = self.scrollIndicatorInsets.top + indicatorInsets
|
||||
let lowerBound = self.visibleSize.height - self.scrollIndicatorInsets.bottom - indicatorInsets - indicatorHeight
|
||||
|
||||
// visibleHeightWithoutIndicatorInsets -> approximateContentHeight
|
||||
// x -> approximateOffset
|
||||
// x = visibleHeightWithoutIndicatorInsets * approximateOffset / approximateContentHeight
|
||||
let indicatorOffset = ceilToScreenPixels(upperBound * (1.0 - approximateScrollingProgress) + lowerBound * approximateScrollingProgress)
|
||||
|
||||
// visibleHeightWithoutIndicatorInsets -> approximateContentHeight
|
||||
// x -> visibleHeightWithoutInsets
|
||||
|
||||
let indicatorOffset = ceilToScreenPixels(visibleHeightWithoutIndicatorInsets * approximateOffset / approximateContentHeight)
|
||||
let approximateIndicatorHeight = ceilToScreenPixels(visibleHeightWithoutIndicatorInsets * visibleHeightWithoutInsets / approximateContentHeight)
|
||||
|
||||
let minHeight: CGFloat = 6.0
|
||||
let indicatorHeight = max(minHeight, approximateIndicatorHeight)
|
||||
|
||||
var indicatorFrame = CGRect(origin: CGPoint(x: self.rotated ? indicatorInsets : (self.visibleSize.width - 3.0 - indicatorInsets), y: self.scrollIndicatorInsets.top + indicatorInsets + indicatorOffset), size: CGSize(width: 3.0, height: indicatorHeight))
|
||||
var indicatorFrame = CGRect(origin: CGPoint(x: self.rotated ? indicatorInsets : (self.visibleSize.width - 3.0 - indicatorInsets), y: indicatorOffset), size: CGSize(width: 3.0, height: indicatorHeight))
|
||||
if indicatorFrame.minY < self.scrollIndicatorInsets.top + indicatorInsets {
|
||||
indicatorFrame.size.height -= self.scrollIndicatorInsets.top + indicatorInsets - indicatorFrame.minY
|
||||
indicatorFrame.origin.y = self.scrollIndicatorInsets.top + indicatorInsets
|
||||
indicatorFrame.size.height = max(minHeight, indicatorFrame.height)
|
||||
indicatorFrame.size.height = max(minIndicatorHeight, indicatorFrame.height)
|
||||
}
|
||||
if verticalScrollIndicator.isHidden {
|
||||
verticalScrollIndicator.isHidden = false
|
||||
if indicatorFrame.maxY > self.visibleSize.height - (self.scrollIndicatorInsets.bottom + indicatorInsets) {
|
||||
indicatorFrame.size.height -= indicatorFrame.maxY - (self.visibleSize.height - (self.scrollIndicatorInsets.bottom + indicatorInsets))
|
||||
indicatorFrame.size.height = max(minIndicatorHeight, indicatorFrame.height)
|
||||
indicatorFrame.origin.y = self.visibleSize.height - (self.scrollIndicatorInsets.bottom + indicatorInsets) - indicatorFrame.height
|
||||
}
|
||||
|
||||
if indicatorHeight >= visibleHeightWithoutIndicatorInsets {
|
||||
verticalScrollIndicator.isHidden = true
|
||||
verticalScrollIndicator.frame = indicatorFrame
|
||||
} else {
|
||||
verticalScrollIndicator.frame = indicatorFrame
|
||||
if verticalScrollIndicator.isHidden {
|
||||
verticalScrollIndicator.isHidden = false
|
||||
verticalScrollIndicator.frame = indicatorFrame
|
||||
} else {
|
||||
verticalScrollIndicator.frame = indicatorFrame
|
||||
}
|
||||
}
|
||||
} else {
|
||||
verticalScrollIndicator.isHidden = true
|
||||
}
|
||||
/*let size = self.visibleSize.height
|
||||
let range = computeVerticalScrollRange()
|
||||
let extent = computeVerticalScrollExtent()
|
||||
let mOffset = computeVerticalScrollOffset()
|
||||
|
||||
let thickness: CGFloat = 3.0
|
||||
var length = round(size * extent / range)
|
||||
var offset = round((size - length) * mOffset / (range - extent))
|
||||
|
||||
let minLength = thickness * 2.0
|
||||
if length < minLength {
|
||||
length = minLength
|
||||
}
|
||||
if offset + length > size {
|
||||
offset = size - length
|
||||
}
|
||||
|
||||
let indicatorHeight: CGFloat = max(3.0, 30.0)
|
||||
verticalScrollIndicator.frame = CGRect(origin: CGPoint(x: self.visibleSize.width - 3.0 - indicatorInsets, y: self.insets.top + offset), size: CGSize(width: 3.0, height: length))*/
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -39,12 +39,31 @@ final class PresentationContext {
|
||||
return self.view != nil && self.layout != nil
|
||||
}
|
||||
|
||||
private(set) var controllers: [ViewController] = []
|
||||
private(set) var controllers: [(ViewController, PresentationSurfaceLevel)] = []
|
||||
|
||||
private var presentationDisposables = DisposableSet()
|
||||
|
||||
var topLevelSubview: UIView?
|
||||
|
||||
private func topLevelSubview(for level: PresentationSurfaceLevel) -> UIView? {
|
||||
var topController: ViewController?
|
||||
for (controller, controllerLevel) in self.controllers.reversed() {
|
||||
if !controller.isViewLoaded || controller.view.superview == nil {
|
||||
continue
|
||||
}
|
||||
if controllerLevel.rawValue > level.rawValue {
|
||||
topController = controller
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
if let topController = topController {
|
||||
return topController.view
|
||||
} else {
|
||||
return topLevelSubview
|
||||
}
|
||||
}
|
||||
|
||||
private var nextBlockInteractionToken = 0
|
||||
private var blockInteractionTokens = Set<Int>()
|
||||
|
||||
@ -67,7 +86,7 @@ final class PresentationContext {
|
||||
}
|
||||
}
|
||||
|
||||
public func present(_ controller: ViewController, on: PresentationSurfaceLevel, blockInteraction: Bool = false, completion: @escaping () -> Void) {
|
||||
public func present(_ controller: ViewController, on level: PresentationSurfaceLevel, blockInteraction: Bool = false, completion: @escaping () -> Void) {
|
||||
let controllerReady = controller.ready.get()
|
||||
|> filter({ $0 })
|
||||
|> take(1)
|
||||
@ -103,11 +122,17 @@ final class PresentationContext {
|
||||
if let blockInteractionToken = blockInteractionToken {
|
||||
strongSelf.removeBlockInteraction(blockInteractionToken)
|
||||
}
|
||||
if strongSelf.controllers.contains(where: { $0 === controller }) {
|
||||
if strongSelf.controllers.contains(where: { $0.0 === controller }) {
|
||||
return
|
||||
}
|
||||
|
||||
strongSelf.controllers.append(controller)
|
||||
var insertIndex: Int?
|
||||
for i in (0 ..< strongSelf.controllers.count).reversed() {
|
||||
if strongSelf.controllers[i].1.rawValue > level.rawValue {
|
||||
insertIndex = i
|
||||
}
|
||||
}
|
||||
strongSelf.controllers.insert((controller, level), at: insertIndex ?? strongSelf.controllers.count)
|
||||
if let view = strongSelf.view, let layout = strongSelf.layout {
|
||||
controller.navigation_setDismiss({ [weak controller] in
|
||||
if let strongSelf = self, let controller = controller {
|
||||
@ -117,14 +142,14 @@ final class PresentationContext {
|
||||
controller.setIgnoreAppearanceMethodInvocations(true)
|
||||
if layout != initialLayout {
|
||||
controller.view.frame = CGRect(origin: CGPoint(), size: layout.size)
|
||||
if let topLevelSubview = strongSelf.topLevelSubview {
|
||||
if let topLevelSubview = strongSelf.topLevelSubview(for: level) {
|
||||
view.insertSubview(controller.view, belowSubview: topLevelSubview)
|
||||
} else {
|
||||
view.addSubview(controller.view)
|
||||
}
|
||||
controller.containerLayoutUpdated(layout, transition: .immediate)
|
||||
} else {
|
||||
if let topLevelSubview = strongSelf.topLevelSubview {
|
||||
if let topLevelSubview = strongSelf.topLevelSubview(for: level) {
|
||||
view.insertSubview(controller.view, belowSubview: topLevelSubview)
|
||||
} else {
|
||||
view.addSubview(controller.view)
|
||||
@ -138,7 +163,7 @@ final class PresentationContext {
|
||||
}
|
||||
}))
|
||||
} else {
|
||||
self.controllers.append(controller)
|
||||
self.controllers.append((controller, level))
|
||||
}
|
||||
}
|
||||
|
||||
@ -147,7 +172,7 @@ final class PresentationContext {
|
||||
}
|
||||
|
||||
private func dismiss(_ controller: ViewController) {
|
||||
if let index = self.controllers.index(where: { $0 === controller }) {
|
||||
if let index = self.controllers.index(where: { $0.0 === controller }) {
|
||||
self.controllers.remove(at: index)
|
||||
controller.viewWillDisappear(false)
|
||||
controller.view.removeFromSuperview()
|
||||
@ -162,7 +187,7 @@ final class PresentationContext {
|
||||
if wasReady != self.ready {
|
||||
self.readyChanged(wasReady: wasReady)
|
||||
} else if self.ready {
|
||||
for controller in self.controllers {
|
||||
for (controller, _) in self.controllers {
|
||||
controller.containerLayoutUpdated(layout, transition: transition)
|
||||
}
|
||||
}
|
||||
@ -178,7 +203,7 @@ final class PresentationContext {
|
||||
|
||||
private func addViews() {
|
||||
if let view = self.view, let layout = self.layout {
|
||||
for controller in self.controllers {
|
||||
for (controller, _) in self.controllers {
|
||||
controller.viewWillAppear(false)
|
||||
if let topLevelSubview = self.topLevelSubview {
|
||||
view.insertSubview(controller.view, belowSubview: topLevelSubview)
|
||||
@ -193,7 +218,7 @@ final class PresentationContext {
|
||||
}
|
||||
|
||||
private func removeViews() {
|
||||
for controller in self.controllers {
|
||||
for (controller, _) in self.controllers {
|
||||
controller.viewWillDisappear(false)
|
||||
controller.view.removeFromSuperview()
|
||||
controller.viewDidDisappear(false)
|
||||
@ -201,7 +226,7 @@ final class PresentationContext {
|
||||
}
|
||||
|
||||
func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
|
||||
for controller in self.controllers.reversed() {
|
||||
for (controller, _) in self.controllers.reversed() {
|
||||
if controller.isViewLoaded {
|
||||
if let result = controller.view.hitTest(point, with: event) {
|
||||
return result
|
||||
@ -214,7 +239,7 @@ final class PresentationContext {
|
||||
func combinedSupportedOrientations() -> ViewControllerSupportedOrientations {
|
||||
var mask = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .all)
|
||||
|
||||
for controller in self.controllers {
|
||||
for (controller, _) in self.controllers {
|
||||
mask = mask.intersection(controller.supportedOrientations)
|
||||
}
|
||||
|
||||
@ -224,7 +249,7 @@ final class PresentationContext {
|
||||
func combinedDeferScreenEdgeGestures() -> UIRectEdge {
|
||||
var edges: UIRectEdge = []
|
||||
|
||||
for controller in self.controllers {
|
||||
for (controller, _) in self.controllers {
|
||||
edges = edges.union(controller.deferScreenEdgeGestures)
|
||||
}
|
||||
|
||||
|
||||
@ -992,7 +992,7 @@ public class Window1 {
|
||||
}
|
||||
|
||||
public func forEachViewController(_ f: (ViewController) -> Bool) {
|
||||
for controller in self.presentationContext.controllers {
|
||||
for (controller, _) in self.presentationContext.controllers {
|
||||
if !f(controller) {
|
||||
break
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user