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

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!

Related

_startDate Date <unavailable; try printing with "vo" or "po">

I want to create a date variable but get the following response:
_startDate Date <unavailable; try printing with "vo" or "po">
The code i have is:
func createMileagesPDF() {
var _someDate: NSDate = NSDate()
self.someDate = Date()
guard (!exportYear.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty && exportYear.count == 4) && cars.count > 0 else {
showAlert.toggle()
return
}
let _dateFormatter = DateFormatter()
//_dateFormatter.dateFormat = "dd-MM-yyyy"
let _startDateString = String(format: "01-01-%#", exportYear)
let _startDate = createdate() // = _dateFormatter.date(from: "01-12-2020 00:00:01")
let _x = createdate()
print("Create PDF")
}
func createdate() -> Date {
return Date()
}
What i want is an startdate based on an given year.
A string converted to an date.
When i do this in PlayGround it all works fine.
Thanks.

Can't get the necessary date

I am trying to get the difference between dates in days. I have a problem translating dates to the desired format.
func daysBefore(_ date: String) -> Int {
let currentDate = Date()
let dateFormater = DateFormatter()
dateFormater.dateFormat = "dd.mm.YYYY"
guard let nextDate = dateFormater.date(from: date) else {
return 0
}
return currentDate.interval(ofComponent: .day, fromDate: nextDate)
}
extension Date {
func interval(ofComponent comp: Calendar.Component, fromDate date: Date) -> Int {
let currentCalendar = Calendar.current
guard let start = currentCalendar.ordinality(of: comp, in: .era, for: date) else { return 0 }
guard let end = currentCalendar.ordinality(of: comp, in: .era, for: self) else { return 0 }
return end - start
}
}
daysBefore("22.09.2019")
When I convert the date from String to Date in:
let currentDate = Date()
let dateFormater = DateFormatter()
dateFormater.dateFormat = "dd.mm.YYYY"
guard let nextDate = dateFormater.date(from: date) else {
return 0
}
I always get something like: 2018-12-22 21:09:00 +0000.
But expected(in example): 22.09.2019.

Swift 4: validating credit card expiration date

I am writing code to check whether the credit card has expired or not.
Here is what I have
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "MM/yyyy"
let enteredDate = dateFormatter.date(from: expiryDate.text!) /* line 3 - set to first day of month */
let now = Date()
if (enteredDate! < now) {
//expired
// does not work if current month and year
// is the same as the expiration date,
// because expiration day is set to the first day of the month on line 3
} else {
// valid
print("valid - now: \(now) entered: \(enteredDate)")
}
Any ideas on how I can change the initialized date to be the last day of the month instead of the first day?
enteredDate will be midnight local time on the first of the month of the expiry date. Since you want that whole month to be valid, add 1 month to that value and then compare Date() to that updated value.
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "MM/yyyy"
let enteredDate = dateFormatter.date(from: expiryDate.text!)!
let endOfMonth = Calendar.current.date(byAdding: .month, value: 1, to: enteredDate)!
let now = Date()
if (endOfMonth < now) {
print("Expired - \(enteredDate) - \(endOfMonth)")
} else {
// valid
print("valid - now: \(now) entered: \(enteredDate)")
}
Please note that I left proper handling of optionals as an exercise for the reader.
Instead of comparing the dates, compare month of the dates using compare(_:to:toGranularity:)
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "MM/yyyy"
if let enteredDate = dateFormatter.date(from: "05/2019") {
let result = Calendar.current.compare(Date(), to: enteredDate, toGranularity: .month)
if result == .orderedSame {
print("valid")
} else if result == .orderedAscending {
print("valid")
} else if result == .orderedDescending {
print("expired")
}
}
rmaddy's answer is perfect. Here is how I thought of using Calendar to handle the validation. Perhaps, I wrote it in more complex way.
enum ExpiryValidation {
case valid, invalidInput, expired
}
func validateCreditCardExpiry(_ input: String) -> ExpiryValidation {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "MM/yyyy"
guard let enteredDate = dateFormatter.date(from: input) else {
return .invalidInput
}
let calendar = Calendar.current
let components = Set([Calendar.Component.month, Calendar.Component.year])
let currentDateComponents = calendar.dateComponents(components, from: Date())
let enteredDateComponents = calendar.dateComponents(components, from: enteredDate)
guard let eMonth = enteredDateComponents.month, let eYear = enteredDateComponents.year, let cMonth = currentDateComponents.month, let cYear = currentDateComponents.year, eMonth >= cMonth, eYear >= cYear else {
return .expired
}
return .valid
}
let invalidInput = validateCreditCardExpiry("hello")
let validInput = validateCreditCardExpiry("09/2020")
let expiredInput = validateCreditCardExpiry("04/2010")
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "MM/yyyy"
let enteredDate = dateFormatter.date(from: expiryDate.text!)
//Also convert the current date in the entered date format and then checks that the date is valid if enteredDate month or year is greater than current date.
let currentDate = dateFormatter.date(from: dateFormatter.string(from: Date()))
if enteredDate.compare(now) != ComparisonResult.orderedAscending {
print("Valid")
} else {
print("Not Valid")
}
As this only compares the month and year so it will resolve your issue of first or last date of month.
Here is the full answer for Expiry date calculation from textfield formatted as MM/YY. Use this in textfieldshouldchangecharactersinrange method
var cleanString = string.replacingOccurrences(of: "/", with: "")
if cleanString.rangeOfCharacter(from: unsupportedCharacterSet) != nil {
return ""
}
let dateString: String
if cleanString.count == 0 {
return string
} else if cleanString.count > 4 {
// trim the string down to 4
let reqIndex = cleanString.index(cleanString.startIndex, offsetBy: 4)
cleanString = String(cleanString[..<reqIndex])
}
if cleanString.hasPrefix("0") == false && cleanString.hasPrefix("1") == false {
dateString = "0" + cleanString
} else {
dateString = cleanString
}
let currentYear = Calendar.current.component(.year, from: Date()) % 100 // This will give you current year (i.e. if 2019 then it will be 19)
let currentMonth = Calendar.current.component(.month, from: Date()) // This will give you current month (i.e if June then it will be 6)
var newText = ""
for (index, character) in dateString.enumerated() {
print("index: \(index)")
if index == 1 {
let enterdMonth = Int(dateString.prefix(2)) ?? 0 // get first two digit from entered string as month
print("enterdMonth at 1:\(enterdMonth)")
if (1 ... 12).contains(enterdMonth){
if enterdMonth < 10 {
newText = "0" + "\(enterdMonth)" + "/"
}else {
newText = "\(enterdMonth)" + "/"
}
}else{
}
}else if index == 2 {
if (2 ... 99).contains(Int(String(character))!) { // Entered year should be greater than 2019.
newText.append(character)
}else{
}
}else if index == 3 {
print("index---: \(index)")
let enterdYr = Int(dateString.suffix(2)) ?? 0 // get last two digit from entered string as year
let enterdMonth = Int(dateString.prefix(2)) ?? 0 // get first two digit from entered string as month
print("enterdYr: \(enterdYr)")
print("currentYear: \(currentYear)")
if (2 ... 99).contains(enterdYr) { // Entered year should be greater than 2019
if enterdYr >= currentYear {
if (1 ... 12).contains(enterdMonth) {
if enterdMonth < 10 {
newText = "0" + "\(enterdMonth)" + "/" + "\(enterdYr)"
}else {
newText = "\(enterdMonth)" + "/" + "\(enterdYr)"
}
return newText
}
}
}
}
else {
newText.append(character)
}
}
return newText
}
if enteredDate.compare(now) == ComparisonResult.orderedDescending
{
print("valid")
}
else{
print("not valid")
}

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 :)