Check Core Data for an entry with Today's Date & perform function - swift

I am fairly new to Swift & hoping someone knows the answer to this - nothing I try seems to work!
I have a Swift app which has a Core Data entity called "Drink" with 2 keys: a Date & then one called "drinkWater" which stores a value of "1" when a button is pushed.
I am trying to write a separate function where I can check if an entry exists for todays date and, if so, perform an action (in this case change an imageview).
I realise the below isn't the answer but it's as far as I got! Basically I can get all of the entries based on the value of drinkWater (this would need to be by Date I am guessing?) and I can get today's date all printing to the console. Now I'm stuck ...
private func updateMyImageView() {
let appDelegate = UIApplication.shared.delegate as! AppDelegate
let context = appDelegate.persistentContainer.viewContext
let request = NSFetchRequest<NSFetchRequestResult>(entityName: "Drink")
request.predicate = NSPredicate(format: "drinkWater = %#", "1")
request.returnsObjectsAsFaults = false
do {
let result = try context.fetch(request)
for data in result as! [NSManagedObject] {
print(data.value(forKey: "timestamp") as! Date)
}
} catch {
print("Failed")
}
let dateNow = Date()
print("Date is \(dateNow)")
}
This returns:
2018-12-29 01:27:27 +0000
Date is 2018-12-29 12:21:21 +0000
Any ideas on how to turn this all into the correct function would be greatly appreciated!!

You need to use a date range from start of day to end of day (midnight to midnight) in a compound predicate. Here is a solution based on a similar SO question
var calendar = Calendar.current
calendar.timeZone = NSTimeZone.local
let dateFrom = calendar.startOfDay(for: Date())
let dateTo = calendar.date(byAdding: .day, value: 1, to: dateFrom)
let fromPredicate = NSPredicate(format: "timestamp >= %#", dateFrom as NSDate)
let toPredicate = NSPredicate(format: "timestamp < %#", dateTo! as NSDate)
let waterPredicate = NSPredicate(format: "drinkWater = %#", "1")
let request = NSFetchRequest<NSFetchRequestResult>(entityName: "Drink")
request.predicate = NSCompoundPredicate(andPredicateWithSubpredicates: [waterPredicate, fromPredicate, toPredicate])

Thank you for all of your help. I have it all working now. My final code is:
let appDelegate = UIApplication.shared.delegate as! AppDelegate
let context = appDelegate.persistentContainer.viewContext
var calendar = Calendar.current
calendar.timeZone = NSTimeZone.local
let dateFrom = calendar.startOfDay(for: Date())
let dateTo = calendar.date(byAdding: .day, value: 1, to: dateFrom)
let fromPredicate = NSPredicate(format: "timestamp >= %#", dateFrom as NSDate)
let toPredicate = NSPredicate(format: "timestamp < %#", dateTo! as NSDate)
let waterPredicate = NSPredicate(format: "drinkWater = %#", "1")
let request = NSFetchRequest<NSFetchRequestResult>(entityName: "Drink")
request.predicate = NSCompoundPredicate(andPredicateWithSubpredicates: [waterPredicate, fromPredicate, toPredicate])
do {
let result = try context.fetch(request)
for data in result as! [NSManagedObject] {
print(data.value(forKey: "timestamp") as! Date)
drinkMoreButton.setBackgroundImage(UIImage(named: "watericonselected.png"), for: UIControlState.normal)
}
} catch {
print("Failed")
}

Related

swift coredata query not working for today's date

I have a class named Places managed by CoreData. This object has a Date object as a timestamp.
I have this query to probe for only today's places. The issue is that I have only 2 places logged in today, however it's yielding me 21 records, which I do not expect
func updatePlaceMarksForADateFromDatabase(date: Date) {
var calendar = Calendar.current
calendar.timeZone = TimeZone(identifier: "UTC")!
let dateFrom = calendar.startOfDay(for: date)
let dateTo = calendar.date(byAdding: .day, value: 1, to: dateFrom)
// Set predicate as date being compliant with date
let fromPredicate = NSPredicate(format: "%# >= %#", date as NSDate, dateFrom as NSDate)
let toPredicate = NSPredicate(format: "%# < %#", date as NSDate, dateTo! as NSDate)
let datePredicate = NSCompoundPredicate(andPredicateWithSubpredicates: [fromPredicate, toPredicate])
// CoreData API's to list only today places
let fetchRequest: NSFetchRequest<Places> = Places.fetchRequest()
fetchRequest.predicate = datePredicate
if let context = (UIApplication.shared.delegate as? AppDelegate)?
.persistentContainer.viewContext {
let sort = NSSortDescriptor(key: #keyPath(Places.timestamp), ascending: false)
fetchRequest.sortDescriptors = [sort]
if let placesCoreData = try?
context.fetch(fetchRequest) {
// Store it to the cache
print(placesCoreData.count)
}
}
}
Your predicate are wrong.
let fromPredicate = NSPredicate(format: "%# >= %#", date as NSDate, dateFrom as NSDate)
let toPredicate = NSPredicate(format: "%# < %#", date as NSDate, dateTo! as NSDate)
let datePredicate = NSCompoundPredicate(andPredicateWithSubpredicates: [fromPredicate, toPredicate])
You aren't testing it against the properties of Places. Currently, it's equivalent to NSPredicate(value: true), because date is greater or equal to dateFrom and less that dateTo. It should always be true (according to how values are created). That's why you are getting all the places when fetching.
It should be:
let fromPredicate = NSPredicate(format: "%K >= %#", argumentArray: [#keyPath(Places.dateVariableName), dateFrom]
let toPredicate = NSPredicate(format: "%K < %#", argumentArray: [#keyPath(Places.dateVariableName), dateTo]

How do i display CoreData data from the past week or month?

I have data saved as meals; Breakfast, Lunch, Dinner and Snacks. I am trying to return a total for the calories from all meals for each day over the previous week.
The meal data is saved as below with .date :
func saveBreakfast() {
let newBreakfastItem = BreakfastItem(context: self.moc)
newBreakfastItem.id = UUID()
newBreakfastItem.name = self.item.name
newBreakfastItem.calories = Int32(self.totalCalories)
newBreakfastItem.carbs = Int32(self.totalCarbs)
newBreakfastItem.protein = Int32(self.totalProtein)
newBreakfastItem.fat = Int32(self.totalFats)
newBreakfastItem.date = self.dateAdded
do {
if self.mocB.hasChanges { // saves only if changes are made
try? self.mocB.save()
}
}
}
func saveLunch() {
let newLunchItem = LunchItem(context: self.moc)
newLunchItem.id = UUID()
newLunchItem.name = self.item.name
newLunchItem.calories = Int32(self.caloriesPerServing)
newLunchItem.carbs = Int32(self.carbsPerServing)
newLunchItem.protein = Int32(self.proteinPerServing)
newLunchItem.fat = Int32(self.fatsPerServing)
newLunchItem.date = self.dateAdded
do {
if self.mocL.hasChanges {
try? self.mocL.save()
}
}
}
I am currently working with the below function to try and get it working for a single day (Date() - 1 day, so yesterday)
func dayOneCal(at date: Date) -> NSNumber {
let request1:NSFetchRequest<BreakfastItem> = BreakfastItem.fetchRequest() as! NSFetchRequest<BreakfastItem>
let request2:NSFetchRequest<LunchItem> = LunchItem.fetchRequest() as! NSFetchRequest<LunchItem>
let request3:NSFetchRequest<DinnerItem> = DinnerItem.fetchRequest() as! NSFetchRequest<DinnerItem>
let request4:NSFetchRequest<SnackItem> = SnackItem.fetchRequest() as! NSFetchRequest<SnackItem>
let startDate = Calendar.current.startOfDay(for: date)
var components = DateComponents()
components.day = -1
components.second = -1
let endDate = Calendar.current.date(byAdding: components, to: startDate)!
request1.predicate = NSPredicate(format: "date >= %# AND date <= %#", startDate as NSDate, endDate as NSDate)
request2.predicate = NSPredicate(format: "date >= %# AND date <= %#", startDate as NSDate, endDate as NSDate)
request3.predicate = NSPredicate(format: "date >= %# AND date <= %#", startDate as NSDate, endDate as NSDate)
request4.predicate = NSPredicate(format: "date >= %# AND date <= %#", startDate as NSDate, endDate as NSDate)
let totalDailyBreakfastCals = BreakfastItems.map({$0.calories}).reduce(0, +)
let totalDailyLunchCals = LunchItems.map({$0.calories}).reduce(0, +)
let totalDailyDinnerCals = DinnerItems.map({$0.calories}).reduce(0, +)
let totalDailySnacksCals = SnackItems.map({$0.calories}).reduce(0, +)
let totalDailyCals = totalDailyBreakfastCals + totalDailyLunchCals + totalDailyDinnerCals + totalDailySnacksCals
return totalDailyCals as NSNumber
}
Any help would be greatly appreciated, thank you in advance!

Core Data. Filter by date

I have a .xcdatamodel in Core Data with the Entity Content and the Attributes dateCreated Type Date and text1 Type String.
//CREATE
func createData() {
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else { return }
let managedContext = appDelegate.persistentContainer.viewContext
let contentEntity = NSEntityDescription.entity(forEntityName: "Content", in: managedContext)!
let content = NSManagedObject(entity: contentEntity, insertInto: managedContext)
content.setValue(Date(), forKey: "dateCreated")
content.setValue("someText", forKey: "text1")
do {
try managedContext.save()
} catch let error as NSError {
print("Could not save. \(error), \(error.userInfo)")
}
}
//READ
func readData() {
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else { return }
let managedContext = appDelegate.persistentContainer.viewContext
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "Content")
fetchRequest.predicate = NSPredicate(format: "text1 = %#", "some text")
do {
let result = try managedContext.fetch(fetchRequest)
for data in result as! [NSManagedObject] {
print(data.value(forKey: "dateCreated") as! Date)
print(data.value(forKey: "text1") as! String)
}
} catch {
print("Failed")
}
}
if I print dateCreated, it gives: 2020-03-12 10:20:42 +0000
My question is: how can I read and filter by date? Only date, not time. For instance 2020-03-12 or any other date without time:
fetchRequest.predicate = NSPredicate(format: "dateCreated = %#", ?????)
In CreateData() you're storing the date by using Date(). So that it will be stored in you're database with the format yyyy-MM-dd HH:mm:ss ZZZZ. That's why it prints this 2020-03-12 10:20:42 +0000
Change from
fetchRequest.predicate = NSPredicate(format: "text1 = %#", "some text")
to
fetchRequest.predicate = NSPredicate(format: "dateCreated = %#", Date())
if you using this predicate instead of text1, the core data will give you result with Date and it'll be difficult to filter if there is time. So you have to insert the date by changing its format to yyyy-MM-dd
You can change the current date format by using DateFormatter().
//Needed date format
let dateFormat = DateFormatter()
dateFormat.dateFormat = "yyyy-MM-dd"
let formattedDate = dateFormat.string(from: Date())
So try by inserting the Date() by changing its format
//in CreateData()
content.setValue(formattedDate, forKey: "dateCreated")
//in ReadData()
fetchRequest.predicate = NSPredicate(format: "dateCreated = %#", formattedDate)

Error handling on fetch request and NSpredicate

I am extracting data from the past week using CoreData. It all works fine for days when some data is stored. However, when no data is stored, the program crashes. How can I handle this error?
for i in 0...6 { // Get every day in the past week
let appDel = (UIApplication.sharedApplication().delegate as! AppDelegate)
let context: NSManagedObjectContext = appDel.managedObjectContext
let request = NSFetchRequest(entityName: "Pulser")
request.returnsObjectsAsFaults = false
let calendar = NSCalendar.currentCalendar()
let now = NSDate()
let beginningOfToday = calendar.startOfDayForDate(now)
let previousDay = calendar.startOfDayForDate(calendar.dateByAddingUnit(.Day, value: -i, toDate: beginningOfToday, options: [])!)
let previousPreiviousDay = calendar.startOfDayForDate(calendar.dateByAddingUnit(.Day, value: -i - 1 , toDate: beginningOfToday, options: [])!)
request.predicate = NSPredicate(format:"(date >= %#) AND (date < %#)", previousPreiviousDay, previousDay)
do {
let results = try context.executeFetchRequest(request) as! [Pulser]
pulseArray.append(meanValue(results)) // if more than one value stored for each day, take the mean value of it
}
catch let error as NSError {
print(error)
}
}
It works like a charm now, thanks.
if results.count > 0 {
pulseArray.append(meanValue(results))
}

Swift - timeIntervalSinceNow Only want to show up to minutes (excluding the seconds part)

I'm using a timeIntervalSinceNow function to determine the difference between a certain time and the current time. I have used the "ZeroFormattingBehaviour" to .DropAll to get rid of any 0's in the date. However I want to make it so the difference doesn't include the seconds difference. For example if the difference is 2 days , 5 hours , 25min and 40 sec I want it to only show 2days,5h,25min. (e.g. excluding the seconds part) Is there any way to do so?
Code is as follows:
let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
let managedContext = appDelegate.managedObjectContext
let fetchRequest = NSFetchRequest(entityName: "Values")
let sortDescriptor = NSSortDescriptor(key: "time", ascending: true)
fetchRequest.sortDescriptors = [sortDescriptor]
do {
let results = try managedContext.executeFetchRequest(fetchRequest)
Values = results as! [NSManagedObject]
for result in results as! [NSManagedObject] {
times = result.valueForKey("time") as! String
let timeFormatter = NSDateFormatter()
timeFormatter.locale = NSLocale.currentLocale()
timeFormatter.dateFormat = "HH:mm dd/MM/yy"
let otherTime = timeFormatter.dateFromString(times)
let dateComponentsFormatter = NSDateComponentsFormatter()
dateComponentsFormatter.unitsStyle = NSDateComponentsFormatterUnitsStyle.Abbreviated
dateComponentsFormatter.zeroFormattingBehavior = NSDateComponentsFormatterZeroFormattingBehavior.DropAll
let difference = otherTime?.timeIntervalSinceNow
let diffAbs = abs(difference!)
let stringDiff = dateComponentsFormatter.stringFromTimeInterval(diffAbs)
// "\n" skips a line in the text
TimerLabel.text = stringDiff!
}
} catch let error as NSError {
TimerLabel.text = "There seems to be an error. Please try again later"
print("Could not fetch \(error), \(error.userInfo)")
}
Thanks
Specify the units you want to display
dateComponentsFormatter.allowedUnits = [.Day, .Hour, .Minute]