The problem I have encountered is that when I fetch the data without any sort descriptor I receive the whole list of data stored in CoreData. However, I want to sort it so that the user would see the data he/she entered in the particular view. The logic is this: I have a TermDefinitionView where user enters the data and then it saves using CoreData. After the user presses "save" button, he/she is relocated to the SetView - the view with the stack of flashcards based on data user entered.
here's SetView():
import SwiftUI
import CoreData
struct SetView: View {
@Environment(\.managedObjectContext) private var viewContext
// @FetchRequest(entity: FlashCardData.entity(), sortDescriptors: NSSortDescriptor[key: ])
@FetchRequest(
entity: FlashCardData.entity(),
sortDescriptors: [NSSortDescriptor(keyPath: \FlashCardData.date, ascending: false)],
predicate: NSPredicate(format: "date > %@", Date().addingTimeInterval(-5*60) as NSDate)
) var flashCardData: FetchedResults<FlashCardData>
let dataController = DataController.shared
var removal: (() -> Void)? = nil
var onRemove: ((SwipeDirection) -> Void)? = nil
@State private var isShown = false
@State private var offset = CGSize.zero
@State private var label: String = "Still Learning" // Define a label string
@State private var showPositiveIndicator = false
@State private var showNegativeIndicator = false
@State private var showMiddleIndicator = false
@State private var showEasyIndicator = false
@State var redirectToSet = false
var body: some View {
ZStack {
RoundedRectangle(cornerRadius: 25, style: .continuous)
.fill(Color.white)
.overlay(RoundedRectangle(cornerRadius: 25).stroke(getColor(), lineWidth: 2)) // Here we change the border color based on the swipe direction
.shadow(radius: 3)
//rest of the code...
VStack {
ForEach(flashCardData, id: \.self) { flashcards in
// FlashcardView(flashcard: flashcard)
// Text(flashcards.name ?? "nothing") - have to create a code where it's above
VStack {
Text(flashcards.term ?? "nothing")
.font(.largeTitle)
.bold()
if isShown {
Text(flashcards.definition ?? "nothing")
.font(.largeTitle)
.bold()
}
}
}
}
// Display positive indicator
if showPositiveIndicator {
VStack {
Spacer()
HStack {
Image(systemName: "checkmark")
.foregroundColor(.green)
.font(.system(size: 40))
Spacer()
}
}
}
// Display negative indicator
if showNegativeIndicator {
VStack {
Spacer()
HStack {
Spacer()
Image(systemName: "xmark")
.foregroundColor(.red)
.font(.system(size: 40))
}
}
}
if showMiddleIndicator {
VStack {
Spacer()
HStack {
Image(systemName: "light.beacon.min")
.foregroundColor(.green)
.font(.system(size: 40))
Spacer()
}
}
}
if showEasyIndicator {
VStack {
Spacer()
HStack {
Image(systemName: "light.beacon.max")
.foregroundColor(.green)
.font(.system(size: 40))
Spacer()
}
}
}
}
.frame(width: 300, height: 550)
.rotationEffect(.degrees(Double(offset.width / 10)))
// makes an effect when swiping the card and it gets back if swipped not too much
.offset(x: offset.width, y: offset.height)
.navigationBarBackButtonHidden(true)// changes the position of the card by x - direction
.onTapGesture {
isShown.toggle()
}
.gesture(
DragGesture().onChanged { value in
offset = value.translation
showPositiveIndicator = offset.width > 50
showNegativeIndicator = offset.width < -50
showMiddleIndicator = offset.height > 50
showEasyIndicator = offset.height < -50
}
.onEnded { value in
if abs(offset.width) > 100 {
withAnimation {
offset.width = value.translation.width > 0 ? 1000 : -1000
onRemove?(value.translation.width > 0 ? .right : .left)
}
} else if abs(offset.height) > 100 {
withAnimation {
offset.height = value.translation.height > 0 ? 1000 : -1000
onRemove?(value.translation.height > 0 ? .up : .down)
}
} else {
offset = .zero
showPositiveIndicator = false
showNegativeIndicator = false
showMiddleIndicator = false
showEasyIndicator = false
}
}
)
}
func getColor() -> Color {
if offset.width > 0 {
// label = "Good Job!" // Change the label based on the swipe direction
return Color.green
} else if offset.width < 0 {
// label = "Needs Improvement"
return Color.red
} else if offset.height > 0{
// label = "Still Learning"
return Color.orange
}
else if offset.height < 0 {
return Color.blue
}
else {
return Color.gray
}
}
}
/* VStack {
ForEach(flashcards) { flashcard in
Text(flashcard.name ?? "")
Text(flashcard.term ?? "")
Text(flashcard.definition ?? "")
}
}
.onAppear {
fetchFlashcards()
}
}
private func fetchFlashcards() {
do {
try viewContext.performAndWait {
try viewContext.save()
}
} catch {
print("Error saving view context: \(error)")
}
}
}
*/
struct SetView_Previews: PreviewProvider {
static var previews: some View {
SetView()
}
}
struct FlashcardView: View {
var flashcard: FlashCardData
/*let term: String
let definition: String
let name: String
*/
var body: some View {
VStack {
Text(flashcard.name ?? "")
Text(flashcard.term ?? "")
Text(flashcard.definition ?? "")
}
}
TermDefinitionView():
import SwiftUI
import Combine
import CoreData
// ViewModel for managing flashcards
struct TermDefinitionView: View {
// let set: SetView
@FetchRequest(
entity: FlashCardData.entity(),
sortDescriptors: [NSSortDescriptor(keyPath: \FlashCardData.date, ascending: false)],
predicate: NSPredicate(format: "date > %@", Date().addingTimeInterval(-5) as NSDate)
) var flashCardData: FetchedResults<FlashCardData>
@ObservedObject private var viewModel = TermDefinitionViewModel()
@State var name = "" // Separate state for the name
@Environment(\.managedObjectContext) var managedObjectContext
@Environment(\.dismiss) var dismiss
let dataController = DataController.shared
@State var showSet = false
@Binding var saveSet: Bool
@State var isShowingSet = false
//@Binding var redirectToSet: Bool
var body: some View {
ZStack {
NavigationStack {
VStack {
Text("Name")
.padding(.trailing, 220)
.fontWeight(.bold)
TextField("Enter name", text: $name)
.frame(width: 250)
.padding()
.overlay(RoundedRectangle(cornerRadius: 8).stroke(Color.gray, lineWidth: 1))
List {
ForEach(viewModel.termdefpairs.indices, id: \.self) { index in
TermView(term: $viewModel.termdefpairs[index].term, definition: $viewModel.termdefpairs[index].definition, tag: $viewModel.termdefpairs[index].tag)
}
.onDelete { index in
self.viewModel.termdefpairs.remove(at: index.first!)
}
}
.navigationBarItems(trailing: Button(action: {
// redirectToSet.toggle()
for testForm in viewModel.termdefpairs {
dataController.add(term: testForm.term, name: name, definition: testForm.definition, tag: testForm.tag, date: Date(), context: managedObjectContext)
}
isShowingSet = true
dismiss()
}) {
Text("Save")
NavigationLink(destination: SetView(flashCardData: _flashCardData), isActive: $isShowingSet) {
EmptyView()
}
})
Spacer()
ForEach(dataController.savedFlash) { x in
Text(x.term ?? "nothing")
Text(x.definition ?? "nothing")
Text(x.tag ?? "nothing")
}
Button(action: {
viewModel.addNew()
}) {
Image(systemName: "plus")
.resizable()
.foregroundColor(.white)
.font(.title2)
.frame(width: 30, height: 30)
}
.frame(width: 40, height: 40)
.background(Color.blue)
.clipShape(Circle())
.padding()
}
}
}
}
}
struct TermDefinitionView_Previews: PreviewProvider {
static var previews: some View {
TermDefinitionView(saveSet: .constant(true))
}
}
struct TermView: View {
@Binding var term: String
@Binding var definition: String
@Binding var tag: String
@State private var isTagExpanded = false
var body: some View {
VStack(alignment: .leading, spacing: 20) {
if isTagExpanded {
VStack(alignment: .leading, spacing: 8) {
Text("Tag")
.font(.headline)
TextField("Enter tag", text: $tag)
.padding()
.overlay(RoundedRectangle(cornerRadius: 8).stroke(Color.gray, lineWidth: 1))
}
}
Button(action: {
isTagExpanded.toggle()
}) {
HStack {
Spacer()
Image(systemName: isTagExpanded ? "minus.circle.fill" : "plus.circle.fill")
.resizable()
.foregroundColor(.blue)
.frame(width: 25, height: 25)
Text(isTagExpanded ? "Hide Tag" : "Add Tag")
.foregroundColor(.blue)
.font(.headline)
}
}
.padding(.bottom, -25)
VStack(alignment: .leading, spacing: 8) {
Text("Term")
.font(.headline)
TextField("Enter term", text: $term)
.padding()
.overlay(RoundedRectangle(cornerRadius: 8).stroke(Color.gray, lineWidth: 1))
}
VStack(alignment: .leading, spacing: 8) {
Text("Definition")
.font(.headline)
TextField("Enter definition", text: $definition)
.padding()
.overlay(RoundedRectangle(cornerRadius: 8).stroke(Color.gray, lineWidth: 1))
}
}
.padding()
}
}
struct TermAndDefinition: Identifiable {
var id = UUID()
var term: String
var definition: String
var tag: String
}
DataController:
import Foundation
import CoreData
class DataController: ObservableObject {
static let shared = DataController()
@Published var savedFlash: [FlashCardData] = []
let container: NSPersistentContainer
let fetchRequest: NSFetchRequest<FlashCardData> = FlashCardData.fetchRequest()
init(inMemory: Bool = false) {
container = NSPersistentContainer(name: "CoreData")
if inMemory {
container.persistentStoreDescriptions.first!.url = URL(fileURLWithPath: "/dev/null")
}
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
// Replace this implementation with code to handle the error appropriately.
// fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
/*
Typical reasons for an error here include:
* The parent directory does not exist, cannot be created, or disallows writing.
* The persistent store is not accessible, due to permissions or data protection when the device is locked.
* The device is out of space.
* The store could not be migrated to the current model version.
Check the error message to determine what the actual problem was.
*/
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
container.viewContext.automaticallyMergesChangesFromParent = true
}
func save(context: NSManagedObjectContext) {
do {
try context.save()
print("Data saved")
} catch {
print("We could not save the data...")
}
}
func add(term: String, name: String, definition: String, tag: String, date: Date, context: NSManagedObjectContext) {
let data = FlashCardData(context: context)
data.name = name
data.id = UUID()
data.definition = definition
data.term = term
data.tag = tag
data.date = date
save(context: context)
}
func edit(data: FlashCardData ,term: String, defintion: String, tag: String, context: NSManagedObjectContext) {
data.term = term
data.definition = defintion
data.tag = tag
save(context: context)
}
}
Here's the new @FetchRequest:
@FetchRequest(
entity: FlashCardData.entity(),
sortDescriptors: [NSSortDescriptor(keyPath: \FlashCardData.date, ascending: false)],
predicate: NSPredicate(format: "date > %@", Date().addingTimeInterval(-5*60) as NSDate)
) var flashCardData: FetchedResults<FlashCardData>

