Hi, I have a computed property in my SwiftUI Todo app that shows the status of the past 4 days - whether the todo was completed or not. For this I have a computed array days that contains the last 4 days including today. Using this array I show the results in checkboxes which is a SwiftUI view. But I have a problem, at midnight when the day changes the app becomes completely unresponsive as this array gets computed again and again infinitely. Only deleting the app and reinstalling it allows it to run normally again, until the next time the day changes when the same thing happens again.var days: [Day] {
		var computedDays = [Day]()
let calendar = Calendar.current
var dayIndex = calendar.component(.weekday, from: Date())
let lastFourDays = formatDateForPastFourDays(from: Date())
// considering today is 20th August 2020
// lastFourDays = ["August 20, 2020", "August 19, 2020", "August 18, 2020", "August 17, 2020"]
let daysInitials = ["Su", "M", "T", "W", "Th", "F", "S"]
let checkedDates = computeCheckedDates(lastFourDays)
// checkedDates = [true, false]
let passedDates = computePassedDates(for: checkedDates, dates: lastFourDays)
// passedDates = [false, true, false, false]
for idx in 0..<checkedDates.count {
computedDays.append(Day(id: idx, date: lastFourDays[idx], dayValue: daysInitials[dayIndex - 1], checked: checkedDates[idx], passed: passedDates[idx]))
dayIndex -= 1
if dayIndex == 0 {
dayIndex = 7
}
}
// computedDays = [ToDoApp.Day(id: 0, date: "August 20, 2020", dayValue: "Th", checked: true, passed: false), ToDoApp.Day(id: 1, date: "August 19, 2020", dayValue: "W", checked: false, passed: true)]
return computedDays
}
private func computeCheckedDates(_ dates: [String]) -> [Bool] {
		var checkedDates: [Bool]
		if createdAt != nil {
				let calendar = Calendar.current
				let daysDiff = calendar.dateComponents([.day], from: calendar.startOfDay(for: createdAt!), to: Date()).day
				if daysDiff == 0 {
						checkedDates = [false]
				} else if daysDiff == 1 {
						checkedDates = [false, false]
				} else if daysDiff == 2 {
						checkedDates = [false, false, false]
				} else {
						checkedDates = [false, false, false, false]
				}
		} else {
				checkedDates = [false]
		}
		if completedDates != nil {
				for idx in 0..<checkedDates.count {
						checkedDates[idx] = completedDates!.contains(dates[idx])
				}
		}
		return checkedDates
}
var createdAt: Date? {
		return habit.createdAt ?? Date()
}
var completedDates: [String]? {
		return habit.completedDates ?? nil
}
				
I think there is a problem in computeCheckedDates method which I haven't been able to figure out because as soon as I return only the current day's checked/unchecked status, the app runs just fine.
I am using the days property in a ForEach in SwiftUI and as soon I remove this code, the app also runs completely fine. But with the code I have shared days get computed again and again infinitely as soon as the system date changes to the next day.
Any suggestions?
Post
Replies
Boosts
Views
Activity
I have two entities in CoreData Todo and DaysOfYear and a many to one relationship exists between these two entities. When the user launches the app if for the past 4 days the user has not completed any of the todos I add those todos to the incompleteTodos relationship for each day. Thing is only the first day gets updated in core data while the rest of them do not update. I have tried everything that I could, all to no avail. Core Data simply refuses to store any of the updates I make except for the first day even though context.hasChanges acknowledges that the context has changed.
ContentView from where I add IncompleteTodos:
Todo.addIncompleteTodo(todo: todo, for: days[idx].date, context: context)
Extension Todo:static func addIncompleteTodo(todo: Todo, for day: String, context: NSManagedObjectContext) {
let today = DayOfYear.withDate(convertStringToGMTDate(day), context: context)
todo.incompleteDay = today
today.addToIncompleteTodos(todo)
print(context.hasChanges)
try? context.save()
}
Extension DayOfYear:
static func withDate( day: Date, context: NSManagedObjectContext) -> DayOfYear {
let request = fetchRequest(NSPredicate(format: "date >= %@ AND date <= %@", day.startOfDay() as CVarArg, day.endOfDay() as CVarArg))
let days = (try? context.fetch(request)) ?? []
if let day = days.first {
return day
} else {
let newDay = DayOfYear(context: context)
newDay.date = day
return newDay
}
}
static func fetchRequest(_ predicate: NSPredicate) -> NSFetchRequest<DayOfYear> {
let request = NSFetchRequest<DaysOfYear>(entityName: "DaysOfYear")
request.sortDescriptors = [NSSortDescriptor(key: "date", ascending: false)]
request.predicate = predicate
return request
}
So let's say if I add 4 incompleteTodos for 10th, 9th, 8th and 7th September 2020. Core Data would only update the relationship for 7th September and store 4 incompleteTodos while 8th, 9th, 10th Septmeber will show 0 incompleteTodos after running this code. If I add a new Todo on the 10th, it will immediately be reflected in incompleteTodos for the 10th, but will NOT update incompleteTodos for 11th September.
I am trying to build a widget for iOS 14 using WidgetKit and CoreData/CloudKit instance on a new Xcode 12 project.
I have added my CoreData's File Target Membership to both my app and widget
I have added an App Group to my main App Target.
My Persistence.swift looks like this:
init(inMemory: Bool = false) {
container = NSPersistentCloudKitContainer(name: "test-cd")
let storeURL = URL.storeURL(for: "group.test-data", databaseName: "test-cd")
let storeDescription = NSPersistentStoreDescription(url: storeURL)
container.persistentStoreDescriptions = [storeDescription]
4. My widget looks like this:
struct Provider: TimelineProvider {
var managedObjectContext : NSManagedObjectContext
init(context : NSManagedObjectContext) {
self.managedObjectContext = context
}
...
}
@main
struct test_widget: Widget {
let kind: String = "test_widget"
var persistentContainer = PersistenceController.shared.container
var body: some WidgetConfiguration {
StaticConfiguration(kind: kind, provider: Provider(context: persistentContainer.viewContext)) { entry in
test_widgetEntryView(entry: entry)
}
.configurationDisplayName("My Widget")
.description("This is an example widget.")
}
}
5. And my widget's entry view looks like this:
struct test_widgetEntryView : View {
@FetchRequest(
sortDescriptors: [NSSortDescriptor(keyPath: \Item.checked, ascending: true)],
animation: .default)
private var items: FetchedResults<Item>
var entry: Provider.Entry
var body: some View {
LazyVStack {
ForEach(items) { item in
HStack {
Text("\(item.name)")
}
}
}
.padding(.horizontal)
}
}
But the app crashes on ForEach(items) as soon as it is launched:
Thread 1: EXCBADINSTRUCTION (code=EXCI386INVOP, subcode=0x0) Any suggestions as to what is going wrong here?
Hi, I have a shared Core Data Container between my app and widget. Both have the same app group enabled. I am using data in my widget that accesses data from Core Data and one single image in FileManager.
In UIApplication.willResignActiveNotification I reload the timeline of my widget and if I make changes to the "one" single image in my FileManager it is immediately reflected in the updated Widget. But I cant figure out how to fetch updated data from my timeline in the widget from Core Data. I can initially load the data from CD using:
struct Provider: TimelineProvider {
var managedObjectContext : NSManagedObjectContext
var items: [Item] = []
init(context : NSManagedObjectContext) {
self.managedObjectContext = context
let fetchRequest = NSFetchRequest<Item>(entityName: "Item")
fetchRequest.sortDescriptors = [NSSortDescriptor(keyPath: \Item.checked, ascending: true), NSSortDescriptor(keyPath: \Item.timestamp, ascending: false)]
do {
items = try managedObjectContext.fetch(fetchRequest)
if items.count == 0 {
}
} catch let error as NSError {
print("Could not fetch: \(error)")
}
backgroundImageURL = getDocumentsDirectory().appendingPathComponent("background.png")
}
...
}
But how do I update "items" in
getTimeline(in context: Context, completion: @escaping (Timeline<Entry>) -> ())