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
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<AnyCancellable>()
@ -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
}
}

View file

@ -6,10 +6,10 @@ import Combine
class StatusListViewController: UITableViewController {
private let viewModel: StatusesViewModel
private var cancellables = Set<AnyCancellable>()
private var cellHeightCaches = [CGFloat: [Status: CGFloat]]()
private var cellHeightCaches = [CGFloat: [String: CGFloat]]()
private lazy var dataSource: UITableViewDiffableDataSource<Int, Status> = {
UITableViewDiffableDataSource(tableView: tableView) { [weak self] tableView, indexPath, status in
private lazy var dataSource: UITableViewDiffableDataSource<Int, String> = {
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..<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) {
let activityViewController = UIActivityViewController(activityItems: [url], applicationActivities: nil)
@ -143,16 +132,16 @@ private extension StatusListViewController {
}
}
private extension Array where Element == [Status] {
func snapshot() -> NSDiffableDataSourceSnapshot<Int, Status> {
var snapshot = NSDiffableDataSourceSnapshot<Int, Status>()
private extension Array where Element: Sequence, Element.Element: Hashable {
func snapshot() -> NSDiffableDataSourceSnapshot<Int, Element.Element> {
var snapshot = NSDiffableDataSourceSnapshot<Int, Element.Element>()
let sections = [Int](0..<count)
snapshot.appendSections(sections)
for section in sections {
snapshot.appendItems(self[section], toSection: section)
snapshot.appendItems(self[section].map { $0 }, toSection: section)
}
return snapshot

View file

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