// // ItemsViewController.swift // Reader // // Created by Shadowfacts on 1/9/22. // import UIKit import CoreData import SafariServices class ItemsViewController: UIViewController { let fervorController: FervorController let fetchRequest: NSFetchRequest private var collectionView: UICollectionView! private var dataSource: UICollectionViewDiffableDataSource! private var resultsController: NSFetchedResultsController! init(fetchRequest: NSFetchRequest, fervorController: FervorController) { self.fervorController = fervorController self.fetchRequest = fetchRequest super.init(nibName: nil, bundle: nil) } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func viewDidLoad() { super.viewDidLoad() var configuration = UICollectionLayoutListConfiguration(appearance: .plain) configuration.backgroundColor = .appBackground let layout = UICollectionViewCompositionalLayout.list(using: configuration) collectionView = UICollectionView(frame: view.bounds, collectionViewLayout: layout) collectionView.delegate = self collectionView.autoresizingMask = [.flexibleWidth, .flexibleHeight] collectionView.register(ItemCollectionViewCell.self, forCellWithReuseIdentifier: "itemCell") view.addSubview(collectionView) dataSource = createDataSource() var snapshot = NSDiffableDataSourceSnapshot() snapshot.appendSections([.items]) dataSource.apply(snapshot, animatingDifferences: false) fetchRequest.sortDescriptors = [NSSortDescriptor(key: "published", ascending: false)] resultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: fervorController.persistentContainer.viewContext, sectionNameKeyPath: nil, cacheName: nil) resultsController.delegate = self try! resultsController.performFetch() } private func createDataSource() -> UICollectionViewDiffableDataSource { let itemCell = UICollectionView.CellRegistration { [unowned self] cell, indexPath, item in cell.updateUI(item: item) cell.delegate = self } let dataSource = UICollectionViewDiffableDataSource(collectionView: collectionView) { collectionView, indexPath, item in return collectionView.dequeueConfiguredReusableCell(using: itemCell, for: indexPath, item: item) } return dataSource } } extension ItemsViewController { enum Section: Hashable { case items } } extension ItemsViewController: NSFetchedResultsControllerDelegate { func controller(_ controller: NSFetchedResultsController, didChangeContentWith snapshot: NSDiffableDataSourceSnapshotReference) { var snapshot = self.dataSource.snapshot() snapshot.deleteItems(snapshot.itemIdentifiers(inSection: .items)) // use resultsController here instead of controller so we don't have to cast snapshot.appendItems(resultsController.fetchedObjects!, toSection: .items) self.dataSource.apply(snapshot, animatingDifferences: false) } } extension ItemsViewController: UICollectionViewDelegate { func collectionView(_ collectionView: UICollectionView, contextMenuConfigurationForItemAt indexPath: IndexPath, point: CGPoint) -> UIContextMenuConfiguration? { guard let item = dataSource.itemIdentifier(for: indexPath) else { return nil } return UIContextMenuConfiguration(identifier: nil, previewProvider: { ReadViewController(item: item, fervorController: self.fervorController) }, actionProvider: { _ in var children: [UIAction] = [] if let url = item.url { children.append(UIAction(title: "Open in Safari", image: UIImage(systemName: "safari"), handler: { [weak self] _ in let vc = SFSafariViewController(url: url) vc.preferredControlTintColor = .appTintColor self?.present(vc, animated: true) })) children.append(UIAction(title: "Share", image: UIImage(systemName: "square.and.arrow.up"), handler: { [weak self] _ in self?.present(UIActivityViewController(activityItems: [url], applicationActivities: nil), animated: true) })) } if item.read { children.append(UIAction(title: "Mark as Unread", image: UIImage(systemName: "checkmark.circle"), handler: { _ in item.read = false })) } else { children.append(UIAction(title: "Mark as Read", image: UIImage(systemName: "checkmark.circle.fill"), handler: { _ in item.read = true })) } return UIMenu(children: children) }) } func collectionView(_ collectionView: UICollectionView, willPerformPreviewActionForMenuWith configuration: UIContextMenuConfiguration, animator: UIContextMenuInteractionCommitAnimating) { guard let vc = animator.previewViewController else { return } animator.preferredCommitStyle = .pop animator.addCompletion { self.show(vc, sender: nil) } } } extension ItemsViewController: ItemCollectionViewCellDelegate { func itemCellSelected(cell: ItemCollectionViewCell, item: Item) { cell.setRead(true, animated: true) show(ReadViewController(item: item, fervorController: fervorController), sender: nil) } }