Fix multi-column nav not animating scroll position when replacing subsequent columns

Closes #500
This commit is contained in:
Shadowfacts 2024-06-08 10:32:32 -07:00
parent c88076eec0
commit 888f44366c
1 changed files with 31 additions and 26 deletions

View File

@ -11,16 +11,20 @@ import UIKit
class MultiColumnNavigationController: UIViewController { class MultiColumnNavigationController: UIViewController {
private var isManuallyUpdating = false private var isManuallyUpdating = false
var viewControllers: [UIViewController] = [] { private var _viewControllers: [UIViewController] = []
didSet { var viewControllers: [UIViewController] {
guard isViewLoaded, get {
!isManuallyUpdating else { _viewControllers
return
} }
set {
_viewControllers = newValue
if isViewLoaded,
!isManuallyUpdating {
updateViews() updateViews()
scrollToEnd(animated: false) scrollToEnd(animated: false)
} }
} }
}
private var scrollView = UIScrollView() private var scrollView = UIScrollView()
private var stackView = UIStackView() private var stackView = UIStackView()
@ -68,13 +72,13 @@ class MultiColumnNavigationController: UIViewController {
private func updateViews() { private func updateViews() {
var i = 0 var i = 0
while i < viewControllers.count { while i < _viewControllers.count {
let needsCloseButton = i > 0 let needsCloseButton = i > 0
if i <= stackView.arrangedSubviews.count - 1 { if i <= stackView.arrangedSubviews.count - 1 {
let existing = stackView.arrangedSubviews[i] as! ColumnView let existing = stackView.arrangedSubviews[i] as! ColumnView
existing.setContent(viewControllers[i], needsCloseButton: needsCloseButton) existing.setContent(_viewControllers[i], needsCloseButton: needsCloseButton)
} else { } else {
let new = ColumnView(owner: self, contentViewController: viewControllers[i], needsCloseButton: needsCloseButton) let new = ColumnView(owner: self, contentViewController: _viewControllers[i], needsCloseButton: needsCloseButton)
stackView.addArrangedSubview(new) stackView.addArrangedSubview(new)
} }
i += 1 i += 1
@ -92,7 +96,7 @@ class MultiColumnNavigationController: UIViewController {
var index: Int? = nil var index: Int? = nil
var current: UIViewController? = sender var current: UIViewController? = sender
while let c = current { while let c = current {
index = viewControllers.firstIndex(of: c) index = _viewControllers.firstIndex(of: c)
if index != nil { if index != nil {
break break
} else { } else {
@ -112,19 +116,20 @@ class MultiColumnNavigationController: UIViewController {
} }
func replaceViewControllers(_ vcs: [UIViewController], after afterIndex: Int, animated: Bool) { func replaceViewControllers(_ vcs: [UIViewController], after afterIndex: Int, animated: Bool) {
if afterIndex == viewControllers.count - 1 && vcs.count == 1 { if afterIndex == _viewControllers.count - 1 && vcs.count == 1 {
pushViewController(vcs[0], animated: animated) pushViewController(vcs[0], animated: animated)
} else { } else {
viewControllers = Array(viewControllers[...afterIndex]) + vcs _viewControllers = Array(_viewControllers[...afterIndex]) + vcs
updateViews()
scrollToEnd(animated: animated) scrollToEnd(animated: animated)
} }
} }
private func scrollToEnd(animated: Bool) { private func scrollToEnd(animated: Bool) {
if viewControllers.isEmpty { if _viewControllers.isEmpty {
scrollView.setContentOffset(.init(x: -scrollView.adjustedLeadingContentInset, y: -scrollView.adjustedContentInset.top), animated: false) scrollView.setContentOffset(.init(x: -scrollView.adjustedLeadingContentInset, y: -scrollView.adjustedContentInset.top), animated: false)
} else { } else {
scrollColumnToEnd(columnIndex: viewControllers.count - 1, animated: animated) scrollColumnToEnd(columnIndex: _viewControllers.count - 1, animated: animated)
} }
} }
@ -142,14 +147,12 @@ class MultiColumnNavigationController: UIViewController {
} }
fileprivate func closeColumn(_ vc: UIViewController) { fileprivate func closeColumn(_ vc: UIViewController) {
guard let index = viewControllers.firstIndex(of: vc), guard let index = _viewControllers.firstIndex(of: vc),
index > 0 else { index > 0 else {
// Can't close the last column // Can't close the last column
return return
} }
isManuallyUpdating = true _viewControllers.removeSubrange(index...)
defer { isManuallyUpdating = false }
viewControllers.removeSubrange(index...)
animateChanges { animateChanges {
for column in self.stackView.arrangedSubviews[index...] { for column in self.stackView.arrangedSubviews[index...] {
column.layer.opacity = 0 column.layer.opacity = 0
@ -158,7 +161,6 @@ class MultiColumnNavigationController: UIViewController {
} completion: { } completion: {
self.updateViews() self.updateViews()
} }
} }
private func animateChanges(_ animations: @escaping () -> Void, completion: (() -> Void)? = nil) { private func animateChanges(_ animations: @escaping () -> Void, completion: (() -> Void)? = nil) {
@ -173,19 +175,22 @@ class MultiColumnNavigationController: UIViewController {
extension MultiColumnNavigationController: NavigationControllerProtocol { extension MultiColumnNavigationController: NavigationControllerProtocol {
var topViewController: UIViewController? { var topViewController: UIViewController? {
viewControllers.last _viewControllers.last
} }
func popToRootViewController(animated: Bool) -> [UIViewController]? { func popToRootViewController(animated: Bool) -> [UIViewController]? {
let removed = Array(viewControllers.dropFirst()) guard !_viewControllers.isEmpty else {
viewControllers = [viewControllers.first!] return nil
}
let removed = Array(_viewControllers.dropFirst())
_viewControllers = [_viewControllers.first!]
updateViews()
scrollToEnd(animated: animated)
return removed return removed
} }
func pushViewController(_ vc: UIViewController, animated: Bool) { func pushViewController(_ vc: UIViewController, animated: Bool) {
isManuallyUpdating = true _viewControllers.append(vc)
defer { isManuallyUpdating = false }
viewControllers.append(vc)
updateViews() updateViews()
scrollToEnd(animated: animated) scrollToEnd(animated: animated)
if animated { if animated {