diff --git a/Shared/View Models/StatusesViewModel.swift b/Shared/View Models/StatusesViewModel.swift index c1550be..0d0790a 100644 --- a/Shared/View Models/StatusesViewModel.swift +++ b/Shared/View Models/StatusesViewModel.swift @@ -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 + private(set) var maintainScrollPositionOfStatusID: String? private let statusListService: StatusListService - private let scrollToStatusInput = PassthroughSubject() - private var hasScrolledToParentAfterContextLoad = false private var cancellables = Set() 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 + } + } } diff --git a/iOS/View Controllers/StatusListViewController.swift b/iOS/View Controllers/StatusListViewController.swift index 55c0360..20eebf0 100644 --- a/iOS/View Controllers/StatusListViewController.swift +++ b/iOS/View Controllers/StatusListViewController.swift @@ -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)