diff --git a/Reader/ItemListType.swift b/Reader/ItemListType.swift index 3bef529..7bb5f03 100644 --- a/Reader/ItemListType.swift +++ b/Reader/ItemListType.swift @@ -9,7 +9,7 @@ import Foundation import CoreData import Persistence -enum ItemListType: Hashable { +enum ItemListType: Hashable, Equatable { case unread case all case group(Group) @@ -52,7 +52,9 @@ enum ItemListType: Hashable { case .all: return nil case .group(let group): - req.predicate = NSPredicate(format: "read = NO AND feed in %@", group.feeds!) + // use the feed ids, because passing the NSSet of feeds into the predicate results in a multithreading violation if the request is used on a different context + let feedIDs = group.feeds!.map { ($0 as! Feed).objectID } + req.predicate = NSPredicate(format: "read = NO AND feed in %@", feedIDs) case .feed(let feed): req.predicate = NSPredicate(format: "read = NO AND feed = %@", feed) } diff --git a/Reader/Screens/Home/HomeCollectionViewCell.swift b/Reader/Screens/Home/HomeCollectionViewCell.swift index 8a1d05b..2ac2b97 100644 --- a/Reader/Screens/Home/HomeCollectionViewCell.swift +++ b/Reader/Screens/Home/HomeCollectionViewCell.swift @@ -7,9 +7,16 @@ import UIKit import Fervor +import Persistence +import OSLog + +private let signposter = OSSignposter(subsystem: "net.shadowfacts.Reader", category: "HomeCollectionViewCell") class HomeCollectionViewCell: UICollectionViewListCell { + private var currentItemListType: ItemListType? + private var itemCount: Int? + #if !targetEnvironment(macCatalyst) override func updateConfiguration(using state: UICellConfigurationState) { var backgroundConfig = UIBackgroundConfiguration.listGroupedCell().updated(for: state) @@ -22,4 +29,45 @@ class HomeCollectionViewCell: UICollectionViewListCell { } #endif + override func prepareForReuse() { + super.prepareForReuse() + itemCount = nil + } + + func updateUI(item: ItemListType, persistentContainer: PersistentContainer) { + self.currentItemListType = item + + var config = UIListContentConfiguration.valueCell() + config.text = item.title + if let itemCount { + config.secondaryText = itemCount.formatted(.number) + } + config.secondaryTextProperties.color = .tintColor + self.contentConfiguration = config + + Task(priority: .userInitiated) { + let state = signposter.beginInterval("fetch count", id: signposter.makeSignpostID()) + if let count = await fetchCount(item: item, in: persistentContainer), + self.currentItemListType == item { + self.itemCount = count + config.secondaryText = count.formatted(.number) + self.contentConfiguration = config + } + signposter.endInterval("fetch count", state) + } + } + + private func fetchCount(item: ItemListType, in persistentContainer: PersistentContainer) async -> Int? { + guard let request = item.countFetchRequest else { + return nil + } + return await withCheckedContinuation({ continuation in + let context = persistentContainer.backgroundContext + context.perform { + let count = try? context.count(for: request) + continuation.resume(returning: count) + } + }) + } + } diff --git a/Reader/Screens/Home/HomeViewController.swift b/Reader/Screens/Home/HomeViewController.swift index 69c53f9..49efe91 100644 --- a/Reader/Screens/Home/HomeViewController.swift +++ b/Reader/Screens/Home/HomeViewController.swift @@ -126,15 +126,8 @@ class HomeViewController: UIViewController { config.text = section.title supplementaryView.contentConfiguration = config } - let listCell = UICollectionView.CellRegistration { cell, indexPath, item in - var config = UIListContentConfiguration.valueCell() - config.text = item.title - if let req = item.countFetchRequest, - let count = try? self.fervorController.persistentContainer.viewContext.count(for: req) { - config.secondaryText = "\(count)" - config.secondaryTextProperties.color = .tintColor - } - cell.contentConfiguration = config + let listCell = UICollectionView.CellRegistration { [unowned self] cell, indexPath, item in + cell.updateUI(item: item, persistentContainer: self.fervorController.persistentContainer) cell.accessories = [.disclosureIndicator()] }