mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-05 20:09:04 +00:00
Rewrite timeline top and bottom detection on top of CurrentValueSubjects
This commit is contained in:
@@ -22,9 +22,6 @@ class ListTableViewAdapter: NSObject, UITableViewDelegate {
|
|||||||
private var contentOffsetObserverToken: NSKeyValueObservation?
|
private var contentOffsetObserverToken: NSKeyValueObservation?
|
||||||
private var boundsObserverToken: NSKeyValueObservation?
|
private var boundsObserverToken: NSKeyValueObservation?
|
||||||
|
|
||||||
private var isAtTop: Bool = false
|
|
||||||
private var isAtBottom: Bool = false
|
|
||||||
|
|
||||||
private var offsetDetails: ContentOffsetDetails?
|
private var offsetDetails: ContentOffsetDetails?
|
||||||
private var draggingInitiated = false
|
private var draggingInitiated = false
|
||||||
private var isAnimatingKeyboardAppearance = false
|
private var isAnimatingKeyboardAppearance = false
|
||||||
@@ -33,12 +30,12 @@ class ListTableViewAdapter: NSObject, UITableViewDelegate {
|
|||||||
private(set) var tableView: UITableView?
|
private(set) var tableView: UITableView?
|
||||||
|
|
||||||
let scrollViewDidRestPublisher = PassthroughSubject<Void, Never>()
|
let scrollViewDidRestPublisher = PassthroughSubject<Void, Never>()
|
||||||
let scrollViewDidReachTopPublisher = PassthroughSubject<Void, Never>()
|
let scrollViewTopVisiblePublisher = CurrentValueSubject<Bool, Never>(false)
|
||||||
let scrollViewBottomVisiblePublisher = PassthroughSubject<Bool, Never>()
|
let scrollViewBottomVisiblePublisher = CurrentValueSubject<Bool, Never>(false)
|
||||||
|
|
||||||
override init() {
|
override init() {
|
||||||
self.topDetectionOffset = 0.0
|
topDetectionOffset = 0.0
|
||||||
self.bottomDetectionOffset = 0.0
|
bottomDetectionOffset = 0.0
|
||||||
}
|
}
|
||||||
|
|
||||||
init(tableView: UITableView, topDetectionOffset: CGFloat, bottomDetectionOffset: CGFloat) {
|
init(tableView: UITableView, topDetectionOffset: CGFloat, bottomDetectionOffset: CGFloat) {
|
||||||
@@ -65,9 +62,9 @@ class ListTableViewAdapter: NSObject, UITableViewDelegate {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if isBottomVisible {
|
if computeIsBottomVisible() {
|
||||||
offsetDetails = .bottomOffset
|
offsetDetails = .bottomOffset
|
||||||
} else if isTopVisible {
|
} else if computeIsTopVisible() {
|
||||||
if let topIndexPath = tableView.indexPathsForVisibleRows?.first {
|
if let topIndexPath = tableView.indexPathsForVisibleRows?.first {
|
||||||
offsetDetails = .topOffset(previousVisibleIndexPath: topIndexPath,
|
offsetDetails = .topOffset(previousVisibleIndexPath: topIndexPath,
|
||||||
previousItemCount: tableView.numberOfRows(inSection: 0))
|
previousItemCount: tableView.numberOfRows(inSection: 0))
|
||||||
@@ -101,27 +98,11 @@ class ListTableViewAdapter: NSObject, UITableViewDelegate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var isTracking: Bool {
|
var isTracking: Bool {
|
||||||
self.tableView?.isTracking == true
|
tableView?.isTracking == true
|
||||||
}
|
}
|
||||||
|
|
||||||
var isDecelerating: Bool {
|
var isDecelerating: Bool {
|
||||||
self.tableView?.isDecelerating == true
|
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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func scrollToBottom(animated: Bool = false) {
|
func scrollToBottom(animated: Bool = false) {
|
||||||
@@ -171,12 +152,12 @@ class ListTableViewAdapter: NSObject, UITableViewDelegate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func handleScrollViewScroll() {
|
private func handleScrollViewScroll() {
|
||||||
guard let tableView = self.tableView else {
|
guard let tableView = tableView else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
let hasScrolledBecauseOfFrameChange = (previousFrame != tableView.frame)
|
let hasScrolledBecauseOfFrameChange = (previousFrame != tableView.frame)
|
||||||
let shouldPinToBottom = isAtBottom && (isAnimatingKeyboardAppearance || hasScrolledBecauseOfFrameChange)
|
let shouldPinToBottom = scrollViewBottomVisiblePublisher.value && (isAnimatingKeyboardAppearance || hasScrolledBecauseOfFrameChange)
|
||||||
|
|
||||||
if shouldPinToBottom {
|
if shouldPinToBottom {
|
||||||
deregisterContentOffsetObserver()
|
deregisterContentOffsetObserver()
|
||||||
@@ -187,35 +168,49 @@ class ListTableViewAdapter: NSObject, UITableViewDelegate {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
let isTopVisible = self.isTopVisible
|
let isTopVisible = computeIsTopVisible()
|
||||||
if isTopVisible && self.isAtTop != isTopVisible {
|
if isTopVisible != scrollViewTopVisiblePublisher.value {
|
||||||
self.scrollViewDidReachTopPublisher.send(())
|
scrollViewTopVisiblePublisher.send(isTopVisible)
|
||||||
}
|
|
||||||
self.isAtTop = isTopVisible
|
|
||||||
|
|
||||||
let isBottomVisible = self.isBottomVisible
|
|
||||||
if self.isAtBottom != isBottomVisible {
|
|
||||||
self.scrollViewBottomVisiblePublisher.send(isBottomVisible)
|
|
||||||
self.isAtBottom = isBottomVisible
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if !self.draggingInitiated && tableView.isDragging {
|
let isBottomVisible = computeIsBottomVisible()
|
||||||
self.draggingInitiated = true
|
if isBottomVisible != scrollViewBottomVisiblePublisher.value {
|
||||||
} else if self.draggingInitiated && !tableView.isDragging {
|
scrollViewBottomVisiblePublisher.send(isBottomVisible)
|
||||||
self.draggingInitiated = false
|
}
|
||||||
self.scrollViewDidRestPublisher.send(())
|
|
||||||
|
if !draggingInitiated && tableView.isDragging {
|
||||||
|
draggingInitiated = true
|
||||||
|
} else if draggingInitiated && !tableView.isDragging {
|
||||||
|
draggingInitiated = false
|
||||||
|
scrollViewDidRestPublisher.send(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc private func handlePanGesture(_ sender: UIPanGestureRecognizer) {
|
@objc private func handlePanGesture(_ sender: UIPanGestureRecognizer) {
|
||||||
guard let tableView = self.tableView,
|
guard let tableView = tableView,
|
||||||
sender.state == .ended,
|
sender.state == .ended,
|
||||||
draggingInitiated == true,
|
draggingInitiated == true,
|
||||||
!tableView.isDecelerating else {
|
!tableView.isDecelerating else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
self.draggingInitiated = false
|
draggingInitiated = false
|
||||||
self.scrollViewDidRestPublisher.send(())
|
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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -76,16 +76,16 @@ struct TimelineItemList: View {
|
|||||||
.onReceive(scrollToBottomPublisher, perform: {
|
.onReceive(scrollToBottomPublisher, perform: {
|
||||||
tableViewObserver.scrollToBottom(animated: true)
|
tableViewObserver.scrollToBottom(animated: true)
|
||||||
})
|
})
|
||||||
.onReceive(tableViewObserver.scrollViewBottomVisiblePublisher, perform: { value in
|
.onReceive(tableViewObserver.scrollViewTopVisiblePublisher, perform: { isTopVisible in
|
||||||
bottomVisiblePublisher.send(value)
|
if !isTopVisible || context.viewState.isBackPaginating {
|
||||||
})
|
|
||||||
.onReceive(tableViewObserver.scrollViewDidReachTopPublisher, perform: {
|
|
||||||
if context.viewState.isBackPaginating {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
attemptBackPagination()
|
attemptBackPagination()
|
||||||
})
|
})
|
||||||
|
.onReceive(tableViewObserver.scrollViewBottomVisiblePublisher, perform: { isBottomVisible in
|
||||||
|
bottomVisiblePublisher.send(isBottomVisible)
|
||||||
|
})
|
||||||
.onChange(of: context.viewState.items) { _ in
|
.onChange(of: context.viewState.items) { _ in
|
||||||
// Don't update the list while moving
|
// Don't update the list while moving
|
||||||
if tableViewObserver.isDecelerating || tableViewObserver.isTracking {
|
if tableViewObserver.isDecelerating || tableViewObserver.isTracking {
|
||||||
@@ -123,9 +123,10 @@ struct TimelineItemList: View {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if tableViewObserver.isTopVisible == false {
|
if tableViewObserver.scrollViewTopVisiblePublisher.value == false {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
context.send(viewAction: .loadPreviousPage)
|
context.send(viewAction: .loadPreviousPage)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user