Rewrite timeline top and bottom detection on top of CurrentValueSubjects

This commit is contained in:
Stefan Ceriu
2022-05-02 13:00:55 +03:00
parent a8572f94a5
commit f45569b4d4
2 changed files with 50 additions and 54 deletions

View File

@@ -22,23 +22,20 @@ class ListTableViewAdapter: NSObject, UITableViewDelegate {
private var contentOffsetObserverToken: NSKeyValueObservation?
private var boundsObserverToken: NSKeyValueObservation?
private var isAtTop: Bool = false
private var isAtBottom: Bool = false
private var offsetDetails: ContentOffsetDetails?
private var draggingInitiated = false
private var isAnimatingKeyboardAppearance = false
private var previousFrame: CGRect = .zero
private(set) var tableView: UITableView?
let scrollViewDidRestPublisher = PassthroughSubject<Void, Never>()
let scrollViewDidReachTopPublisher = PassthroughSubject<Void, Never>()
let scrollViewBottomVisiblePublisher = PassthroughSubject<Bool, Never>()
let scrollViewTopVisiblePublisher = CurrentValueSubject<Bool, Never>(false)
let scrollViewBottomVisiblePublisher = CurrentValueSubject<Bool, Never>(false)
override init() {
self.topDetectionOffset = 0.0
self.bottomDetectionOffset = 0.0
topDetectionOffset = 0.0
bottomDetectionOffset = 0.0
}
init(tableView: UITableView, topDetectionOffset: CGFloat, bottomDetectionOffset: CGFloat) {
@@ -65,9 +62,9 @@ class ListTableViewAdapter: NSObject, UITableViewDelegate {
return
}
if isBottomVisible {
if computeIsBottomVisible() {
offsetDetails = .bottomOffset
} else if isTopVisible {
} else if computeIsTopVisible() {
if let topIndexPath = tableView.indexPathsForVisibleRows?.first {
offsetDetails = .topOffset(previousVisibleIndexPath: topIndexPath,
previousItemCount: tableView.numberOfRows(inSection: 0))
@@ -101,27 +98,11 @@ class ListTableViewAdapter: NSObject, UITableViewDelegate {
}
var isTracking: Bool {
self.tableView?.isTracking == true
tableView?.isTracking == true
}
var isDecelerating: Bool {
self.tableView?.isDecelerating == true
}
var isTopVisible: Bool {
guard let scrollView = tableView else {
return false
}
return (scrollView.contentOffset.y + scrollView.adjustedContentInset.top) <= topDetectionOffset
}
var isBottomVisible: Bool {
guard let scrollView = tableView else {
return false
}
return (scrollView.contentOffset.y + self.bottomDetectionOffset) >= (scrollView.contentSize.height - scrollView.frame.size.height)
tableView?.isDecelerating == true
}
func scrollToBottom(animated: Bool = false) {
@@ -171,12 +152,12 @@ class ListTableViewAdapter: NSObject, UITableViewDelegate {
}
private func handleScrollViewScroll() {
guard let tableView = self.tableView else {
guard let tableView = tableView else {
return
}
let hasScrolledBecauseOfFrameChange = (previousFrame != tableView.frame)
let shouldPinToBottom = isAtBottom && (isAnimatingKeyboardAppearance || hasScrolledBecauseOfFrameChange)
let shouldPinToBottom = scrollViewBottomVisiblePublisher.value && (isAnimatingKeyboardAppearance || hasScrolledBecauseOfFrameChange)
if shouldPinToBottom {
deregisterContentOffsetObserver()
@@ -187,35 +168,49 @@ class ListTableViewAdapter: NSObject, UITableViewDelegate {
return
}
let isTopVisible = self.isTopVisible
if isTopVisible && self.isAtTop != isTopVisible {
self.scrollViewDidReachTopPublisher.send(())
}
self.isAtTop = isTopVisible
let isBottomVisible = self.isBottomVisible
if self.isAtBottom != isBottomVisible {
self.scrollViewBottomVisiblePublisher.send(isBottomVisible)
self.isAtBottom = isBottomVisible
let isTopVisible = computeIsTopVisible()
if isTopVisible != scrollViewTopVisiblePublisher.value {
scrollViewTopVisiblePublisher.send(isTopVisible)
}
if !self.draggingInitiated && tableView.isDragging {
self.draggingInitiated = true
} else if self.draggingInitiated && !tableView.isDragging {
self.draggingInitiated = false
self.scrollViewDidRestPublisher.send(())
let isBottomVisible = computeIsBottomVisible()
if isBottomVisible != scrollViewBottomVisiblePublisher.value {
scrollViewBottomVisiblePublisher.send(isBottomVisible)
}
if !draggingInitiated && tableView.isDragging {
draggingInitiated = true
} else if draggingInitiated && !tableView.isDragging {
draggingInitiated = false
scrollViewDidRestPublisher.send(())
}
}
@objc private func handlePanGesture(_ sender: UIPanGestureRecognizer) {
guard let tableView = self.tableView,
guard let tableView = tableView,
sender.state == .ended,
draggingInitiated == true,
!tableView.isDecelerating else {
return
}
self.draggingInitiated = false
self.scrollViewDidRestPublisher.send(())
draggingInitiated = false
scrollViewDidRestPublisher.send(())
}
private func computeIsTopVisible() -> Bool {
guard let scrollView = tableView else {
return false
}
return (scrollView.contentOffset.y + scrollView.adjustedContentInset.top) <= topDetectionOffset
}
private func computeIsBottomVisible() -> Bool {
guard let scrollView = tableView else {
return false
}
return (scrollView.contentOffset.y + bottomDetectionOffset) >= (scrollView.contentSize.height - scrollView.frame.size.height)
}
}

View File

@@ -76,16 +76,16 @@ struct TimelineItemList: View {
.onReceive(scrollToBottomPublisher, perform: {
tableViewObserver.scrollToBottom(animated: true)
})
.onReceive(tableViewObserver.scrollViewBottomVisiblePublisher, perform: { value in
bottomVisiblePublisher.send(value)
})
.onReceive(tableViewObserver.scrollViewDidReachTopPublisher, perform: {
if context.viewState.isBackPaginating {
.onReceive(tableViewObserver.scrollViewTopVisiblePublisher, perform: { isTopVisible in
if !isTopVisible || context.viewState.isBackPaginating {
return
}
attemptBackPagination()
})
.onReceive(tableViewObserver.scrollViewBottomVisiblePublisher, perform: { isBottomVisible in
bottomVisiblePublisher.send(isBottomVisible)
})
.onChange(of: context.viewState.items) { _ in
// Don't update the list while moving
if tableViewObserver.isDecelerating || tableViewObserver.isTracking {
@@ -123,9 +123,10 @@ struct TimelineItemList: View {
return
}
if tableViewObserver.isTopVisible == false {
if tableViewObserver.scrollViewTopVisiblePublisher.value == false {
return
}
context.send(viewAction: .loadPreviousPage)
}