Refactoring

This commit is contained in:
Justin Mazzocchi 2020-08-26 01:25:34 -07:00
parent 9e40c45b0f
commit e74daa4df0
No known key found for this signature in database
GPG Key ID: E223E6937AAFB01C
3 changed files with 41 additions and 43 deletions

View File

@ -4,10 +4,11 @@ import Foundation
import Combine import Combine
class StatusesViewModel: ObservableObject { class StatusesViewModel: ObservableObject {
@Published private(set) var statusSections = [[Status]]() @Published private(set) var statusIDs = [[String]]()
@Published var alertItem: AlertItem? @Published var alertItem: AlertItem?
@Published private(set) var loading = false @Published private(set) var loading = false
private(set) var maintainScrollPositionOfStatusID: String? private(set) var maintainScrollPositionOfStatusID: String?
private var statuses = [String: Status]()
private let statusListService: StatusListService private let statusListService: StatusListService
private var statusViewModelCache = [Status: (StatusViewModel, AnyCancellable)]() private var statusViewModelCache = [Status: (StatusViewModel, AnyCancellable)]()
private var cancellables = Set<AnyCancellable>() private var cancellables = Set<AnyCancellable>()
@ -19,9 +20,11 @@ class StatusesViewModel: ObservableObject {
.handleEvents(receiveOutput: { [weak self] in .handleEvents(receiveOutput: { [weak self] in
self?.determineIfScrollPositionShouldBeMaintained(newStatusSections: $0) self?.determineIfScrollPositionShouldBeMaintained(newStatusSections: $0)
self?.cleanViewModelCache(newStatusSections: $0) self?.cleanViewModelCache(newStatusSections: $0)
self?.statuses = Dictionary(uniqueKeysWithValues: $0.reduce([], +).map { ($0.id, $0) })
}) })
.assignErrorsToAlertItem(to: \.alertItem, on: self) .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) .store(in: &cancellables)
} }
func statusViewModel(status: Status) -> StatusViewModel { func statusViewModel(id: String) -> StatusViewModel? {
guard let status = statuses[id] else { return nil }
var statusViewModel: StatusViewModel var statusViewModel: StatusViewModel
if let cachedViewModel = statusViewModelCache[status]?.0 { if let cachedViewModel = statusViewModelCache[status]?.0 {
@ -59,8 +64,10 @@ extension StatusesViewModel {
return statusViewModel return statusViewModel
} }
func contextViewModel(status: Status) -> StatusesViewModel { func contextViewModel(id: String) -> StatusesViewModel? {
StatusesViewModel(statusListService: statusListService.contextService(status: status)) 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 maintainScrollPositionOfStatusID = nil // clear old value
// Maintain scroll position of parent after initial load of context // 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 maintainScrollPositionOfStatusID = contextParentID
} }
} }

View File

@ -6,10 +6,10 @@ import Combine
class StatusListViewController: UITableViewController { class StatusListViewController: UITableViewController {
private let viewModel: StatusesViewModel private let viewModel: StatusesViewModel
private var cancellables = Set<AnyCancellable>() private var cancellables = Set<AnyCancellable>()
private var cellHeightCaches = [CGFloat: [Status: CGFloat]]() private var cellHeightCaches = [CGFloat: [String: CGFloat]]()
private lazy var dataSource: UITableViewDiffableDataSource<Int, Status> = { private lazy var dataSource: UITableViewDiffableDataSource<Int, String> = {
UITableViewDiffableDataSource(tableView: tableView) { [weak self] tableView, indexPath, status in UITableViewDiffableDataSource(tableView: tableView) { [weak self] tableView, indexPath, statusID in
guard guard
let self = self, let self = self,
let cell = tableView.dequeueReusableCell( let cell = tableView.dequeueReusableCell(
@ -17,9 +17,7 @@ class StatusListViewController: UITableViewController {
for: indexPath) as? StatusTableViewCell for: indexPath) as? StatusTableViewCell
else { return nil } else { return nil }
let statusViewModel = self.viewModel.statusViewModel(status: status) cell.viewModel = self.viewModel.statusViewModel(id: statusID)
cell.viewModel = statusViewModel
cell.delegate = self cell.delegate = self
return cell return cell
@ -50,7 +48,7 @@ class StatusListViewController: UITableViewController {
tableView.cellLayoutMarginsFollowReadableWidth = true tableView.cellLayoutMarginsFollowReadableWidth = true
tableView.separatorInset = .zero tableView.separatorInset = .zero
viewModel.$statusSections.map { $0.snapshot() } viewModel.$statusIDs
.sink { [weak self] in .sink { [weak self] in
guard let self = self else { return } guard let self = self else { return }
@ -58,16 +56,16 @@ class StatusListViewController: UITableViewController {
if if
let id = self.viewModel.maintainScrollPositionOfStatusID, let id = self.viewModel.maintainScrollPositionOfStatusID,
let indexPath = self.indexPath(statusID: id), let indexPath = self.dataSource.indexPath(for: id),
let navigationBar = self.navigationController?.navigationBar { let navigationBar = self.navigationController?.navigationBar {
let navigationBarMaxY = self.tableView.convert(navigationBar.bounds, from: navigationBar).maxY let navigationBarMaxY = self.tableView.convert(navigationBar.bounds, from: navigationBar).maxY
offsetFromNavigationBar = self.tableView.rectForRow(at: indexPath).origin.y - navigationBarMaxY offsetFromNavigationBar = self.tableView.rectForRow(at: indexPath).origin.y - navigationBarMaxY
} }
self.dataSource.apply($0, animatingDifferences: false) { self.dataSource.apply($0.snapshot(), animatingDifferences: false) {
if if
let id = self.viewModel.maintainScrollPositionOfStatusID, let id = self.viewModel.maintainScrollPositionOfStatusID,
let indexPath = self.indexPath(statusID: id), let indexPath = self.dataSource.indexPath(for: id),
let offsetFromNavigationBar = offsetFromNavigationBar { let offsetFromNavigationBar = offsetFromNavigationBar {
self.tableView.scrollToRow(at: indexPath, at: .top, animated: false) self.tableView.scrollToRow(at: indexPath, at: .top, animated: false)
self.tableView.contentOffset.y -= offsetFromNavigationBar self.tableView.contentOffset.y -= offsetFromNavigationBar
@ -88,7 +86,7 @@ class StatusListViewController: UITableViewController {
forRowAt indexPath: IndexPath) { forRowAt indexPath: IndexPath) {
guard let item = dataSource.itemIdentifier(for: indexPath) else { return } 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 heightCache[item] = cell.frame.height
cellHeightCaches[tableView.frame.width] = heightCache cellHeightCaches[tableView.frame.width] = heightCache
@ -101,41 +99,32 @@ class StatusListViewController: UITableViewController {
} }
override func tableView(_ tableView: UITableView, shouldHighlightRowAt indexPath: IndexPath) -> Bool { 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) { 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( navigationController?.pushViewController(
StatusListViewController(viewModel: viewModel.contextViewModel(status: status)), StatusListViewController(viewModel: contextViewModel),
animated: true) animated: true)
} }
} }
extension StatusListViewController: StatusTableViewCellDelegate { extension StatusListViewController: StatusTableViewCellDelegate {
func statusTableViewCellDidHaveShareButtonTapped(_ cell: StatusTableViewCell) { func statusTableViewCellDidHaveShareButtonTapped(_ cell: StatusTableViewCell) {
guard let url = cell.viewModel.sharingURL else { return } guard let url = cell.viewModel?.sharingURL else { return }
share(url: url) share(url: url)
} }
} }
private extension StatusListViewController { private extension StatusListViewController {
func indexPath(statusID: String) -> IndexPath? {
for section in 0..<dataSource.numberOfSections(in: tableView) {
for row in 0..<dataSource.tableView(tableView, numberOfRowsInSection: section) {
let indexPath = IndexPath(row: row, section: section)
if dataSource.itemIdentifier(for: indexPath)?.id == statusID {
return indexPath
}
}
}
return nil
}
func share(url: URL) { func share(url: URL) {
let activityViewController = UIActivityViewController(activityItems: [url], applicationActivities: nil) let activityViewController = UIActivityViewController(activityItems: [url], applicationActivities: nil)
@ -143,16 +132,16 @@ private extension StatusListViewController {
} }
} }
private extension Array where Element == [Status] { private extension Array where Element: Sequence, Element.Element: Hashable {
func snapshot() -> NSDiffableDataSourceSnapshot<Int, Status> { func snapshot() -> NSDiffableDataSourceSnapshot<Int, Element.Element> {
var snapshot = NSDiffableDataSourceSnapshot<Int, Status>() var snapshot = NSDiffableDataSourceSnapshot<Int, Element.Element>()
let sections = [Int](0..<count) let sections = [Int](0..<count)
snapshot.appendSections(sections) snapshot.appendSections(sections)
for section in sections { for section in sections {
snapshot.appendItems(self[section], toSection: section) snapshot.appendItems(self[section].map { $0 }, toSection: section)
} }
return snapshot return snapshot

View File

@ -58,8 +58,10 @@ class StatusTableViewCell: UITableViewCell {
@IBOutlet private var separatorConstraints: [NSLayoutConstraint]! @IBOutlet private var separatorConstraints: [NSLayoutConstraint]!
var viewModel: StatusViewModel! { var viewModel: StatusViewModel? {
didSet { didSet {
guard let viewModel = viewModel else { return }
let mutableContent = NSMutableAttributedString(attributedString: viewModel.content) let mutableContent = NSMutableAttributedString(attributedString: viewModel.content)
let mutableDisplayName = NSMutableAttributedString(string: viewModel.displayName) let mutableDisplayName = NSMutableAttributedString(string: viewModel.displayName)
let mutableSpoilerText = NSMutableAttributedString(string: viewModel.spoilerText) let mutableSpoilerText = NSMutableAttributedString(string: viewModel.spoilerText)
@ -274,7 +276,7 @@ extension StatusTableViewCell {
} }
@IBAction func favoriteButtonTapped(_ sender: UIButton) { @IBAction func favoriteButtonTapped(_ sender: UIButton) {
viewModel.toggleFavorited() viewModel?.toggleFavorited()
} }
@IBAction func actionsButtonTapped(_ sender: Any) { @IBAction func actionsButtonTapped(_ sender: Any) {
@ -325,7 +327,7 @@ private extension StatusTableViewCell {
let reblogColor: UIColor = reblogged ? .systemGreen : .secondaryLabel let reblogColor: UIColor = reblogged ? .systemGreen : .secondaryLabel
let reblogButton: UIButton let reblogButton: UIButton
if viewModel.isContextParent { if viewModel?.isContextParent ?? false {
reblogButton = contextParentReblogButton reblogButton = contextParentReblogButton
} else { } else {
reblogButton = self.reblogButton reblogButton = self.reblogButton
@ -340,7 +342,7 @@ private extension StatusTableViewCell {
let favoriteButton: UIButton let favoriteButton: UIButton
let scale: UIImage.SymbolScale let scale: UIImage.SymbolScale
if viewModel.isContextParent { if viewModel?.isContextParent ?? false {
favoriteButton = contextParentFavoriteButton favoriteButton = contextParentFavoriteButton
scale = .medium scale = .medium
} else { } else {