chore: Implement FollowedTagsViewModel+DiffableDataSource
This commit is contained in:
parent
0c571a2df6
commit
75dc530dcf
|
@ -23,6 +23,7 @@
|
||||||
0FB3D33825E6401400AAD544 /* PickServerCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0FB3D33725E6401400AAD544 /* PickServerCell.swift */; };
|
0FB3D33825E6401400AAD544 /* PickServerCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0FB3D33725E6401400AAD544 /* PickServerCell.swift */; };
|
||||||
164F0EBC267D4FE400249499 /* BoopSound.caf in Resources */ = {isa = PBXBuildFile; fileRef = 164F0EBB267D4FE400249499 /* BoopSound.caf */; };
|
164F0EBC267D4FE400249499 /* BoopSound.caf in Resources */ = {isa = PBXBuildFile; fileRef = 164F0EBB267D4FE400249499 /* BoopSound.caf */; };
|
||||||
18BC7629F65E6DB12CB8416D /* Pods_Mastodon_MastodonUITests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3C030226D3C73DCC23D67452 /* Pods_Mastodon_MastodonUITests.framework */; };
|
18BC7629F65E6DB12CB8416D /* Pods_Mastodon_MastodonUITests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3C030226D3C73DCC23D67452 /* Pods_Mastodon_MastodonUITests.framework */; };
|
||||||
|
2A1FE47C2938BB2600784BF1 /* FollowedTagsViewModel+DiffableDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A1FE47B2938BB2600784BF1 /* FollowedTagsViewModel+DiffableDataSource.swift */; };
|
||||||
2A3F6FE3292ECB5E002E6DA7 /* FollowedTagsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A3F6FE2292ECB5E002E6DA7 /* FollowedTagsViewModel.swift */; };
|
2A3F6FE3292ECB5E002E6DA7 /* FollowedTagsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A3F6FE2292ECB5E002E6DA7 /* FollowedTagsViewModel.swift */; };
|
||||||
2A3F6FE5292F6E44002E6DA7 /* FollowedTagsTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A3F6FE4292F6E44002E6DA7 /* FollowedTagsTableViewCell.swift */; };
|
2A3F6FE5292F6E44002E6DA7 /* FollowedTagsTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A3F6FE4292F6E44002E6DA7 /* FollowedTagsTableViewCell.swift */; };
|
||||||
2A506CF4292CD85800059C37 /* FollowedTagsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A506CF3292CD85800059C37 /* FollowedTagsViewController.swift */; };
|
2A506CF4292CD85800059C37 /* FollowedTagsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A506CF3292CD85800059C37 /* FollowedTagsViewController.swift */; };
|
||||||
|
@ -525,6 +526,7 @@
|
||||||
0FB3D33725E6401400AAD544 /* PickServerCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PickServerCell.swift; sourceTree = "<group>"; };
|
0FB3D33725E6401400AAD544 /* PickServerCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PickServerCell.swift; sourceTree = "<group>"; };
|
||||||
164F0EBB267D4FE400249499 /* BoopSound.caf */ = {isa = PBXFileReference; lastKnownFileType = file; path = BoopSound.caf; sourceTree = "<group>"; };
|
164F0EBB267D4FE400249499 /* BoopSound.caf */ = {isa = PBXFileReference; lastKnownFileType = file; path = BoopSound.caf; sourceTree = "<group>"; };
|
||||||
1D6D967E77A5357E2C6110D9 /* Pods-Mastodon.asdk - debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Mastodon.asdk - debug.xcconfig"; path = "Target Support Files/Pods-Mastodon/Pods-Mastodon.asdk - debug.xcconfig"; sourceTree = "<group>"; };
|
1D6D967E77A5357E2C6110D9 /* Pods-Mastodon.asdk - debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Mastodon.asdk - debug.xcconfig"; path = "Target Support Files/Pods-Mastodon/Pods-Mastodon.asdk - debug.xcconfig"; sourceTree = "<group>"; };
|
||||||
|
2A1FE47B2938BB2600784BF1 /* FollowedTagsViewModel+DiffableDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "FollowedTagsViewModel+DiffableDataSource.swift"; sourceTree = "<group>"; };
|
||||||
2A3F6FE2292ECB5E002E6DA7 /* FollowedTagsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FollowedTagsViewModel.swift; sourceTree = "<group>"; };
|
2A3F6FE2292ECB5E002E6DA7 /* FollowedTagsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FollowedTagsViewModel.swift; sourceTree = "<group>"; };
|
||||||
2A3F6FE4292F6E44002E6DA7 /* FollowedTagsTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FollowedTagsTableViewCell.swift; sourceTree = "<group>"; };
|
2A3F6FE4292F6E44002E6DA7 /* FollowedTagsTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FollowedTagsTableViewCell.swift; sourceTree = "<group>"; };
|
||||||
2A506CF3292CD85800059C37 /* FollowedTagsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FollowedTagsViewController.swift; sourceTree = "<group>"; };
|
2A506CF3292CD85800059C37 /* FollowedTagsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FollowedTagsViewController.swift; sourceTree = "<group>"; };
|
||||||
|
@ -1243,6 +1245,7 @@
|
||||||
children = (
|
children = (
|
||||||
2A506CF3292CD85800059C37 /* FollowedTagsViewController.swift */,
|
2A506CF3292CD85800059C37 /* FollowedTagsViewController.swift */,
|
||||||
2A3F6FE2292ECB5E002E6DA7 /* FollowedTagsViewModel.swift */,
|
2A3F6FE2292ECB5E002E6DA7 /* FollowedTagsViewModel.swift */,
|
||||||
|
2A1FE47B2938BB2600784BF1 /* FollowedTagsViewModel+DiffableDataSource.swift */,
|
||||||
2A3F6FE4292F6E44002E6DA7 /* FollowedTagsTableViewCell.swift */,
|
2A3F6FE4292F6E44002E6DA7 /* FollowedTagsTableViewCell.swift */,
|
||||||
);
|
);
|
||||||
path = FollowedTags;
|
path = FollowedTags;
|
||||||
|
@ -3398,6 +3401,7 @@
|
||||||
DB938EED2623F79B00E5B6C1 /* ThreadViewModel.swift in Sources */,
|
DB938EED2623F79B00E5B6C1 /* ThreadViewModel.swift in Sources */,
|
||||||
DB6988DE2848D11C002398EF /* PagerTabStripNavigateable.swift in Sources */,
|
DB6988DE2848D11C002398EF /* PagerTabStripNavigateable.swift in Sources */,
|
||||||
2DCB73FD2615C13900EC03D4 /* SearchRecommendCollectionHeader.swift in Sources */,
|
2DCB73FD2615C13900EC03D4 /* SearchRecommendCollectionHeader.swift in Sources */,
|
||||||
|
2A1FE47C2938BB2600784BF1 /* FollowedTagsViewModel+DiffableDataSource.swift in Sources */,
|
||||||
DB852D1C26FB021500FC9D81 /* RootSplitViewController.swift in Sources */,
|
DB852D1C26FB021500FC9D81 /* RootSplitViewController.swift in Sources */,
|
||||||
DB697DD1278F4871004EF2F7 /* AutoGenerateTableViewDelegate.swift in Sources */,
|
DB697DD1278F4871004EF2F7 /* AutoGenerateTableViewDelegate.swift in Sources */,
|
||||||
DB02CDBF2625AE5000D0A2AF /* AdaptiveUserInterfaceStyleBarButtonItem.swift in Sources */,
|
DB02CDBF2625AE5000D0A2AF /* AdaptiveUserInterfaceStyleBarButtonItem.swift in Sources */,
|
||||||
|
|
|
@ -0,0 +1,51 @@
|
||||||
|
//
|
||||||
|
// FollowedTagsViewModel+DiffableDataSource.swift
|
||||||
|
// Mastodon
|
||||||
|
//
|
||||||
|
// Created by Marcus Kida on 01.12.22.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
import Combine
|
||||||
|
import CoreData
|
||||||
|
import CoreDataStack
|
||||||
|
import MastodonSDK
|
||||||
|
import MastodonCore
|
||||||
|
|
||||||
|
extension FollowedTagsViewModel {
|
||||||
|
enum Section: Hashable {
|
||||||
|
case main
|
||||||
|
}
|
||||||
|
|
||||||
|
enum Item: Hashable {
|
||||||
|
case hashtag(Tag)
|
||||||
|
}
|
||||||
|
|
||||||
|
func tableViewDiffableDataSource(
|
||||||
|
for tableView: UITableView
|
||||||
|
) -> UITableViewDiffableDataSource<Section, Item> {
|
||||||
|
UITableViewDiffableDataSource(tableView: tableView) { tableView, indexPath, item in
|
||||||
|
switch item {
|
||||||
|
case let .hashtag(tag):
|
||||||
|
guard let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: FollowedTagsTableViewCell.self), for: indexPath) as? FollowedTagsTableViewCell else {
|
||||||
|
assertionFailure()
|
||||||
|
return UITableViewCell()
|
||||||
|
}
|
||||||
|
|
||||||
|
cell.setup(self)
|
||||||
|
cell.populate(with: tag)
|
||||||
|
return cell
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func setupDiffableDataSource(
|
||||||
|
tableView: UITableView
|
||||||
|
) {
|
||||||
|
diffableDataSource = tableViewDiffableDataSource(for: tableView)
|
||||||
|
|
||||||
|
var snapshot = NSDiffableDataSourceSnapshot<Section, Item>()
|
||||||
|
snapshot.appendSections([.main])
|
||||||
|
diffableDataSource?.apply(snapshot)
|
||||||
|
}
|
||||||
|
}
|
|
@ -19,6 +19,7 @@ final class FollowedTagsViewModel: NSObject {
|
||||||
let fetchedResultsController: FollowedTagsFetchedResultController
|
let fetchedResultsController: FollowedTagsFetchedResultController
|
||||||
|
|
||||||
private weak var tableView: UITableView?
|
private weak var tableView: UITableView?
|
||||||
|
var diffableDataSource: UITableViewDiffableDataSource<Section, Item>?
|
||||||
|
|
||||||
// input
|
// input
|
||||||
let context: AppContext
|
let context: AppContext
|
||||||
|
@ -35,12 +36,18 @@ final class FollowedTagsViewModel: NSObject {
|
||||||
domain: authContext.mastodonAuthenticationBox.domain,
|
domain: authContext.mastodonAuthenticationBox.domain,
|
||||||
user: authContext.mastodonAuthenticationBox.authenticationRecord.object(in: context.managedObjectContext)!.user
|
user: authContext.mastodonAuthenticationBox.authenticationRecord.object(in: context.managedObjectContext)!.user
|
||||||
)
|
)
|
||||||
|
|
||||||
super.init()
|
super.init()
|
||||||
|
|
||||||
self.fetchedResultsController
|
self.fetchedResultsController
|
||||||
.$records
|
.$records
|
||||||
.receive(on: DispatchQueue.main)
|
.receive(on: DispatchQueue.main)
|
||||||
.sink { [weak self] _ in
|
.sink { [weak self] records in
|
||||||
self?.tableView?.reloadSections(IndexSet(integer: 0), with: .automatic)
|
guard let self = self else { return }
|
||||||
|
var snapshot = NSDiffableDataSourceSnapshot<Section, Item>()
|
||||||
|
snapshot.appendSections([.main])
|
||||||
|
snapshot.appendItems(records.map {.hashtag($0) })
|
||||||
|
self.diffableDataSource?.applySnapshot(snapshot, animated: true)
|
||||||
}
|
}
|
||||||
.store(in: &disposeBag)
|
.store(in: &disposeBag)
|
||||||
}
|
}
|
||||||
|
@ -49,28 +56,21 @@ final class FollowedTagsViewModel: NSObject {
|
||||||
extension FollowedTagsViewModel {
|
extension FollowedTagsViewModel {
|
||||||
func setupTableView(_ tableView: UITableView) {
|
func setupTableView(_ tableView: UITableView) {
|
||||||
self.tableView = tableView
|
self.tableView = tableView
|
||||||
tableView.dataSource = self
|
setupDiffableDataSource(tableView: tableView)
|
||||||
tableView.delegate = self
|
tableView.delegate = self
|
||||||
|
|
||||||
fetchFollowedTags {
|
fetchFollowedTags()
|
||||||
tableView.reloadData()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func fetchFollowedTags(_ done: @escaping () -> Void) {
|
func fetchFollowedTags() {
|
||||||
Task { @MainActor [weak self] in
|
Task { @MainActor in
|
||||||
try? await self?._fetchFollowedTags()
|
|
||||||
done()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private func _fetchFollowedTags() async throws {
|
|
||||||
try await context.apiService.getFollowedTags(
|
try await context.apiService.getFollowedTags(
|
||||||
domain: authContext.mastodonAuthenticationBox.domain,
|
domain: authContext.mastodonAuthenticationBox.domain,
|
||||||
query: Mastodon.API.Account.FollowedTagsQuery(limit: nil),
|
query: Mastodon.API.Account.FollowedTagsQuery(limit: nil),
|
||||||
authenticationBox: authContext.mastodonAuthenticationBox
|
authenticationBox: authContext.mastodonAuthenticationBox
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func followOrUnfollow(_ tag: Tag) {
|
func followOrUnfollow(_ tag: Tag) {
|
||||||
Task { @MainActor in
|
Task { @MainActor in
|
||||||
|
@ -86,53 +86,24 @@ extension FollowedTagsViewModel {
|
||||||
authenticationBox: authContext.mastodonAuthenticationBox
|
authenticationBox: authContext.mastodonAuthenticationBox
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
try? await _fetchFollowedTags()
|
fetchFollowedTags()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension FollowedTagsViewModel: UITableViewDataSource {
|
|
||||||
func numberOfSections(in tableView: UITableView) -> Int {
|
|
||||||
1
|
|
||||||
}
|
|
||||||
|
|
||||||
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
|
||||||
fetchedResultsController.records.count
|
|
||||||
}
|
|
||||||
|
|
||||||
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
|
||||||
guard
|
|
||||||
indexPath.section == 0,
|
|
||||||
let object = fetchedResultsController.records[indexPath.row].object(in: context.managedObjectContext)
|
|
||||||
else {
|
|
||||||
return UITableViewCell()
|
|
||||||
}
|
|
||||||
|
|
||||||
let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: FollowedTagsTableViewCell.self), for: indexPath) as! FollowedTagsTableViewCell
|
|
||||||
|
|
||||||
cell.setup(self)
|
|
||||||
cell.populate(with: object)
|
|
||||||
return cell
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension FollowedTagsViewModel: UITableViewDelegate {
|
extension FollowedTagsViewModel: UITableViewDelegate {
|
||||||
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
||||||
logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): \(indexPath)")
|
logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): \(indexPath)")
|
||||||
tableView.deselectRow(at: indexPath, animated: true)
|
tableView.deselectRow(at: indexPath, animated: true)
|
||||||
|
|
||||||
guard
|
let object = fetchedResultsController.records[indexPath.row]
|
||||||
indexPath.section == 0,
|
|
||||||
let object = fetchedResultsController.records[indexPath.row].object(in: context.managedObjectContext)
|
|
||||||
else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
let hashtagTimelineViewModel = HashtagTimelineViewModel(
|
let hashtagTimelineViewModel = HashtagTimelineViewModel(
|
||||||
context: self.context,
|
context: self.context,
|
||||||
authContext: self.authContext,
|
authContext: self.authContext,
|
||||||
hashtag: object.name
|
hashtag: object.name
|
||||||
)
|
)
|
||||||
|
|
||||||
presentHashtagTimeline.send(hashtagTimelineViewModel)
|
presentHashtagTimeline.send(hashtagTimelineViewModel)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,8 +23,7 @@ public final class FollowedTagsFetchedResultController: NSObject {
|
||||||
@Published public var user: MastodonUser? = nil
|
@Published public var user: MastodonUser? = nil
|
||||||
|
|
||||||
// output
|
// output
|
||||||
let _objectIDs = CurrentValueSubject<[NSManagedObjectID], Never>([])
|
@Published public private(set) var records: [Tag] = []
|
||||||
@Published public private(set) var records: [ManagedObjectRecord<Tag>] = []
|
|
||||||
|
|
||||||
public init(managedObjectContext: NSManagedObjectContext, domain: String, user: MastodonUser) {
|
public init(managedObjectContext: NSManagedObjectContext, domain: String, user: MastodonUser) {
|
||||||
self.domain = domain
|
self.domain = domain
|
||||||
|
@ -45,12 +44,6 @@ public final class FollowedTagsFetchedResultController: NSObject {
|
||||||
}()
|
}()
|
||||||
super.init()
|
super.init()
|
||||||
|
|
||||||
// debounce output to prevent UI update issues
|
|
||||||
_objectIDs
|
|
||||||
.throttle(for: 0.1, scheduler: DispatchQueue.main, latest: true)
|
|
||||||
.map { objectIDs in objectIDs.map { ManagedObjectRecord(objectID: $0) } }
|
|
||||||
.assign(to: &$records)
|
|
||||||
|
|
||||||
fetchedResultsController.delegate = self
|
fetchedResultsController.delegate = self
|
||||||
try? fetchedResultsController.performFetch()
|
try? fetchedResultsController.performFetch()
|
||||||
|
|
||||||
|
@ -75,11 +68,10 @@ public final class FollowedTagsFetchedResultController: NSObject {
|
||||||
|
|
||||||
// MARK: - NSFetchedResultsControllerDelegate
|
// MARK: - NSFetchedResultsControllerDelegate
|
||||||
extension FollowedTagsFetchedResultController: NSFetchedResultsControllerDelegate {
|
extension FollowedTagsFetchedResultController: NSFetchedResultsControllerDelegate {
|
||||||
public func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
|
public func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChangeContentWith snapshot: NSDiffableDataSourceSnapshotReference) {
|
||||||
os_log("%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function)
|
os_log("%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function)
|
||||||
|
|
||||||
let objects = fetchedResultsController.fetchedObjects ?? []
|
let objects = fetchedResultsController.fetchedObjects ?? []
|
||||||
self._objectIDs.value = objects.map { $0.objectID }
|
self.records = objects
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue