Basically I need a view with a calendar that will show data attributes from the item. I've tried two different approaches both have their listed problems. There must be a better way to do something like this. Surely it's not ideal to create a new item every time a date is opened or constantly check if something is there, but I don't know any other way.
Actual View:
import SwiftUI
import CoreData
struct ContentView: View {
@Environment(\.managedObjectContext) var managedObjContext
@Environment(\.calendar) var calenda
@Environment(\.dismiss) var dismiss
@FetchRequest(sortDescriptors: [], predicate: NSPredicate(format: "timestamp == %@", Date.now as CVarArg)) var items: FetchedResults<Item>
@State private var date = Date.now
var body: some View {
NavigationView{
VStack{
DatePicker("Calendar", selection: $date, in: Date.now...,displayedComponents: [.date])
.datePickerStyle(.graphical)
.onAppear(perform: {
if (items.isEmpty){
PersistenceController().addItem(date: date, context: managedObjContext)
}
})
.onChange(of: date){ value in
items.nsPredicate=NSPredicate(format: "timestamp == %@", date as CVarArg)
if (items.isEmpty){
PersistenceController().addItem(date: date, context: managedObjContext)
}
}
if (!items.isEmpty){
//This is the only difference in the two approaches. I just put either one of the next two blocks of code in here
}
}
.navigationBarTitle("My Planner")
}
}
func getTitle(date: Date)->String{
let formatter = DateFormatter()
formatter.dateStyle = .medium
return formatter.string(from: date)
}
}
First (looks correct, but doesn't show the changes live):
PlannedMealsView(item: items[0])
Spacer()
//And then this is added at the bottom
struct PlannedMealsView: View {
@Environment(\.managedObjectContext) var managedObjContext
@State var item: Item
var body: some View {
VStack{
Text(item.timestamp ?? Date.now, style: .date)
.font(.title2)
.bold()
Section("Word"){
if(item.word != nil){
HStack{
Spacer()
Text(item.word!)
Spacer()
Button(action: {
PersistenceController().removeFromItem(item: item, context: managedObjContext)
}){
Image(systemName: "minus.circle").bold()
}
Spacer()
}
} else {
Button(action: {
PersistenceController().addToItem(item: item, context: managedObjContext)
}){
Image(systemName: "plus.circle").bold()
.padding(.vertical, 10)
.padding(.horizontal, 20)
}
}
}
Spacer()
}
.frame(height:200)
}
}
Second (allows direct access to the objects data, but bugs after 5 or 6 date changes):
VStack{
Text(items[0].timestamp ?? Date.now, style: .date)
.font(.title2)
.bold()
Section("Word"){
if(items[0].word != nil){
HStack{
Spacer()
Text(items[0].word!)
Spacer()
Button(action: {
PersistenceController().removeFromItem(item: items[0], context: managedObjContext)
}){
Image(systemName: "minus.circle").bold()
}
Spacer()
}
} else {
Button(action: {
PersistenceController().addToItem(item: items[0], context: managedObjContext)
}){
Image(systemName: "plus.circle").bold()
.padding(.vertical, 10)
.padding(.horizontal, 20)
}
}
}
Spacer()
}
.frame(height:200)
Unchanged Files:
Persistence-
import CoreData
struct PersistenceController {
static let shared = PersistenceController()
let container: NSPersistentContainer
init(inMemory: Bool = false) {
container = NSPersistentContainer(name: "Test")
if inMemory {
container.persistentStoreDescriptions.first!.url = URL(fileURLWithPath: "/dev/null")
}
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
container.viewContext.automaticallyMergesChangesFromParent = true
}
func addItem(date: Date, context: NSManagedObjectContext){
let item = Item(context: context)
item.timestamp = date
item.word = nil
save(context: context)
}
func addToItem(item: Item, context: NSManagedObjectContext){
item.word = "Test"
save(context: context)
}
func removeFromItem(item: Item, context: NSManagedObjectContext){
item.word = nil
save(context: context)
}
func save(context: NSManagedObjectContext){
do {
try context.save()
} catch {
let nsError = error as NSError
fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
}
}
}
Data Model-
If you have any questions I'll be happy to answer. Any help is greatly appreciated. All the best!
Main Struct
@main
struct TestForWeagleWeagleApp: App {
let persistence = PersistenceController.shared // initiates the CoreData stack
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
Persistence
import Foundation
import CoreData
class PersistenceController : ObservableObject {
static let shared = PersistenceController()
let container: NSPersistentContainer
init(inMemory: Bool = false) {
container = NSPersistentContainer(name: "Test")
if inMemory {
container.persistentStoreDescriptions.first!.url = URL(fileURLWithPath: "/dev/null")
}
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
container.viewContext.automaticallyMergesChangesFromParent = true
}
func addItem(date: Date){
let context = container.viewContext
let item = Item(context: context)
item.timestamp = date
item.word = nil
contextSave()
}
func addToItem(item: Item) {
item.word = "Test"
contextSave()
}
func removeFromItem(item: Item){
item.word = nil
contextSave()
}
func contextSave() {
let context = container.viewContext
if context.hasChanges {
do {
try context.save()
self.objectWillChange.send()
} catch {
print("**** ERROR: Unable to save context \(error)")
}
}
}
func getItemsFor(_ date: Date) -> [Item] {
let context = container.viewContext
var request = NSFetchRequest<Item>()
request = Item.fetchRequest()
//request.fetchLimit = 1
request.entity = NSEntityDescription.entity(forEntityName: "Item", in: context)
request.predicate = NSPredicate(format: "timestamp >= %@ and timestamp <= %@", Calendar.current.startOfDay(for: date) as CVarArg, Calendar.current.startOfDay(for: date).addingTimeInterval(86399.0) as CVarArg)
do {
let items = try context.fetch(request)
if items.count == 0 { return []}
return items.sorted(by: {$0.timestamp! > $1.timestamp!})
} catch {
print("**** ERROR: items fetch failed \(error)")
return []
}
}
}
ContentView
import SwiftUI
struct ContentView: View {
@ObservedObject var persistence = PersistenceController.shared
@State private var items = PersistenceController.shared.getItemsFor(Date())
@State private var date = Date.now
var body: some View {
NavigationView{
VStack {
DatePicker("Calendar", selection: $date, in: Date.now...,displayedComponents: [.date])
.datePickerStyle(.graphical)
.onAppear(perform: {
if items.isEmpty {
persistence.addItem(date: date)
items = persistence.getItemsFor(date)
}
})
if !(items.isEmpty) {
PlannedMealsView(item: items.last!)
Spacer()
}
}
.navigationBarTitle("My Planner")
}
.onChange(of: date){ newDate in
items = persistence.getItemsFor(newDate)
if items.isEmpty {
persistence.addItem(date: newDate)
items = persistence.getItemsFor(newDate)
}
}
}
func getTitle(date: Date)->String{
let formatter = DateFormatter()
formatter.dateStyle = .medium
return formatter.string(from: date)
}
}
PerformanceMealsView
import SwiftUI
struct PlannedMealsView: View {
@ObservedObject var persistence = PersistenceController.shared
var item: Item
@State private var forceRefresh : Bool = false
var body: some View {
VStack{
Text(item.timestamp!, style: .date)
.font(.title2)
.bold()
Section("Word"){
if(item.word != nil){
HStack{
Spacer()
Text(item.word!)
Spacer()
Button(action: {
persistence.removeFromItem(item: item)
}){
Image(systemName: "minus.circle").bold()
}
Spacer()
}
} else {
Button(action: {
persistence.addToItem(item: item)
forceRefresh.toggle()
}){
Image(systemName: "plus.circle").bold()
.padding(.vertical, 10)
.padding(.horizontal, 20)
}
}
}
Spacer()
}
}
}
This works on my system, except that (probably unwisely) I used my development environment, which uses beta Xcode and beta iOS. I couldn't backwards convert the Xcode project (new format) to test on my production equipment without redoing everything.
I hope this works for you too!!! Regards, Michaela