Utilize NSFetchedResultsController to update the deck when answers are recorded.
This commit is contained in:
parent
7b3cbd9038
commit
a1efa0f6c6
|
@ -34,7 +34,7 @@
|
|||
F378AA0E23D4CC6400296A76 /* DraggableCardView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F378AA0D23D4CC6400296A76 /* DraggableCardView.swift */; };
|
||||
F378AA1023D5BAD500296A76 /* CardDeckView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F378AA0F23D5BAD500296A76 /* CardDeckView.swift */; };
|
||||
F378AA1323D63AA700296A76 /* NumberFormatters.swift in Sources */ = {isa = PBXBuildFile; fileRef = F378AA1223D63AA700296A76 /* NumberFormatters.swift */; };
|
||||
F378AA1523D7156200296A76 /* Card+Outcome.swift in Sources */ = {isa = PBXBuildFile; fileRef = F378AA1423D7156100296A76 /* Card+Outcome.swift */; };
|
||||
F378AA1523D7156200296A76 /* Card+AnswerState.swift in Sources */ = {isa = PBXBuildFile; fileRef = F378AA1423D7156100296A76 /* Card+AnswerState.swift */; };
|
||||
F378AA1723D733EF00296A76 /* EditDeckView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F378AA1623D733EE00296A76 /* EditDeckView.swift */; };
|
||||
F378AA1923D7359B00296A76 /* EditDeckView+ViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F378AA1823D7359B00296A76 /* EditDeckView+ViewModel.swift */; };
|
||||
F378AA1B23D7A06600296A76 /* CountdownTimerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F378AA1A23D7A06500296A76 /* CountdownTimerView.swift */; };
|
||||
|
@ -43,6 +43,7 @@
|
|||
F378AA2323D7B8D900296A76 /* CardDeck+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = F378AA2123D7B8D900296A76 /* CardDeck+CoreDataProperties.swift */; };
|
||||
F378AA2523D7B91E00296A76 /* CardDeck+Computeds.swift in Sources */ = {isa = PBXBuildFile; fileRef = F378AA2423D7B91E00296A76 /* CardDeck+Computeds.swift */; };
|
||||
F378AA3023D8909C00296A76 /* KeyboardAvoider in Frameworks */ = {isa = PBXBuildFile; productRef = F378AA2F23D8909C00296A76 /* KeyboardAvoider */; };
|
||||
F378AA3223D8A6EA00296A76 /* Card+FetchHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = F378AA3123D8A6EA00296A76 /* Card+FetchHelpers.swift */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
|
@ -72,7 +73,7 @@
|
|||
F378AA0D23D4CC6400296A76 /* DraggableCardView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DraggableCardView.swift; sourceTree = "<group>"; };
|
||||
F378AA0F23D5BAD500296A76 /* CardDeckView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CardDeckView.swift; sourceTree = "<group>"; };
|
||||
F378AA1223D63AA700296A76 /* NumberFormatters.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NumberFormatters.swift; sourceTree = "<group>"; };
|
||||
F378AA1423D7156100296A76 /* Card+Outcome.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Card+Outcome.swift"; sourceTree = "<group>"; };
|
||||
F378AA1423D7156100296A76 /* Card+AnswerState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Card+AnswerState.swift"; sourceTree = "<group>"; };
|
||||
F378AA1623D733EE00296A76 /* EditDeckView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditDeckView.swift; sourceTree = "<group>"; };
|
||||
F378AA1823D7359B00296A76 /* EditDeckView+ViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "EditDeckView+ViewModel.swift"; sourceTree = "<group>"; };
|
||||
F378AA1A23D7A06500296A76 /* CountdownTimerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CountdownTimerView.swift; sourceTree = "<group>"; };
|
||||
|
@ -80,6 +81,7 @@
|
|||
F378AA2023D7B8D900296A76 /* CardDeck+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CardDeck+CoreDataClass.swift"; sourceTree = "<group>"; };
|
||||
F378AA2123D7B8D900296A76 /* CardDeck+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CardDeck+CoreDataProperties.swift"; sourceTree = "<group>"; };
|
||||
F378AA2423D7B91E00296A76 /* CardDeck+Computeds.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CardDeck+Computeds.swift"; sourceTree = "<group>"; };
|
||||
F378AA3123D8A6EA00296A76 /* Card+FetchHelpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Card+FetchHelpers.swift"; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
|
@ -216,7 +218,8 @@
|
|||
isa = PBXGroup;
|
||||
children = (
|
||||
F378A9F423D44E3800296A76 /* Card+CoreDataClass.swift */,
|
||||
F378AA1423D7156100296A76 /* Card+Outcome.swift */,
|
||||
F378AA3123D8A6EA00296A76 /* Card+FetchHelpers.swift */,
|
||||
F378AA1423D7156100296A76 /* Card+AnswerState.swift */,
|
||||
F378A9F523D44E3900296A76 /* Card+CoreDataProperties.swift */,
|
||||
);
|
||||
path = Card;
|
||||
|
@ -382,6 +385,7 @@
|
|||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
F378AA0423D48D6D00296A76 /* CardDeckContainerView+ViewModel.swift in Sources */,
|
||||
F378AA3223D8A6EA00296A76 /* Card+FetchHelpers.swift in Sources */,
|
||||
F378AA1E23D7A0B900296A76 /* CountdownTimerView+ViewModel.swift in Sources */,
|
||||
F378A9F623D44E3900296A76 /* Card+CoreDataClass.swift in Sources */,
|
||||
F378AA0C23D4A5D300296A76 /* View+Stacked.swift in Sources */,
|
||||
|
@ -400,7 +404,7 @@
|
|||
F378A9E623D43A6900296A76 /* CoreDataManager+Utils.swift in Sources */,
|
||||
F378AA1023D5BAD500296A76 /* CardDeckView.swift in Sources */,
|
||||
F378AA1723D733EF00296A76 /* EditDeckView.swift in Sources */,
|
||||
F378AA1523D7156200296A76 /* Card+Outcome.swift in Sources */,
|
||||
F378AA1523D7156200296A76 /* Card+AnswerState.swift in Sources */,
|
||||
F378AA1923D7359B00296A76 /* EditDeckView+ViewModel.swift in Sources */,
|
||||
F378A9F323D43CAA00296A76 /* PreviewData+Cards.swift in Sources */,
|
||||
F378AA0223D48CB900296A76 /* CardDeckContainerView.swift in Sources */,
|
||||
|
|
|
@ -12,7 +12,7 @@ import CoreData
|
|||
|
||||
@objc(CardDeck)
|
||||
public class CardDeck: NSManagedObject {
|
||||
|
||||
|
||||
}
|
||||
|
||||
extension CardDeck: Identifiable {}
|
||||
|
|
|
@ -21,6 +21,7 @@ extension CardDeck {
|
|||
@NSManaged public var cards: NSSet?
|
||||
}
|
||||
|
||||
|
||||
// MARK: Generated accessors for cards
|
||||
extension CardDeck {
|
||||
|
||||
|
|
|
@ -12,11 +12,6 @@ import CoreData
|
|||
|
||||
|
||||
extension Card {
|
||||
|
||||
@nonobjc public class func fetchRequest() -> NSFetchRequest<Card> {
|
||||
return NSFetchRequest<Card>(entityName: "Card")
|
||||
}
|
||||
|
||||
@NSManaged public var prompt: String?
|
||||
@NSManaged public var answer: String?
|
||||
@NSManaged public var decks: NSSet?
|
||||
|
|
|
@ -0,0 +1,51 @@
|
|||
//
|
||||
// Card+FetchHelpers.swift
|
||||
// Flashzilla
|
||||
//
|
||||
// Created by CypherPoet on 1/22/20.
|
||||
// ✌️
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import CoreData
|
||||
import CypherPoetCoreDataKit_PredicateUtils
|
||||
|
||||
|
||||
extension Card {
|
||||
|
||||
@nonobjc public class func fetchRequest() -> NSFetchRequest<Card> {
|
||||
return NSFetchRequest<Card>(entityName: "Card")
|
||||
}
|
||||
|
||||
|
||||
public enum SortDescriptors {
|
||||
}
|
||||
|
||||
|
||||
public enum Predicate {
|
||||
|
||||
public static func cards(in cardDeck: CardDeck) -> NSPredicate {
|
||||
let keyword = NSComparisonPredicate.keyword(for: .contains)
|
||||
|
||||
let predicate = NSPredicate(
|
||||
format: "%K \(keyword) %@",
|
||||
#keyPath(Card.decks),
|
||||
cardDeck.objectID
|
||||
)
|
||||
|
||||
return predicate
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@nonobjc public class func fetchRequest(
|
||||
forCardsIn cardDeck: CardDeck
|
||||
) -> NSFetchRequest<Card> {
|
||||
let fetchRequest: NSFetchRequest<Card> = Self.fetchRequest()
|
||||
|
||||
fetchRequest.sortDescriptors = []
|
||||
fetchRequest.predicate = Self.Predicate.cards(in: cardDeck)
|
||||
|
||||
return fetchRequest
|
||||
}
|
||||
}
|
|
@ -16,7 +16,11 @@ public protocol FetchedResultsControlling: NSObject {
|
|||
var fetchRequest: NSFetchRequest<FetchedResult> { get }
|
||||
var fetchedResultsController: NSFetchedResultsController<FetchedResult> { get }
|
||||
|
||||
func makeFetchedResultsController() -> FetchedResultsController
|
||||
func makeFetchedResultsController(
|
||||
sectionNameKeyPath: String?,
|
||||
cacheName: String?
|
||||
) -> FetchedResultsController
|
||||
|
||||
func extractResults(from fetchedResultsController: FetchedResultsController) -> [FetchedResult]
|
||||
}
|
||||
|
||||
|
@ -26,12 +30,15 @@ extension FetchedResultsControlling {
|
|||
public typealias FetchRequest = NSFetchRequest<FetchedResult>
|
||||
|
||||
|
||||
public func makeFetchedResultsController() -> FetchedResultsController {
|
||||
public func makeFetchedResultsController(
|
||||
sectionNameKeyPath: String? = nil,
|
||||
cacheName: String? = nil
|
||||
) -> FetchedResultsController {
|
||||
.init(
|
||||
fetchRequest: fetchRequest,
|
||||
managedObjectContext: CurrentApp.coreDataManager.mainContext,
|
||||
sectionNameKeyPath: nil,
|
||||
cacheName: nil
|
||||
sectionNameKeyPath: sectionNameKeyPath,
|
||||
cacheName: cacheName
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -10,13 +10,20 @@
|
|||
import SwiftUI
|
||||
import Combine
|
||||
import CoreData
|
||||
import CypherPoetCoreDataKit
|
||||
|
||||
|
||||
extension CardDeckContainerView {
|
||||
|
||||
final class ViewModel: ObservableObject {
|
||||
final class ViewModel: NSObject, ObservableObject, FetchedResultsControlling {
|
||||
typealias FetchedResult = Card
|
||||
|
||||
lazy var fetchRequest: NSFetchRequest<Card> = Card.fetchRequest(forCardsIn: cardDeck)
|
||||
internal lazy var fetchedResultsController: FetchedResultsController = makeFetchedResultsController()
|
||||
|
||||
private var subscriptions = Set<AnyCancellable>()
|
||||
|
||||
|
||||
@ObservedObject var cardDeck: CardDeck
|
||||
|
||||
var isTimerActive = false
|
||||
|
@ -25,6 +32,7 @@ extension CardDeckContainerView {
|
|||
|
||||
// MARK: - Published Outputs
|
||||
@Published var cards: [Card] = []
|
||||
@Published var visibleCards: [Card] = []
|
||||
@Published var timeRemaining: TimeInterval
|
||||
|
||||
|
||||
|
@ -37,7 +45,11 @@ extension CardDeckContainerView {
|
|||
self.roundDuration = roundDuration
|
||||
self.timeRemaining = roundDuration
|
||||
|
||||
super.init()
|
||||
|
||||
self.fetchedResultsController.delegate = self
|
||||
setupSubscribers()
|
||||
fetchCards()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -65,22 +77,17 @@ extension CardDeckContainerView.ViewModel {
|
|||
}
|
||||
|
||||
|
||||
private var cardsPublisher: Publishers.Share<AnyPublisher<[Card], Never>> {
|
||||
cardDeck.publisher(for: \.cards)
|
||||
.map { _ in self.cardDeck.cardsArray }
|
||||
private var visibleCardsPublisher: AnyPublisher<[Card], Never> {
|
||||
$cards
|
||||
.map { $0.filter { $0.answerState == .unanswered } }
|
||||
// .print("visibleCardsPublisher")
|
||||
.eraseToAnyPublisher()
|
||||
.share()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Computeds
|
||||
extension CardDeckContainerView.ViewModel {
|
||||
|
||||
var visibleCards: [Card] {
|
||||
cards.filter { $0.answerState == .unanswered }
|
||||
}
|
||||
|
||||
var isDeckEmpty: Bool { visibleCards.isEmpty }
|
||||
}
|
||||
|
||||
|
@ -89,6 +96,12 @@ extension CardDeckContainerView.ViewModel {
|
|||
// MARK: - Public Methods
|
||||
extension CardDeckContainerView.ViewModel {
|
||||
|
||||
func fetchCards() {
|
||||
try? fetchedResultsController.performFetch()
|
||||
cards = extractResults(from: fetchedResultsController)
|
||||
}
|
||||
|
||||
|
||||
func resetDeck() {
|
||||
cards.forEach { $0.answerState = .unanswered }
|
||||
|
||||
|
@ -100,13 +113,14 @@ extension CardDeckContainerView.ViewModel {
|
|||
isTimerActive = false
|
||||
}
|
||||
|
||||
|
||||
func resumeRound() {
|
||||
isTimerActive = true
|
||||
}
|
||||
|
||||
|
||||
func record(_ answerState: Card.AnswerState, forCardAt index: Int) {
|
||||
let card = cards[index]
|
||||
let card = visibleCards[index]
|
||||
|
||||
guard let managedObjectContext = card.managedObjectContext else { fatalError() }
|
||||
|
||||
|
@ -120,14 +134,13 @@ extension CardDeckContainerView.ViewModel {
|
|||
}
|
||||
|
||||
|
||||
|
||||
// MARK: - Private Helpers
|
||||
private extension CardDeckContainerView.ViewModel {
|
||||
|
||||
func setupSubscribers() {
|
||||
cardsPublisher
|
||||
visibleCardsPublisher
|
||||
.receive(on: DispatchQueue.main)
|
||||
.assign(to: \.cards, on: self)
|
||||
.assign(to: \.visibleCards, on: self)
|
||||
.store(in: &subscriptions)
|
||||
|
||||
timeRemainingPublisher
|
||||
|
@ -148,3 +161,15 @@ private extension CardDeckContainerView.ViewModel {
|
|||
.store(in: &subscriptions)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - NSFetchedResultsControllerDelegate
|
||||
extension CardDeckContainerView.ViewModel: NSFetchedResultsControllerDelegate {
|
||||
|
||||
func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
|
||||
guard let controller = controller as? FetchedResultsController else { return }
|
||||
|
||||
print("controllerDidChangeContent")
|
||||
cards = extractResults(from: controller)
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue