Various fixes

This commit is contained in:
Peter Iakovlev 2018-11-30 14:26:48 +04:00
parent bb44454500
commit 7a58ac1bfc
6 changed files with 494 additions and 352 deletions

View File

@ -10,7 +10,7 @@ public protocol GridSection {
public protocol GridItem {
var section: GridSection? { get }
func node(layout: GridNodeLayout) -> GridItemNode
func node(layout: GridNodeLayout, synchronousLoad: Bool) -> GridItemNode
func update(node: GridItemNode)
var aspectRatio: CGFloat { get }
var fillsRowWithHeight: CGFloat? { get }

View File

@ -97,8 +97,9 @@ public struct GridNodeTransaction {
public let stationaryItems: GridNodeStationaryItems
public let updateFirstIndexInSectionOffset: Int?
public let updateOpaqueState: Any?
public let synchronousLoads: Bool
public init(deleteItems: [Int], insertItems: [GridNodeInsertItem], updateItems: [GridNodeUpdateItem], scrollToItem: GridNodeScrollToItem?, updateLayout: GridNodeUpdateLayout?, itemTransition: ContainedViewLayoutTransition, stationaryItems: GridNodeStationaryItems, updateFirstIndexInSectionOffset: Int?, updateOpaqueState: Any? = nil) {
public init(deleteItems: [Int], insertItems: [GridNodeInsertItem], updateItems: [GridNodeUpdateItem], scrollToItem: GridNodeScrollToItem?, updateLayout: GridNodeUpdateLayout?, itemTransition: ContainedViewLayoutTransition, stationaryItems: GridNodeStationaryItems, updateFirstIndexInSectionOffset: Int?, updateOpaqueState: Any? = nil, synchronousLoads: Bool = false) {
self.deleteItems = deleteItems
self.insertItems = insertItems
self.updateItems = updateItems
@ -108,6 +109,7 @@ public struct GridNodeTransaction {
self.stationaryItems = stationaryItems
self.updateFirstIndexInSectionOffset = updateFirstIndexInSectionOffset
self.updateOpaqueState = updateOpaqueState
self.synchronousLoads = synchronousLoads
}
}
@ -346,7 +348,7 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate {
generatedScrollToItem = nil
}
self.applyPresentaionLayoutTransition(self.generatePresentationLayoutTransition(stationaryItems: transaction.stationaryItems, layoutTransactionOffset: layoutTransactionOffset, scrollToItem: generatedScrollToItem), removedNodes: removedNodes, updateLayoutTransition: updateLayoutTransition, customScrollToItem: transaction.scrollToItem != nil, itemTransition: transaction.itemTransition, updatingLayout: transaction.updateLayout != nil, completion: completion)
self.applyPresentaionLayoutTransition(self.generatePresentationLayoutTransition(stationaryItems: transaction.stationaryItems, layoutTransactionOffset: layoutTransactionOffset, scrollToItem: generatedScrollToItem), removedNodes: removedNodes, updateLayoutTransition: updateLayoutTransition, customScrollToItem: transaction.scrollToItem != nil, itemTransition: transaction.itemTransition, synchronousLoads: transaction.synchronousLoads, updatingLayout: transaction.updateLayout != nil, completion: completion)
}
public func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
@ -368,7 +370,7 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate {
public func scrollViewDidScroll(_ scrollView: UIScrollView) {
if !self.applyingContentOffset {
self.applyPresentaionLayoutTransition(self.generatePresentationLayoutTransition(layoutTransactionOffset: 0.0), removedNodes: [], updateLayoutTransition: nil, customScrollToItem: false, itemTransition: .immediate, updatingLayout: false, completion: { _ in })
self.applyPresentaionLayoutTransition(self.generatePresentationLayoutTransition(layoutTransactionOffset: 0.0), removedNodes: [], updateLayoutTransition: nil, customScrollToItem: false, itemTransition: .immediate, synchronousLoads: false, updatingLayout: false, completion: { _ in })
}
}
@ -744,7 +746,7 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate {
return lowestHeaderNode
}
private func applyPresentaionLayoutTransition(_ presentationLayoutTransition: GridNodePresentationLayoutTransition, removedNodes: [GridItemNode], updateLayoutTransition: ContainedViewLayoutTransition?, customScrollToItem: Bool, itemTransition: ContainedViewLayoutTransition, updatingLayout: Bool, completion: (GridNodeDisplayedItemRange) -> Void) {
private func applyPresentaionLayoutTransition(_ presentationLayoutTransition: GridNodePresentationLayoutTransition, removedNodes: [GridItemNode], updateLayoutTransition: ContainedViewLayoutTransition?, customScrollToItem: Bool, itemTransition: ContainedViewLayoutTransition, synchronousLoads: Bool, updatingLayout: Bool, completion: (GridNodeDisplayedItemRange) -> Void) {
let boundsTransition: ContainedViewLayoutTransition = updateLayoutTransition ?? .immediate
var previousItemFrames: [WrappedGridItemNode: CGRect]?
@ -806,7 +808,7 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate {
itemNode.frame = item.frame
}
} else {
let itemNode = self.items[item.index].node(layout: presentationLayoutTransition.layout.layout)
let itemNode = self.items[item.index].node(layout: presentationLayoutTransition.layout.layout, synchronousLoad: synchronousLoads)
itemNode.frame = item.frame
self.addItemNode(index: item.index, itemNode: itemNode, lowestSectionNode: lowestSectionNode)
}

View File

@ -116,6 +116,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel
private final let scroller: ListViewScroller
private final var visibleSize: CGSize = CGSize()
public private(set) final var insets = UIEdgeInsets()
public private(set) final var scrollIndicatorInsets = UIEdgeInsets()
private final var ensureTopInsetForOverlayHighlightedItems: CGFloat?
private final var lastContentOffset: CGPoint = CGPoint()
private final var lastContentOffsetTimestamp: CFAbsoluteTime = 0.0
@ -125,6 +126,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel
private final var needsAnimations = false
public final var dynamicBounceEnabled = true
public final var rotated = false
private final var invisibleInset: CGFloat = 500.0
public var preloadPages: Bool = true {
@ -177,6 +179,8 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel
if let fillColor = self.verticalScrollIndicatorColor {
if self.verticalScrollIndicator == nil {
let verticalScrollIndicator = ASImageNode()
verticalScrollIndicator.isUserInteractionEnabled = false
verticalScrollIndicator.alpha = 0.0
verticalScrollIndicator.image = generateStretchableFilledCircleImage(diameter: 3.0, color: fillColor)
self.verticalScrollIndicator = verticalScrollIndicator
self.addSubnode(verticalScrollIndicator)
@ -233,6 +237,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel
private var selectionTouchDelayTimer: Foundation.Timer?
private var selectionLongTapDelayTimer: Foundation.Timer?
private var flashNodesDelayTimer: Foundation.Timer?
private var flashScrollIndicatorTimer: Foundation.Timer?
private var highlightedItemIndex: Int?
private var reorderNode: ListViewReorderingItemNode?
private var reorderFeedback: HapticFeedback?
@ -383,7 +388,11 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel
}
let reorderNode = ListViewReorderingItemNode(itemNode: itemNode, initialLocation: itemNode.frame.origin)
self.reorderNode = reorderNode
if let verticalScrollIndicator = self.verticalScrollIndicator {
self.insertSubnode(reorderNode, belowSubnode: verticalScrollIndicator)
} else {
self.addSubnode(reorderNode)
}
itemNode.isHidden = true
}
@ -492,6 +501,31 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel
}
}
private func resetScrollIndicatorFlashTimer(start: Bool) {
if let flashScrollIndicatorTimer = self.flashScrollIndicatorTimer {
flashScrollIndicatorTimer.invalidate()
self.flashScrollIndicatorTimer = nil
}
if start {
let timer = Timer(timeInterval: 0.1, target: ListViewTimerProxy { [weak self] in
if let strongSelf = self {
if let flashScrollIndicatorTimer = strongSelf.flashScrollIndicatorTimer {
flashScrollIndicatorTimer.invalidate()
strongSelf.flashScrollIndicatorTimer = nil
strongSelf.verticalScrollIndicator?.alpha = 0.0
strongSelf.verticalScrollIndicator?.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3)
}
}
}, selector: #selector(ListViewTimerProxy.timerEvent), userInfo: nil, repeats: false)
self.flashScrollIndicatorTimer = timer
RunLoop.main.add(timer, forMode: RunLoopMode.commonModes)
} else {
self.verticalScrollIndicator?.layer.removeAnimation(forKey: "opacity")
self.verticalScrollIndicator?.alpha = 1.0
}
}
private func headerItemsAreFlashing() -> Bool {
//print("\(self.scroller.isDragging) || (\(self.scroller.isDecelerating) && \(self.isDeceleratingAfterTracking)) || \(self.flashNodesDelayTimer != nil)")
return self.scroller.isDragging || (self.isDeceleratingAfterTracking) || self.flashNodesDelayTimer != nil
@ -508,6 +542,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel
self.lastContentOffsetTimestamp = 0.0
self.resetHeaderItemsFlashTimer(start: false)
self.updateHeaderItemsFlashing(animated: true)
self.resetScrollIndicatorFlashTimer(start: false)
if self.snapToBottomInsetUntilFirstInteraction {
self.snapToBottomInsetUntilFirstInteraction = false
@ -521,10 +556,12 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel
self.lastContentOffsetTimestamp = CACurrentMediaTime()
self.isDeceleratingAfterTracking = true
self.updateHeaderItemsFlashing(animated: true)
self.resetScrollIndicatorFlashTimer(start: false)
} else {
self.isDeceleratingAfterTracking = false
self.resetHeaderItemsFlashTimer(start: true)
self.updateHeaderItemsFlashing(animated: true)
self.resetScrollIndicatorFlashTimer(start: true)
self.lastContentOffsetTimestamp = 0.0
self.didEndScrolling?()
@ -536,6 +573,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel
self.isDeceleratingAfterTracking = false
self.resetHeaderItemsFlashTimer(start: true)
self.updateHeaderItemsFlashing(animated: true)
self.resetScrollIndicatorFlashTimer(start: true)
self.didEndScrolling?()
}
@ -1006,6 +1044,9 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel
if itemNode.isHighlightedInOverlay {
lowestOverlayNode = itemNode
itemNode.view.superview?.bringSubview(toFront: itemNode.view)
if let verticalScrollIndicator = self.verticalScrollIndicator {
verticalScrollIndicator.view.superview?.bringSubview(toFront: verticalScrollIndicator.view)
}
}
}
@ -1030,6 +1071,9 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel
for (_, headerNode) in self.itemHeaderNodes {
self.view.bringSubview(toFront: headerNode.view)
}
if let verticalScrollIndicator = self.verticalScrollIndicator {
verticalScrollIndicator.view.superview?.bringSubview(toFront: verticalScrollIndicator.view)
}
}
}
@ -1121,7 +1165,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel
DispatchQueue.global().async(execute: f)
}
private func nodeForItem(synchronous: Bool, item: ListViewItem, previousNode: QueueLocalObject<ListViewItemNode>?, index: Int, previousItem: ListViewItem?, nextItem: ListViewItem?, params: ListViewItemLayoutParams, updateAnimation: ListViewItemUpdateAnimation, completion: @escaping (QueueLocalObject<ListViewItemNode>, ListViewItemNodeLayout, @escaping () -> (Signal<Void, NoError>?, () -> Void)) -> Void) {
private func nodeForItem(synchronous: Bool, synchronousLoads: Bool, item: ListViewItem, previousNode: QueueLocalObject<ListViewItemNode>?, index: Int, previousItem: ListViewItem?, nextItem: ListViewItem?, params: ListViewItemLayoutParams, updateAnimation: ListViewItemUpdateAnimation, completion: @escaping (QueueLocalObject<ListViewItemNode>, ListViewItemNodeLayout, @escaping () -> (Signal<Void, NoError>?, () -> Void)) -> Void) {
if let previousNode = previousNode {
item.updateNode(async: { f in
if synchronous {
@ -1170,8 +1214,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel
} else {
self.async(f)
}
}, params: params, previousItem: previousItem, nextItem: nextItem, completion: { itemNode, apply in
//assert(Queue.mainQueue().isCurrent())
}, params: params, synchronousLoads: synchronousLoads, previousItem: previousItem, nextItem: nextItem, completion: { itemNode, apply in
itemNode.index = index
completion(QueueLocalObject(queue: Queue.mainQueue(), generate: { return itemNode }), ListViewItemNodeLayout(contentSize: itemNode.contentSize, insets: itemNode.insets), apply)
})
@ -1216,6 +1259,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel
if let updateSizeAndInsets = updateSizeAndInsets , (self.items.count == 0 || (updateSizeAndInsets.size == self.visibleSize && updateSizeAndInsets.insets == self.insets)) {
self.visibleSize = updateSizeAndInsets.size
self.insets = updateSizeAndInsets.insets
self.scrollIndicatorInsets = updateSizeAndInsets.scrollIndicatorInsets ?? self.insets
self.ensureTopInsetForOverlayHighlightedItems = updateSizeAndInsets.ensureTopInsetForOverlayHighlightedItems
let wasIgnoringScrollingEvents = self.ignoreScrollingEvents
@ -1438,7 +1482,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel
print("deleteAndInsertItemsTransaction prepare \((CACurrentMediaTime() - startTime) * 1000.0) ms")
}
self.fillMissingNodes(synchronous: options.contains(.Synchronous), animated: animated, inputAnimatedInsertIndices: animated ? insertedIndexSet : Set<Int>(), insertDirectionHints: insertDirectionHints, inputState: state, inputPreviousNodes: previousNodes, inputOperations: operations, inputCompletion: { updatedState, operations in
self.fillMissingNodes(synchronous: options.contains(.Synchronous), synchronousLoads: options.contains(.PreferSynchronousResourceLoading), animated: animated, inputAnimatedInsertIndices: animated ? insertedIndexSet : Set<Int>(), insertDirectionHints: insertDirectionHints, inputState: state, inputPreviousNodes: previousNodes, inputOperations: operations, inputCompletion: { updatedState, operations in
if self.debugInfo {
print("fillMissingNodes completion \((CACurrentMediaTime() - startTime) * 1000.0) ms")
@ -1461,7 +1505,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel
updateIndices.subtract(explicitelyUpdateIndices)
self.updateNodes(synchronous: options.contains(.Synchronous), animated: animated, updateIndicesAndItems: updateIndicesAndItems, inputState: updatedState, previousNodes: previousNodes, inputOperations: operations, completion: { updatedState, operations in
self.updateNodes(synchronous: options.contains(.Synchronous), synchronousLoads: options.contains(.PreferSynchronousResourceLoading), animated: animated, updateIndicesAndItems: updateIndicesAndItems, inputState: updatedState, previousNodes: previousNodes, inputOperations: operations, completion: { updatedState, operations in
self.updateAdjacent(synchronous: options.contains(.Synchronous), animated: animated, state: updatedState, updateAdjacentItemsIndices: updateIndices, operations: operations, completion: { state, operations in
var updatedState = state
var updatedOperations = operations
@ -1614,7 +1658,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel
}
}
private func fillMissingNodes(synchronous: Bool, animated: Bool, inputAnimatedInsertIndices: Set<Int>, insertDirectionHints: [Int: ListViewItemOperationDirectionHint], inputState: ListViewState, inputPreviousNodes: [Int: QueueLocalObject<ListViewItemNode>], inputOperations: [ListViewStateOperation], inputCompletion: @escaping (ListViewState, [ListViewStateOperation]) -> Void) {
private func fillMissingNodes(synchronous: Bool, synchronousLoads: Bool, animated: Bool, inputAnimatedInsertIndices: Set<Int>, insertDirectionHints: [Int: ListViewItemOperationDirectionHint], inputState: ListViewState, inputPreviousNodes: [Int: QueueLocalObject<ListViewItemNode>], inputOperations: [ListViewStateOperation], inputCompletion: @escaping (ListViewState, [ListViewStateOperation]) -> Void) {
let animatedInsertIndices = inputAnimatedInsertIndices
var state = inputState
var previousNodes = inputPreviousNodes
@ -1649,7 +1693,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel
let index = insertionItemIndexAndDirection.0
let threadId = pthread_self()
var tailRecurse = false
self.nodeForItem(synchronous: synchronous, item: self.items[index], previousNode: previousNodes[index], index: index, previousItem: index == 0 ? nil : self.items[index - 1], nextItem: self.items.count == index + 1 ? nil : self.items[index + 1], params: ListViewItemLayoutParams(width: state.visibleSize.width, leftInset: state.insets.left, rightInset: state.insets.right), updateAnimation: updateAnimation, completion: { (node, layout, apply) in
self.nodeForItem(synchronous: synchronous, synchronousLoads: synchronousLoads, item: self.items[index], previousNode: previousNodes[index], index: index, previousItem: index == 0 ? nil : self.items[index - 1], nextItem: self.items.count == index + 1 ? nil : self.items[index + 1], params: ListViewItemLayoutParams(width: state.visibleSize.width, leftInset: state.insets.left, rightInset: state.insets.right), updateAnimation: updateAnimation, completion: { (node, layout, apply) in
if pthread_equal(pthread_self(), threadId) != 0 && !tailRecurse {
tailRecurse = true
@ -1658,7 +1702,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel
var updatedState = state
var updatedOperations = operations
updatedState.insertNode(index, node: node, layout: layout, apply: apply, offsetDirection: insertionItemIndexAndDirection.1, animated: animated && animatedInsertIndices.contains(index), operations: &updatedOperations, itemCount: self.items.count)
self.fillMissingNodes(synchronous: synchronous, animated: animated, inputAnimatedInsertIndices: animatedInsertIndices, insertDirectionHints: insertDirectionHints, inputState: updatedState, inputPreviousNodes: previousNodes, inputOperations: updatedOperations, inputCompletion: completion)
self.fillMissingNodes(synchronous: synchronous, synchronousLoads: synchronousLoads, animated: animated, inputAnimatedInsertIndices: animatedInsertIndices, insertDirectionHints: insertDirectionHints, inputState: updatedState, inputPreviousNodes: previousNodes, inputOperations: updatedOperations, inputCompletion: completion)
}
})
if !tailRecurse {
@ -1673,7 +1717,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel
}
}
private func updateNodes(synchronous: Bool, animated: Bool, updateIndicesAndItems: [ListViewUpdateItem], inputState: ListViewState, previousNodes: [Int: QueueLocalObject<ListViewItemNode>], inputOperations: [ListViewStateOperation], completion: @escaping (ListViewState, [ListViewStateOperation]) -> Void) {
private func updateNodes(synchronous: Bool, synchronousLoads: Bool, animated: Bool, updateIndicesAndItems: [ListViewUpdateItem], inputState: ListViewState, previousNodes: [Int: QueueLocalObject<ListViewItemNode>], inputOperations: [ListViewStateOperation], completion: @escaping (ListViewState, [ListViewStateOperation]) -> Void) {
var state = inputState
var operations = inputOperations
var updateIndicesAndItems = updateIndicesAndItems
@ -1685,11 +1729,11 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel
} else {
let updateItem = updateIndicesAndItems[0]
if let previousNode = previousNodes[updateItem.index] {
self.nodeForItem(synchronous: synchronous, item: updateItem.item, previousNode: previousNode, index: updateItem.index, previousItem: updateItem.index == 0 ? nil : self.items[updateItem.index - 1], nextItem: updateItem.index == (self.items.count - 1) ? nil : self.items[updateItem.index + 1], params: ListViewItemLayoutParams(width: state.visibleSize.width, leftInset: state.insets.left, rightInset: state.insets.right), updateAnimation: animated ? .System(duration: insertionAnimationDuration) : .None, completion: { _, layout, apply in
self.nodeForItem(synchronous: synchronous, synchronousLoads: synchronousLoads, item: updateItem.item, previousNode: previousNode, index: updateItem.index, previousItem: updateItem.index == 0 ? nil : self.items[updateItem.index - 1], nextItem: updateItem.index == (self.items.count - 1) ? nil : self.items[updateItem.index + 1], params: ListViewItemLayoutParams(width: state.visibleSize.width, leftInset: state.insets.left, rightInset: state.insets.right), updateAnimation: animated ? .System(duration: insertionAnimationDuration) : .None, completion: { _, layout, apply in
state.updateNodeAtItemIndex(updateItem.index, layout: layout, direction: updateItem.directionHint, animation: animated ? .System(duration: insertionAnimationDuration) : .None, apply: apply, operations: &operations)
updateIndicesAndItems.remove(at: 0)
self.updateNodes(synchronous: synchronous, animated: animated, updateIndicesAndItems: updateIndicesAndItems, inputState: state, previousNodes: previousNodes, inputOperations: operations, completion: completion)
self.updateNodes(synchronous: synchronous, synchronousLoads: synchronousLoads, animated: animated, updateIndicesAndItems: updateIndicesAndItems, inputState: state, previousNodes: previousNodes, inputOperations: operations, completion: completion)
})
break
} else {
@ -2008,6 +2052,8 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel
self.insertSubnode(node, belowSubnode: itemNode)
} else if let lowestNodeToInsertBelow = lowestNodeToInsertBelow {
self.insertSubnode(node, belowSubnode: lowestNodeToInsertBelow)
} else if let verticalScrollIndicator = self.verticalScrollIndicator {
self.insertSubnode(node, belowSubnode: verticalScrollIndicator)
} else {
self.addSubnode(node)
}
@ -2023,6 +2069,8 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel
self.insertSubnode(node, belowSubnode: itemNode)
} else if let lowestNodeToInsertBelow = lowestNodeToInsertBelow {
self.insertSubnode(node, belowSubnode: lowestNodeToInsertBelow)
} else if let verticalScrollIndicator = self.verticalScrollIndicator {
self.insertSubnode(node, belowSubnode: verticalScrollIndicator)
} else {
self.addSubnode(node)
}
@ -2049,8 +2097,12 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel
} else {
referenceNode.index = nil
self.insertNodeAtIndex(animated: false, animateAlpha: false, forceAnimateInsertion: false, previousFrame: nil, nodeIndex: index, offsetDirection: offsetDirection, node: referenceNode, layout: previousLayout, apply: { return (nil, {}) }, timestamp: timestamp, listInsets: listInsets)
if let verticalScrollIndicator = self.verticalScrollIndicator {
self.insertSubnode(referenceNode, belowSubnode: verticalScrollIndicator)
} else {
self.addSubnode(referenceNode)
}
}
} else {
assertionFailure()
}
@ -2178,6 +2230,9 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel
if hadInserts, let reorderNode = self.reorderNode, reorderNode.supernode != nil {
self.view.bringSubview(toFront: reorderNode.view)
if let verticalScrollIndicator = self.verticalScrollIndicator {
verticalScrollIndicator.view.superview?.bringSubview(toFront: verticalScrollIndicator.view)
}
}
if self.debugInfo {
@ -2249,7 +2304,6 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel
self.stopScrolling()
}
self.insertNodesInBatches(nodes: [], completion: {
self.debugCheckMonotonity()
var sizeAndInsetsOffset: CGFloat = 0.0
@ -2273,6 +2327,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel
offsetFix += additionalScrollDistance
self.insets = updateSizeAndInsets.insets
self.scrollIndicatorInsets = updateSizeAndInsets.scrollIndicatorInsets ?? self.insets
self.ensureTopInsetForOverlayHighlightedItems = updateSizeAndInsets.ensureTopInsetForOverlayHighlightedItems
self.visibleSize = updateSizeAndInsets.size
@ -2474,6 +2529,8 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel
itemNode.frame = itemNode.frame.offsetBy(dx: 0.0, dy: offset)
if let lowestNodeToInsertBelow = lowestNodeToInsertBelow {
self.insertSubnode(itemNode, belowSubnode: lowestNodeToInsertBelow)
} else if let verticalScrollIndicator = self.verticalScrollIndicator {
self.insertSubnode(itemNode, belowSubnode: verticalScrollIndicator)
} else {
self.addSubnode(itemNode)
}
@ -2484,9 +2541,13 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel
if headerNode.supernode == nil {
headerNode.frame = headerNode.frame.offsetBy(dx: 0.0, dy: offset)
temporaryHeaderNodes.append(headerNode)
if let verticalScrollIndicator = self.verticalScrollIndicator {
self.insertSubnode(headerNode, belowSubnode: verticalScrollIndicator)
} else {
self.addSubnode(headerNode)
}
}
}
let animation: CABasicAnimation
switch scrollToItem.curve {
@ -2613,18 +2674,6 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel
}
}
completion()
}
})
}
private func insertNodesInBatches(nodes: [ASDisplayNode], completion: () -> Void) {
if nodes.count == 0 {
completion()
} else {
for node in nodes {
self.addSubnode(node)
}
completion()
}
}
@ -2722,7 +2771,11 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel
headerNode.updateLayoutInternal(size: headerFrame.size, leftInset: leftInset, rightInset: rightInset)
headerNode.updateInternalStickLocationDistanceFactor(stickLocationDistanceFactor, animated: false)
self.itemHeaderNodes[id] = headerNode
if let verticalScrollIndicator = self.verticalScrollIndicator {
self.insertSubnode(headerNode, belowSubnode: verticalScrollIndicator)
} else {
self.addSubnode(headerNode)
}
if animateInsertion {
headerNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
headerNode.layer.animateScale(from: 0.2, to: 1.0, duration: 0.3)
@ -2912,9 +2965,85 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel
}
}
if let verticalScrollIndicator = self.verticalScrollIndicator {
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 {
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 {
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 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 convertedTopBoundary: CGFloat
if topIndexAndBoundary.1 < 0.0 {
convertedTopBoundary = topIndexAndBoundary.1 * averageRangeItemHeight / topIndexAndBoundary.2
} else {
convertedTopBoundary = topIndexAndBoundary.1
}
var convertedBottomBoundary: CGFloat = 0.0
if bottomIndexAndBoundary.1 > self.visibleSize.height {
convertedBottomBoundary = (bottomIndexAndBoundary.1 - self.visibleSize.height) * averageRangeItemHeight / bottomIndexAndBoundary.2
}
convertedBottomBoundary += convertedTopBoundary + CGFloat(rangeItemCount) * averageRangeItemHeight
let approximateFirstItemOffset = convertedTopBoundary - upperItemsHeight
let approximateLastItemOffset = convertedBottomBoundary
let approximateOffset = -approximateFirstItemOffset + self.insets.top
let approximateBottomOffset = -approximateLastItemOffset + self.insets.top
let indicatorInsets: CGFloat = 3.0
if let verticalScrollIndicator = self.verticalScrollIndicator {
//print("convertedTopBoundary = \(convertedTopBoundary), topIndexAndBoundary.1 = \(topIndexAndBoundary.1), upperItemsHeight = \(upperItemsHeight), approximateOffset = \(approximateOffset), approximateBottomOffset = \(approximateBottomOffset)")
let visibleHeightWithoutInsets = self.visibleSize.height - self.insets.top - self.insets.bottom
let visibleHeightWithoutIndicatorInsets = visibleHeightWithoutInsets - indicatorInsets * 2.0
// visibleHeightWithoutIndicatorInsets -> approximateContentHeight
// x -> approximateOffset
// x = visibleHeightWithoutIndicatorInsets * approximateOffset / approximateContentHeight
// 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))
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)
}
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()
@ -2986,7 +3115,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel
let state = self.currentState()
self.async {
self.fillMissingNodes(synchronous: false, animated: false, inputAnimatedInsertIndices: [], insertDirectionHints: [:], inputState: state, inputPreviousNodes: [:], inputOperations: []) { state, operations in
self.fillMissingNodes(synchronous: false, synchronousLoads: false, animated: false, inputAnimatedInsertIndices: [], insertDirectionHints: [:], inputState: state, inputPreviousNodes: [:], inputOperations: []) { state, operations in
var updatedState = state
var updatedOperations = operations
updatedState.removeInvisibleNodes(&updatedOperations)
@ -3489,6 +3618,9 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel
if let itemHighlightOverlayBackground = self.itemHighlightOverlayBackground {
itemHighlightOverlayBackground.view.superview?.bringSubview(toFront: itemHighlightOverlayBackground.view)
}
if let verticalScrollIndicator = self.verticalScrollIndicator {
verticalScrollIndicator.view.superview?.bringSubview(toFront: verticalScrollIndicator.view)
}
}
private func reorderHeaderNodeToFront(_ headerNode: ListViewItemHeaderNode) {
@ -3496,5 +3628,8 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel
if let itemHighlightOverlayBackground = self.itemHighlightOverlayBackground {
itemHighlightOverlayBackground.view.superview?.bringSubview(toFront: itemHighlightOverlayBackground.view)
}
if let verticalScrollIndicator = self.verticalScrollIndicator {
verticalScrollIndicator.view.superview?.bringSubview(toFront: verticalScrollIndicator.view)
}
}
}

View File

@ -108,13 +108,15 @@ public struct ListViewDeleteAndInsertOptions: OptionSet {
public struct ListViewUpdateSizeAndInsets {
public let size: CGSize
public let insets: UIEdgeInsets
public let scrollIndicatorInsets: UIEdgeInsets?
public let duration: Double
public let curve: ListViewAnimationCurve
public let ensureTopInsetForOverlayHighlightedItems: CGFloat?
public init(size: CGSize, insets: UIEdgeInsets, duration: Double, curve: ListViewAnimationCurve, ensureTopInsetForOverlayHighlightedItems: CGFloat? = nil) {
public init(size: CGSize, insets: UIEdgeInsets, scrollIndicatorInsets: UIEdgeInsets? = nil, duration: Double, curve: ListViewAnimationCurve, ensureTopInsetForOverlayHighlightedItems: CGFloat? = nil) {
self.size = size
self.insets = insets
self.scrollIndicatorInsets = scrollIndicatorInsets
self.duration = duration
self.curve = curve
self.ensureTopInsetForOverlayHighlightedItems = ensureTopInsetForOverlayHighlightedItems

View File

@ -33,7 +33,7 @@ public struct ListViewItemConfigureNodeFlags: OptionSet {
}
public protocol ListViewItem {
func nodeConfiguredForParams(async: @escaping (@escaping () -> Void) -> Void, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal<Void, NoError>?, () -> Void)) -> Void)
func nodeConfiguredForParams(async: @escaping (@escaping () -> Void) -> Void, params: ListViewItemLayoutParams, synchronousLoads: Bool, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal<Void, NoError>?, () -> Void)) -> Void)
func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: @escaping () -> ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping () -> Void) -> Void)
var accessoryItem: ListViewAccessoryItem? { get }

View File

@ -32,6 +32,9 @@ public let UIScreenScale = UIScreen.main.scale
public func floorToScreenPixels(_ value: CGFloat) -> CGFloat {
return floor(value * UIScreenScale) / UIScreenScale
}
public func ceilToScreenPixels(_ value: CGFloat) -> CGFloat {
return ceil(value * UIScreenScale) / UIScreenScale
}
public let UIScreenPixel = 1.0 / UIScreenScale