Better scroll position maintenance

This commit is contained in:
Justin Mazzocchi 2020-08-23 01:38:39 -07:00
parent c309b94ad0
commit cabbe30c2f
No known key found for this signature in database
GPG Key ID: E223E6937AAFB01C
2 changed files with 39 additions and 28 deletions

View File

@ -7,33 +7,17 @@ class StatusesViewModel: ObservableObject {
@Published private(set) var statusSections = [[Status]]() @Published private(set) var statusSections = [[Status]]()
@Published var alertItem: AlertItem? @Published var alertItem: AlertItem?
@Published private(set) var loading = false @Published private(set) var loading = false
let scrollToStatus: AnyPublisher<Status, Never> private(set) var maintainScrollPositionOfStatusID: String?
private let statusListService: StatusListService private let statusListService: StatusListService
private let scrollToStatusInput = PassthroughSubject<Status, Never>()
private var hasScrolledToParentAfterContextLoad = false
private var cancellables = Set<AnyCancellable>() private var cancellables = Set<AnyCancellable>()
init(statusListService: StatusListService) { init(statusListService: StatusListService) {
self.statusListService = statusListService self.statusListService = statusListService
scrollToStatus = scrollToStatusInput.eraseToAnyPublisher()
statusListService.statusSections statusListService.statusSections
.handleEvents(receiveOutput: determineIfScrollPositionShouldBeMaintained(newStatusSections:))
.assignErrorsToAlertItem(to: \.alertItem, on: self) .assignErrorsToAlertItem(to: \.alertItem, on: self)
.assign(to: &$statusSections) .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 { private extension StatusesViewModel {
static var viewModelCache = [Status: StatusViewModel]() 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
}
}
} }

View File

@ -51,18 +51,28 @@ class StatusListViewController: UITableViewController {
tableView.separatorInset = .zero tableView.separatorInset = .zero
viewModel.$statusSections.map { $0.snapshot() } 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 .sink { [weak self] in
guard guard let self = self else { return }
let self = self,
let indexPath = self.dataSource.indexPath(for: $0)
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) .store(in: &cancellables)
} }
@ -112,6 +122,14 @@ extension StatusListViewController: StatusTableViewCellDelegate {
} }
private extension StatusListViewController { 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) { func share(url: URL) {
let activityViewController = UIActivityViewController(activityItems: [url], applicationActivities: nil) let activityViewController = UIActivityViewController(activityItems: [url], applicationActivities: nil)