From e74daa4df068ff458323ae8b804df85ff7f6d604 Mon Sep 17 00:00:00 2001 From: Justin Mazzocchi <2831158+jzzocc@users.noreply.github.com> Date: Wed, 26 Aug 2020 01:25:34 -0700 Subject: [PATCH] Refactoring --- Shared/View Models/StatusesViewModel.swift | 19 +++++-- .../StatusListViewController.swift | 55 ++++++++----------- iOS/Views/StatusTableViewCell.swift | 10 ++-- 3 files changed, 41 insertions(+), 43 deletions(-) diff --git a/Shared/View Models/StatusesViewModel.swift b/Shared/View Models/StatusesViewModel.swift index 83074c6..dc7466b 100644 --- a/Shared/View Models/StatusesViewModel.swift +++ b/Shared/View Models/StatusesViewModel.swift @@ -4,10 +4,11 @@ import Foundation import Combine class StatusesViewModel: ObservableObject { - @Published private(set) var statusSections = [[Status]]() + @Published private(set) var statusIDs = [[String]]() @Published var alertItem: AlertItem? @Published private(set) var loading = false private(set) var maintainScrollPositionOfStatusID: String? + private var statuses = [String: Status]() private let statusListService: StatusListService private var statusViewModelCache = [Status: (StatusViewModel, AnyCancellable)]() private var cancellables = Set() @@ -19,9 +20,11 @@ class StatusesViewModel: ObservableObject { .handleEvents(receiveOutput: { [weak self] in self?.determineIfScrollPositionShouldBeMaintained(newStatusSections: $0) self?.cleanViewModelCache(newStatusSections: $0) + self?.statuses = Dictionary(uniqueKeysWithValues: $0.reduce([], +).map { ($0.id, $0) }) }) .assignErrorsToAlertItem(to: \.alertItem, on: self) - .assign(to: &$statusSections) + .map { $0.map { section in section.map(\.id) } } + .assign(to: &$statusIDs) } } @@ -38,7 +41,9 @@ extension StatusesViewModel { .store(in: &cancellables) } - func statusViewModel(status: Status) -> StatusViewModel { + func statusViewModel(id: String) -> StatusViewModel? { + guard let status = statuses[id] else { return nil } + var statusViewModel: StatusViewModel if let cachedViewModel = statusViewModelCache[status]?.0 { @@ -59,8 +64,10 @@ extension StatusesViewModel { return statusViewModel } - func contextViewModel(status: Status) -> StatusesViewModel { - StatusesViewModel(statusListService: statusListService.contextService(status: status)) + func contextViewModel(id: String) -> StatusesViewModel? { + guard let status = statuses[id] else { return nil } + + return StatusesViewModel(statusListService: statusListService.contextService(status: status)) } } @@ -69,7 +76,7 @@ private extension StatusesViewModel { maintainScrollPositionOfStatusID = nil // clear old value // Maintain scroll position of parent after initial load of context - if let contextParentID = contextParentID, statusSections.reduce([], +).map(\.id) == [contextParentID] { + if let contextParentID = contextParentID, statusIDs.reduce([], +) == [contextParentID] { maintainScrollPositionOfStatusID = contextParentID } } diff --git a/iOS/View Controllers/StatusListViewController.swift b/iOS/View Controllers/StatusListViewController.swift index 486b72f..7a9e20d 100644 --- a/iOS/View Controllers/StatusListViewController.swift +++ b/iOS/View Controllers/StatusListViewController.swift @@ -6,10 +6,10 @@ import Combine class StatusListViewController: UITableViewController { private let viewModel: StatusesViewModel private var cancellables = Set() - private var cellHeightCaches = [CGFloat: [Status: CGFloat]]() + private var cellHeightCaches = [CGFloat: [String: CGFloat]]() - private lazy var dataSource: UITableViewDiffableDataSource = { - UITableViewDiffableDataSource(tableView: tableView) { [weak self] tableView, indexPath, status in + private lazy var dataSource: UITableViewDiffableDataSource = { + UITableViewDiffableDataSource(tableView: tableView) { [weak self] tableView, indexPath, statusID in guard let self = self, let cell = tableView.dequeueReusableCell( @@ -17,9 +17,7 @@ class StatusListViewController: UITableViewController { for: indexPath) as? StatusTableViewCell else { return nil } - let statusViewModel = self.viewModel.statusViewModel(status: status) - - cell.viewModel = statusViewModel + cell.viewModel = self.viewModel.statusViewModel(id: statusID) cell.delegate = self return cell @@ -50,7 +48,7 @@ class StatusListViewController: UITableViewController { tableView.cellLayoutMarginsFollowReadableWidth = true tableView.separatorInset = .zero - viewModel.$statusSections.map { $0.snapshot() } + viewModel.$statusIDs .sink { [weak self] in guard let self = self else { return } @@ -58,16 +56,16 @@ class StatusListViewController: UITableViewController { if let id = self.viewModel.maintainScrollPositionOfStatusID, - let indexPath = self.indexPath(statusID: id), + let indexPath = self.dataSource.indexPath(for: 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) { + self.dataSource.apply($0.snapshot(), animatingDifferences: false) { if let id = self.viewModel.maintainScrollPositionOfStatusID, - let indexPath = self.indexPath(statusID: id), + let indexPath = self.dataSource.indexPath(for: id), let offsetFromNavigationBar = offsetFromNavigationBar { self.tableView.scrollToRow(at: indexPath, at: .top, animated: false) self.tableView.contentOffset.y -= offsetFromNavigationBar @@ -88,7 +86,7 @@ class StatusListViewController: UITableViewController { forRowAt indexPath: IndexPath) { guard let item = dataSource.itemIdentifier(for: indexPath) else { return } - var heightCache = cellHeightCaches[tableView.frame.width] ?? [Status: CGFloat]() + var heightCache = cellHeightCaches[tableView.frame.width] ?? [String: CGFloat]() heightCache[item] = cell.frame.height cellHeightCaches[tableView.frame.width] = heightCache @@ -101,41 +99,32 @@ class StatusListViewController: UITableViewController { } override func tableView(_ tableView: UITableView, shouldHighlightRowAt indexPath: IndexPath) -> Bool { - viewModel.statusSections[indexPath.section][indexPath.row].id != viewModel.contextParentID + guard let id = dataSource.itemIdentifier(for: indexPath) else { return true } + + return id != viewModel.contextParentID } override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - let status = viewModel.statusSections[indexPath.section][indexPath.row] + guard + let id = dataSource.itemIdentifier(for: indexPath), + let contextViewModel = viewModel.contextViewModel(id: id) + else { return } navigationController?.pushViewController( - StatusListViewController(viewModel: viewModel.contextViewModel(status: status)), + StatusListViewController(viewModel: contextViewModel), animated: true) } } extension StatusListViewController: StatusTableViewCellDelegate { func statusTableViewCellDidHaveShareButtonTapped(_ cell: StatusTableViewCell) { - guard let url = cell.viewModel.sharingURL else { return } + guard let url = cell.viewModel?.sharingURL else { return } share(url: url) } } private extension StatusListViewController { - func indexPath(statusID: String) -> IndexPath? { - for section in 0.. NSDiffableDataSourceSnapshot { - var snapshot = NSDiffableDataSourceSnapshot() +private extension Array where Element: Sequence, Element.Element: Hashable { + func snapshot() -> NSDiffableDataSourceSnapshot { + var snapshot = NSDiffableDataSourceSnapshot() let sections = [Int](0..