swift coredata query not working for today's date - swift

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]

Related

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!

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

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")
}

Fetch All Photos from Library based on creationDate in Swift [Faster Way?]

I have a UICollectionView displaying library photos based on latest "creationDate". For that I am using below code:
struct AssetsData {
var creationDate: Date, assetResult: PHFetchResult<PHAsset>
}
func fetchPhotos() -> [AssetsData] {
//Date Formatter
let formatter = DateFormatter()
formatter.dateStyle = DateFormatter.Style.medium
formatter.timeStyle = DateFormatter.Style.none
//Photos fetch
let fetchOptions = PHFetchOptions()
let sortOrder = [NSSortDescriptor(key: "creationDate", ascending: false)]
fetchOptions.sortDescriptors = sortOrder
let assetsFetchResult = PHAsset.fetchAssets(with: .image, options: fetchOptions)
var arrCreationDate = [Date]()
var arrDates = [String]()
//Getting All dates
for index in 0..<assetsFetchResult.count {
if let creationDate = assetsFetchResult[index].creationDate {
let formattedDate = formatter.string(from: creationDate)
if !arrDates.contains(formattedDate) {
arrDates.append(formattedDate)
arrCreationDate.append(creationDate)
}
}
}
//Fetching Assets based on Dates
var arrPhotoAssetsData = [AssetsData]()
for createdDate in arrCreationDate {
if let startDate = getDate(forDay: createdDate.day, forMonth: createdDate.month, forYear: createdDate.year, forHour: 0, forMinute: 0, forSecond: 0), let endDate = getDate(forDay: createdDate.day, forMonth: createdDate.month, forYear: createdDate.year, forHour: 23, forMinute: 59, forSecond: 59) {
fetchOptions.predicate = NSPredicate(format: "creationDate > %# AND creationDate < %#", startDate as NSDate, endDate as NSDate)
let assetsPhotoFetchResult = PHAsset.fetchAssets(with: PHAssetMediaType.image, options: fetchOptions)
arrPhotoAssetsData.append(AssetsData(creationDate: createdDate, assetResult: assetsPhotoFetchResult))
}
}
return arrPhotoAssetsData
}
func getDate(forDay day: Int, forMonth month: Int, forYear year: Int, forHour hour: Int, forMinute minute: Int, forSecond second: Int) -> Date? {
var dateComponents = DateComponents()
dateComponents.day = day
dateComponents.month = month
dateComponents.year = year
dateComponents.hour = hour
dateComponents.minute = minute
dateComponents.second = second
var gregorian = Calendar(identifier: Calendar.Identifier.gregorian)
gregorian.timeZone = NSTimeZone.system
return gregorian.date(from: dateComponents)
}
The code works nicely! But the problem is it takes almost 7 - 9 seconds to load 10k+ photos. Till 6k photos there is no problem, but I really need some efficient way so that I can load some of the asset in UICollectionView and rest of them I can add later. I need that no matter the photos count, it should not take more than 2 - 3 seconds. Can anybody please help?
Let's say you have 8k photos. So you iterate through two 'for' loops in order to get the arrCreationDate and arrPhotoAssets data(which is double the work needed)
Instead, you can try doing it through a single loop. Here's a rough way:-
let assetsFetchResult = PHAsset.fetchAssets(with: .image, options: fetchOptions)
let fetchOptions = PHFetchOptions()
var arrCreationDate = [Date]()
var arrPhotoAssetsData = [AssetsData]()
var arrDates = [String]()
for index in 0..<assetsFetchResult.count {
if let creationDate = assetsFetchResult[index].creationDate {
let formattedDate = formatter.string(from: creationDate)
if !arrDates.contains(formattedDate) {
//You can convert the formattedDate to actual date here and do a check similar to this, do what you do in the other loop here too
if(actualDate < actualDateOfTheFirstElementAtArray){
arrCreationDate.insert(actualDate, at: 0)
fetchOptions.predicate = NSPredicate(format: "creationDate > %# AND creationDate < %#", startDate as NSDate, endDate as NSDate)
let assetsPhotoFetchResult = PHAsset.fetchAssets(with: PHAssetMediaType.image, options: fetchOptions)
arrPhotoAssetsData.insert(AssetsData(creationDate: createdDate, assetResult: assetsPhotoFetchResult), at: 0)
}
}
}
}
This is just for you to get a rough idea of what I'm talking about, as this will reduce half the burden(just a single loop)
Also try using prefetchDataSource for your collection view to preload it with some data
EDIT:-
I assume that you have tried the following already:-
func fetchPhotos() -> [AssetsData] {
//Date Formatter
let formatter = DateFormatter()
formatter.dateStyle = DateFormatter.Style.medium
formatter.timeStyle = DateFormatter.Style.none
//Photos fetch
let fetchOptions = PHFetchOptions()
let sortOrder = [NSSortDescriptor(key: "creationDate", ascending: false)]
fetchOptions.sortDescriptors = sortOrder
let assetsFetchResult = PHAsset.fetchAssets(with: .image, options: fetchOptions)
var arrCreationDate = [Date]()
var arrDates = [String]()
var arrPhotoAssetsData = [AssetsData]()
//Getting All dates
for index in 0..<assetsFetchResult.count {
if let creationDate = assetsFetchResult[index].creationDate {
let formattedDate = formatter.string(from: creationDate)
if !arrDates.contains(formattedDate) {
arrDates.append(formattedDate)
arrCreationDate.append(creationDate)
convertToAssetsDataAndAppend(date: creationDate, fetchOptions: fetchOptions, toArray: &arrPhotoAssetsData)
}
}
}
return arrPhotoAssetsData
}
func convertToAssetsDataAndAppend(date: Date, fetchOptions: PHFetchOptions, toArray: inout [AssetsData]){
if let startDate = getDate(forDay: date.day, forMonth: date.month, forYear: date.year, forHour: 0, forMinute: 0, forSecond: 0), let endDate = getDate(forDay: date.day, forMonth: date.month, forYear: date.year, forHour: 23, forMinute: 59, forSecond: 59) {
fetchOptions.predicate = NSPredicate(format: "creationDate > %# AND creationDate < %#", startDate as NSDate, endDate as NSDate)
let assetsPhotoFetchResult = PHAsset.fetchAssets(with: PHAssetMediaType.image, options: fetchOptions)
toArray.append(AssetsData(creationDate: date, assetResult: assetsPhotoFetchResult))
}
}
func getDate(forDay day: Int, forMonth month: Int, forYear year: Int, forHour hour: Int, forMinute minute: Int, forSecond second: Int) -> Date? {
var dateComponents = DateComponents()
dateComponents.day = day
dateComponents.month = month
dateComponents.year = year
dateComponents.hour = hour
dateComponents.minute = minute
dateComponents.second = second
var gregorian = Calendar(identifier: Calendar.Identifier.gregorian)
gregorian.timeZone = NSTimeZone.system
return gregorian.date(from: dateComponents)
}
If this doesn't help, how about reloading the collection view with some kind of callback after every loop iteration? (with the above approach)
This way, you won't make the user wait until the whole thing gets loaded
Idk, these might look petty but I'm just trying to help :)

Swift 4 NSPredicate date format not working

I am trying to fetch photos and videos from the users camera roll, taken within the last 7 days.
Here is my code...
let oneWeekAgoDate = NSCalendar.current.date(byAdding: .weekOfYear, value: -1, to: NSDate() as Date)
fetchOptions.predicate = NSPredicate(format: "date > %# && (mediaType = %d || mediaType = %d)",oneWeekAgoDate! as NSDate, PHAssetMediaType.image.rawValue, PHAssetMediaType.video.rawValue)
Taken from https://stackoverflow.com/a/30520861/6633865
However my app keeps terminating with this message
Unsupported predicate in fetch options: date > CAST(556405508.551209, "NSDate")
Any ideas?
Thanks
var p: NSPredicate?
let date = Date()
p = NSPredicate(format: "mediaType = %d AND ( creationDate < %# )",PHAssetMediaType.image.rawValue,date as NSDate)
try again!

Error with realm query using NSDate()

i have this code in swift:
lists = sharedAppCore.getRealm().objects(Event).filter("status = 1 OR status = 2").sorted("end_date", ascending: false)
now i want to filter with start_date NSDate() but this not work:
lists = sharedAppCore.getRealm().objects(Event).filter("status = 1 OR status = 2 OR start_date >= \(NSDate())").sorted("end_date", ascending: false)
any ideas?
lists = sharedAppCore.getRealm()
.objects(Event)
.filter("status = 1 OR status = 2 OR start_date >= \(NSDate())")
.sorted("end_date", ascending: false)
Strictly speaking, Above code is not the same as your final code.
filter("status = 1 OR status = 2").filter(predicate).sorted("end_date", ascending: false)
^ Because this predicate same as the following:
filter("(status = 1 OR status = 2) AND end_date >= %#", NSDate())
If you create predicate as all OR, you can just do the following:
filter("status = 1 OR status = 2 OR end_date >= %#", NSDate())
Additionally, if you compare without hours, you should truncate hours from the date first. Then compare with the truncated date.
Like the following:
let now = NSDate()
let calendar = NSCalendar.currentCalendar()
let component = calendar.components([.Year, .Month, .Day], fromDate: now)
let today = calendar.dateFromComponents(component)! // truncated time
Then use truncated date to compare in the predicate.
let now = NSDate()
let calendar = NSCalendar.currentCalendar()
let component = calendar.components([.Year, .Month, .Day], fromDate: now)
let today = calendar.dateFromComponents(component)! // truncated time
let lists = realm
.objects(Event)
.filter("status = 1 OR status = 2 OR end_date >= %#", today)
.sorted("end_date", ascending: false)
solved with this code:
let predicate = NSPredicate(format: "end_date >= %#", NSDate())
lists = sharedAppCore.getRealm().objects(Event).filter("status = 1 OR status = 2").filter(predicate).sorted("end_date", ascending: false)