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 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
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue