Better scroll position maintenance
This commit is contained in:
parent
c309b94ad0
commit
cabbe30c2f
|
@ -7,33 +7,17 @@ class StatusesViewModel: ObservableObject {
|
|||
@Published private(set) var statusSections = [[Status]]()
|
||||
@Published var alertItem: AlertItem?
|
||||
@Published private(set) var loading = false
|
||||
let scrollToStatus: AnyPublisher<Status, Never>
|
||||
private(set) var maintainScrollPositionOfStatusID: String?
|
||||
private let statusListService: StatusListService
|
||||
private let scrollToStatusInput = PassthroughSubject<Status, Never>()
|
||||
private var hasScrolledToParentAfterContextLoad = false
|
||||
private var cancellables = Set<AnyCancellable>()
|
||||
|
||||
init(statusListService: StatusListService) {
|
||||
self.statusListService = statusListService
|
||||
scrollToStatus = scrollToStatusInput.eraseToAnyPublisher()
|
||||
|
||||
statusListService.statusSections
|
||||
.handleEvents(receiveOutput: determineIfScrollPositionShouldBeMaintained(newStatusSections:))
|
||||
.assignErrorsToAlertItem(to: \.alertItem, on: self)
|
||||
.assign(to: &$statusSections)
|
||||
|
||||
$statusSections
|
||||
.sink { [weak self] in
|
||||
guard let self = self else { return }
|
||||
|
||||
if
|
||||
let contextParent = self.contextParent,
|
||||
!($0.first ?? []).isEmpty || !(($0.last ?? []).isEmpty),
|
||||
!self.hasScrolledToParentAfterContextLoad {
|
||||
self.hasScrolledToParentAfterContextLoad = true
|
||||
self.scrollToStatusInput.send(contextParent)
|
||||
}
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -71,4 +55,13 @@ extension StatusesViewModel {
|
|||
|
||||
private extension StatusesViewModel {
|
||||
static var viewModelCache = [Status: StatusViewModel]()
|
||||
|
||||
func determineIfScrollPositionShouldBeMaintained(newStatusSections: [[Status]]) {
|
||||
maintainScrollPositionOfStatusID = nil // clear old value
|
||||
|
||||
// Maintain scroll position of parent after initial load of context
|
||||
if let contextParent = contextParent, statusSections == [[], [contextParent], []] {
|
||||
maintainScrollPositionOfStatusID = contextParent.id
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -51,18 +51,28 @@ class StatusListViewController: UITableViewController {
|
|||
tableView.separatorInset = .zero
|
||||
|
||||
viewModel.$statusSections.map { $0.snapshot() }
|
||||
.sink { [weak self] in self?.dataSource.apply($0, animatingDifferences: false) }
|
||||
.store(in: &cancellables)
|
||||
|
||||
viewModel.scrollToStatus
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink { [weak self] in
|
||||
guard
|
||||
let self = self,
|
||||
let indexPath = self.dataSource.indexPath(for: $0)
|
||||
else { return }
|
||||
guard let self = self else { return }
|
||||
|
||||
self.tableView.scrollToRow(at: indexPath, at: .none, animated: true)
|
||||
var offsetFromNavigationBar: CGFloat?
|
||||
|
||||
if
|
||||
let id = self.viewModel.maintainScrollPositionOfStatusID,
|
||||
let indexPath = self.indexPath(statusID: id),
|
||||
let navigationBar = self.navigationController?.navigationBar {
|
||||
let navigationBarMaxY = self.tableView.convert(navigationBar.bounds, from: navigationBar).maxY
|
||||
offsetFromNavigationBar = self.tableView.rectForRow(at: indexPath).origin.y - navigationBarMaxY
|
||||
}
|
||||
|
||||
self.dataSource.apply($0, animatingDifferences: false) {
|
||||
if
|
||||
let id = self.viewModel.maintainScrollPositionOfStatusID,
|
||||
let indexPath = self.indexPath(statusID: id),
|
||||
let offsetFromNavigationBar = offsetFromNavigationBar {
|
||||
self.tableView.scrollToRow(at: indexPath, at: .top, animated: false)
|
||||
self.tableView.contentOffset.y -= offsetFromNavigationBar
|
||||
}
|
||||
}
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
}
|
||||
|
@ -112,6 +122,14 @@ extension StatusListViewController: StatusTableViewCellDelegate {
|
|||
}
|
||||
|
||||
private extension StatusListViewController {
|
||||
func indexPath(statusID: String) -> IndexPath? {
|
||||
guard let status = viewModel.statusSections.reduce([], +).first(where: { $0.id == statusID }) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
return dataSource.indexPath(for: status)
|
||||
}
|
||||
|
||||
func share(url: URL) {
|
||||
let activityViewController = UIActivityViewController(activityItems: [url], applicationActivities: nil)
|
||||
|
||||
|
|
Loading…
Reference in New Issue