From 8acc303a80d6d956b723c80b68f43dc367995262 Mon Sep 17 00:00:00 2001 From: Shadowfacts Date: Sun, 9 Jan 2022 11:11:52 -0500 Subject: [PATCH] Display groups and feeds --- Reader/CoreData/PersistentContainer.swift | 2 + Reader/SceneDelegate.swift | 12 +- Reader/Screens/Home/HomeViewController.swift | 115 +++++++++++++++++-- 3 files changed, 114 insertions(+), 15 deletions(-) diff --git a/Reader/CoreData/PersistentContainer.swift b/Reader/CoreData/PersistentContainer.swift index 4df009b..2471cde 100644 --- a/Reader/CoreData/PersistentContainer.swift +++ b/Reader/CoreData/PersistentContainer.swift @@ -17,6 +17,7 @@ class PersistentContainer: NSPersistentContainer { private(set) lazy var backgroundContext: NSManagedObjectContext = { let context = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType) + // todo: should the background context really be parented to the view context, or should they both be direct children of the PSC? context.parent = self.viewContext return context }() @@ -58,6 +59,7 @@ class PersistentContainer: NSPersistentContainer { if self.backgroundContext.hasChanges { try self.backgroundContext.save() + try self.viewContext.save() } } } diff --git a/Reader/SceneDelegate.swift b/Reader/SceneDelegate.swift index f93db8b..5c068a9 100644 --- a/Reader/SceneDelegate.swift +++ b/Reader/SceneDelegate.swift @@ -44,12 +44,6 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { // Called when the scene has moved from an inactive state to an active state. // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive. } - - @MainActor - private func fetchFeeds() async { - let feeds = try! self.fervorController.persistentContainer.viewContext.fetch(Feed.fetchRequest()) - print(feeds) - } func sceneWillResignActive(_ scene: UIScene) { // Called when the scene will move from an active state to an inactive state. @@ -80,6 +74,12 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { await self.fetchFeeds() } } + + @MainActor + private func fetchFeeds() async { + let feeds = try! self.fervorController.persistentContainer.viewContext.fetch(Feed.fetchRequest()) + print(feeds) + } } diff --git a/Reader/Screens/Home/HomeViewController.swift b/Reader/Screens/Home/HomeViewController.swift index 6442df7..50caf80 100644 --- a/Reader/Screens/Home/HomeViewController.swift +++ b/Reader/Screens/Home/HomeViewController.swift @@ -6,11 +6,17 @@ // import UIKit +import CoreData -class HomeViewController: UIViewController { +class HomeViewController: UIViewController, UICollectionViewDelegate { let fervorController: FervorController + private var collectionView: UICollectionView! + private var dataSource: UICollectionViewDiffableDataSource! + private var groupResultsController: NSFetchedResultsController! + private var feedResultsController: NSFetchedResultsController! + init(fervorController: FervorController) { self.fervorController = fervorController @@ -26,14 +32,105 @@ class HomeViewController: UIViewController { view.backgroundColor = .systemBackground - let label = UILabel() - label.translatesAutoresizingMaskIntoConstraints = false - label.text = "Logged in to \(fervorController.instanceURL.host!)" - view.addSubview(label) - NSLayoutConstraint.activate([ - label.centerXAnchor.constraint(equalTo: view.centerXAnchor), - label.centerYAnchor.constraint(equalTo: view.centerYAnchor), - ]) +// let label = UILabel() +// label.translatesAutoresizingMaskIntoConstraints = false +// label.text = "Logged in to \(fervorController.instanceURL.host!)" +// view.addSubview(label) +// NSLayoutConstraint.activate([ +// label.centerXAnchor.constraint(equalTo: view.centerXAnchor), +// label.centerYAnchor.constraint(equalTo: view.centerYAnchor), +// ]) + + let configuration = UICollectionLayoutListConfiguration(appearance: .insetGrouped) + let layout = UICollectionViewCompositionalLayout.list(using: configuration) + collectionView = UICollectionView(frame: view.bounds, collectionViewLayout: layout) + collectionView.autoresizingMask = [.flexibleWidth, .flexibleHeight] + collectionView.delegate = self + view.addSubview(collectionView) + + dataSource = createDataSource() + + // todo: maybe NSFetchedResultsController to automatically update when the database changes? +// applyInitialSnapshot() + var snapshot = NSDiffableDataSourceSnapshot() + snapshot.appendSections([.groups, .feeds]) + dataSource.apply(snapshot, animatingDifferences: false) + + let groupReq = Group.fetchRequest() + groupReq.sortDescriptors = [NSSortDescriptor(key: "title", ascending: true)] + groupResultsController = NSFetchedResultsController(fetchRequest: groupReq, managedObjectContext: fervorController.persistentContainer.viewContext, sectionNameKeyPath: nil, cacheName: nil) + groupResultsController.delegate = self + try! groupResultsController.performFetch() + + let feedReq = Feed.fetchRequest() + feedReq.sortDescriptors = [NSSortDescriptor(key: "title", ascending: true)] + feedResultsController = NSFetchedResultsController(fetchRequest: feedReq, managedObjectContext: fervorController.persistentContainer.viewContext, sectionNameKeyPath: nil, cacheName: nil) + feedResultsController.delegate = self + try! feedResultsController.performFetch() } + private func createDataSource() -> UICollectionViewDiffableDataSource { + let listCell = UICollectionView.CellRegistration { cell, indexPath, item in + var config = cell.defaultContentConfiguration() + config.text = item.title + cell.contentConfiguration = config + + cell.accessories = [.disclosureIndicator()] + } + let dataSource = UICollectionViewDiffableDataSource(collectionView: collectionView) { collectionView, indexPath, item in + return collectionView.dequeueConfiguredReusableCell(using: listCell, for: indexPath, item: item) + } + return dataSource + } + +// private func applyInitialSnapshot() { +// let groups = try! fervorController.persistentContainer.viewContext.fetch(Group.fetchRequest()) +// let feeds = try! fervorController.persistentContainer.viewContext.fetch(Feed.fetchRequest()) +// +// var snapshot = NSDiffableDataSourceSnapshot() +// snapshot.appendSections([.groups, .feeds]) +// snapshot.appendItems(groups.map { .group($0) }, toSection: .groups) +// snapshot.appendItems(feeds.map { .feed($0) }, toSection: .feeds) +// dataSource.apply(snapshot, animatingDifferences: false) +// } + +} + +extension HomeViewController { + enum Section: Hashable { + case groups + case feeds + } + enum Item: Hashable { + case group(Group) + case feed(Feed) + + var title: String { + switch self { + case let .group(group): + // todo: manual codegen for models so this force unwrap isn't necessary + return group.title! + case let .feed(feed): + return feed.title! + } + } + } +} + +extension HomeViewController: NSFetchedResultsControllerDelegate { + func controller(_ controller: NSFetchedResultsController, didChangeContentWith snapshot: NSDiffableDataSourceSnapshotReference) { + var snapshot = dataSource.snapshot() + if controller == groupResultsController { + if snapshot.sectionIdentifiers.contains(.groups) { + snapshot.deleteItems(snapshot.itemIdentifiers(inSection: .groups)) + } + snapshot.appendItems(controller.fetchedObjects!.map { .group($0 as! Group) }, toSection: .groups) + } else if controller == feedResultsController { + if snapshot.sectionIdentifiers.contains(.feeds) { + snapshot.deleteItems(snapshot.itemIdentifiers(inSection: .feeds)) + } + snapshot.appendItems(controller.fetchedObjects!.map { .feed($0 as! Feed) }, toSection: .feeds) + } + dataSource.apply(snapshot, animatingDifferences: false) + } }