node for item
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
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>?, (ListViewItemApply) -> Void)) -> Void) {
if let previousNode = previousNode {
// alreay initialized
// updateNode
item.updateNode(async: { f in
if synchronous {
f()
} else {
self.async(f)
}
}, node: {
assert(Queue.mainQueue().isCurrent())
return previousNode.syncWith({ $0 })!
}, params: params, previousItem: previousItem, nextItem: nextItem, animation: updateAnimation, completion: { (layout, apply) in
if Thread.isMainThread {
if synchronous {
completion(previousNode, layout, {
return (nil, { info in
assert(Queue.mainQueue().isCurrent())
previousNode.with({ $0.index = index })
apply(info)
})
})
} else {
self.async {
completion(previousNode, layout, {
return (nil, { info in
assert(Queue.mainQueue().isCurrent())
previousNode.with({ $0.index = index })
apply(info)
})
})
}
}
} else {
completion(previousNode, layout, {
return (nil, { info in
assert(Queue.mainQueue().isCurrent())
previousNode.with({ $0.index = index })
apply(info)
})
})
}
})
} else {
item.nodeConfiguredForParams(async: { f in
if synchronous {
f()
} else {
self.async(f)
}
}, 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)
})
}
}
fillMissingNodes
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
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
var operations = inputOperations
let completion = inputCompletion
let updateAnimation: ListViewItemUpdateAnimation = animated ? .System(duration: insertionAnimationDuration) : .None
if state.nodes.count > 1000 {
print("state.nodes.count > 1000")
}
while true {
if self.items.count == 0 {
completion(state, operations)
break
} else {
var insertionItemIndexAndDirection: (Int, ListViewInsertionOffsetDirection)?
if self.debugInfo {
assert(true)
}
if let insertionPoint = state.insertionPoint(insertDirectionHints, itemCount: self.items.count) {
insertionItemIndexAndDirection = (insertionPoint.index, insertionPoint.direction)
}
if self.debugInfo {
print("insertionItemIndexAndDirection \(String(describing: insertionItemIndexAndDirection))")
}
if let insertionItemIndexAndDirection = insertionItemIndexAndDirection {
let index = insertionItemIndexAndDirection.0
let threadId = pthread_self()
var tailRecurse = false
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
state.insertNode(index, node: node, layout: layout, apply: apply, offsetDirection: insertionItemIndexAndDirection.1, animated: animated && animatedInsertIndices.contains(index), operations: &operations, itemCount: self.items.count)
} else {
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, synchronousLoads: synchronousLoads, animated: animated, inputAnimatedInsertIndices: animatedInsertIndices, insertDirectionHints: insertDirectionHints, inputState: updatedState, inputPreviousNodes: previousNodes, inputOperations: updatedOperations, inputCompletion: completion)
}
})
if !tailRecurse {
tailRecurse = true
break
}
} else {
completion(state, operations)
break
}
}
}
}
updateVisibleItemsTransaction
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
private func updateVisibleItemsTransaction(synchronous: Bool, completion: @escaping () -> Void) {
if self.items.count == 0 && self.itemNodes.count == 0 {
completion()
return
}
var i = 0
while i < self.itemNodes.count {
let node = self.itemNodes[i]
if node.index == nil && node.apparentHeight <= CGFloat.ulpOfOne {
self.removeItemNodeAtIndex(i)
} else {
i += 1
}
}
let state = self.currentState()
let begin: () -> Void = {
// fillMissingNodes based on current state
// no `insertDirectionHints`
// no `inputAnimatedInsertIndices`
// no `inputPreviousNodes`
// no `inputOperations`
self.fillMissingNodes(synchronous: synchronous, synchronousLoads: false, animated: false, inputAnimatedInsertIndices: [], insertDirectionHints: [:], inputState: state, inputPreviousNodes: [:], inputOperations: []) { state, operations in
var updatedState = state
var updatedOperations = operations
updatedState.removeInvisibleNodes(&updatedOperations)
self.dispatchOnVSync {
self.replayOperations(animated: false, animateAlpha: false, animateCrossfade: false, synchronous: false, animateTopItemVerticalOrigin: false, operations: updatedOperations, requestItemInsertionAnimationsIndices: Set(), scrollToItem: nil, additionalScrollDistance: 0.0, updateSizeAndInsets: nil, stationaryItemIndex: nil, updateOpaqueState: nil, completion: completion)
}
}
}
if synchronous {
begin()
} else {
self.async {
begin()
}
}
}
updateVisibleItemRange
1
2
3
4
5
6
7
8
private func updateVisibleItemRange(force: Bool = false) {
let currentRange = self.immediateDisplayedItemRange()
if currentRange != self.displayedItemRange || force {
self.displayedItemRange = currentRange
self.displayedItemRangeChanged(currentRange, self.opaqueTransactionState)
}
}
immediateDisplayedItemRange
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
private func immediateDisplayedItemRange() -> ListViewDisplayedItemRange {
var loadedRange: ListViewItemRange?
var visibleRange: ListViewVisibleItemRange?
if self.itemNodes.count != 0 {
var firstIndex: (nodeIndex: Int, index: Int)?
var lastIndex: (nodeIndex: Int, index: Int)?
// find the first index
var i = 0
while i < self.itemNodes.count {
if let index = self.itemNodes[i].index {
firstIndex = (i, index)
break
}
i += 1
}
// find the last index
i = self.itemNodes.count - 1
while i >= 0 {
if let index = self.itemNodes[i].index {
lastIndex = (i, index)
break
}
i -= 1
}
if let firstIndex = firstIndex, let lastIndex = lastIndex {
// find first visible index
// frame.maxY >= self.insets.top && frame.minY < self.visibleSize.height + self.insets.bottom
var firstVisibleIndex: (Int, Bool)?
for i in firstIndex.nodeIndex ... lastIndex.nodeIndex {
if let index = self.itemNodes[i].index {
let frame = self.itemNodes[i].apparentFrame
if frame.maxY >= self.insets.top && frame.minY < self.visibleSize.height + self.insets.bottom {
firstVisibleIndex = (index, frame.minY >= self.insets.top - 10.0)
break
}
}
}
if let firstVisibleIndex = firstVisibleIndex {
// find last visible index
// frame.maxY >= self.insets.top && frame.minY < self.visibleSize.height - self.insets.bottom
var lastVisibleIndex: Int?
for i in (firstIndex.nodeIndex ... lastIndex.nodeIndex).reversed() {
if let index = self.itemNodes[i].index {
let frame = self.itemNodes[i].apparentFrame
if frame.maxY >= self.insets.top && frame.minY < self.visibleSize.height - self.insets.bottom {
lastVisibleIndex = index
break
}
}
}
if let lastVisibleIndex = lastVisibleIndex {
// visible range
visibleRange = ListViewVisibleItemRange(firstIndex: firstVisibleIndex.0, firstIndexFullyVisible: firstVisibleIndex.1, lastIndex: lastVisibleIndex)
}
}
// loaded range
loadedRange = ListViewItemRange(firstIndex: firstIndex.index, lastIndex: lastIndex.index)
}
}
return ListViewDisplayedItemRange(loadedRange: loadedRange, visibleRange: visibleRange)
}
displayedItemRangeChanged
1
public final var displayedItemRangeChanged: (ListViewDisplayedItemRange, Any?) -> Void = { _, _ in }
current state
1
2
3
4
5
6
7
8
9
10
11
12
13
14
private func currentState() -> ListViewState {
var nodes: [ListViewStateNode] = []
nodes.reserveCapacity(self.itemNodes.count)
for node in self.itemNodes {
if let index = node.index {
nodes.append(.Node(index: index, frame: node.apparentFrame, referenceNode: QueueLocalObject(queue: Queue.mainQueue(), generate: {
return node
})))
} else {
nodes.append(.Placeholder(frame: node.apparentFrame))
}
}
return ListViewState(insets: self.insets, visibleSize: self.visibleSize, invisibleInset: self.invisibleInset, nodes: nodes, scrollPosition: nil, stationaryOffset: nil, stackFromBottom: self.stackFromBottom)
}