getting specific object in fetchedObjects without iteration - ios

My app has a custom object SSSchedule that I persist in CoreData, with a sortDescriptor of "date" (SSSchedule has a variable var date : NSDate?). Is there a more efficient method to finding a specific SSSchedule object with a specific date rather than iterating through the fetchedObjects array checking each for schedule.date == myDate as! NSDate?
My app references the fetchedObjects quite often, so I would imagine constantly mapping fetchedObjects to a dictionary of type [String : SSSchedule] (for example) every time the context is saved would affect performance...

Write a fetch request to return the objects matching that specific date from the datastore. If you are being consistent, then from what you've written you'll get back an array with one element.
Let Core Data do that searching for you. That's what it's for.

I think if you use a plist which has a Dictionary of Dictionaries. it could be a more what you need.
The first Dictionary will have a key of a tuple of (Day,Month,Year) which can be easily extracted from NSDate. and a value of a Dictionary which key is a tuple of (Hours, Minutes) also extracted from NSDate and a value of String which is the task to do at that time.
this way if you have a specific date, that date is the key to access only the tasks and events you have during that specific date in O(1) time complexity.
Now if you want to know if you have something at a specific time you access it in a similar way. The method is supposed to return String?. If there's a task at a specific time, it will return the task, otherwise it will return nil which means you're free at this time.
This is how the data structure should look [(Day, Month,Year):[(Hours,Minutes):String]]
Regarding extracting components from NSDate
let date = NSDate()
let calendar = NSCalendar.currentCalendar()
let components = calendar.components(.CalendarUnitHour |.CalendarUnitMinute
|.CalendarUnitYear|.CalendarUnitMonth|.CalendarUnitDay, fromDate: date)
let day = components.day
let month = components.month
let year = compononets.year
let hour = components.hour
let minutes = components.minutes

Use filter() to build a new array with any objects that match your criteria:
let newArray = fetchedObjects.filter() { $0.date == myDate as! NSDate }
Then check the count of newArray and handle accordingly - unless you know the dates are unique, there could be zero, one or more elements in the array.

Related

Sort received dates from response in swift

I am working on code where I am receiving lots of data associated with dates
each object having one date parameter and there might many objects with the same date.
I need to show this all objects in UITableView. each object as one cell.
I succeed in that,
I need to get unique dates from the response array of objects.
Those unique dates will be stored in an array which will act as a number of sections of my table view with section header title will be the date from the unique date array.
somehow I am able to sort out that with what I want,
The only problem I am facing is I am not able to sort the unique date array
every time the sequence change.
I need the latest date as the first date and the oldest date as the end date.
How to achieve this in swift.
Following is a piece of code I have written
let sortedKeys = Array(dictValue.keys).sorted(by: {$0 > $1})
print(sortedKeys)
here dicValue.keys is my unique date array and I wanted to sort it.
Following is a sample response I am getting
["08/03/2021”, “10/02/2021”, "26/04/2021", "25/03/2021”, "09/12/2020”, , "27/04/2021”, "23/03/2021”, "11/01/2021”, "05/03/2021”, "09/03/2021”, "16/10/2020", "19/03/2021", "12/10/2020" ]
and after applying sort I am getting the following output
[“27/04/2021", "26/04/2021", "25/03/2021", "23/03/2021", "19/03/2021", "16/10/2020", "12/10/2020", "11/01/2021", "10/02/2021", "09/12/2020", "09/03/2021", "08/03/2021", "05/03/2021”]
where dates are not properly sorted out.
Can anyone please help me out with it.
Thanks in advance.
This string date format is inappropriate for sorting, because the most significant component is day. Only a date format like yyyy/MM/dd can be sorted properly by comparison operator >.
However this is Swift. The closure can contain anything as long as it returns a Bool. You could sort the array with a custom sort algorithm. It splits the strings into components and sorts first year then month then day
let sortedKeys = dictValue.keys.sorted { (date1, date2) -> Bool in
let comps1 = date1.components(separatedBy: "/")
let comps2 = date2.components(separatedBy: "/")
return (comps1[2], comps1[1], comps1[0]) > (comps2[2], comps2[1], comps2[0])
}
print(sortedKeys)
If you want to sort a date, just sort a Date. Date supports Hashable and can be used as a dictionary key, you could map your original dictionary and by using a DateFormatter to format your string keys into Dates then you can easily sort them.
let dictionary = ["08/03/2021": 2, "10/02/2021": 5, "26/04/2021" : 6]
let formatter = DateFormatter()
formatter.dateFormat = "dd/MM/yyyy" // You should probably adjust other properties of the formatter
let newDict = Dictionary(uniqueKeysWithValues:
dictionary.map { (key, value) -> (Date, Int) in
print("Key: \(key)")
return (formatter.date(from: key)!, value)
})
let sortedDates = newDict.keys.sorted { $0 > $1 }
let value = newDict[sortedDates[0]]

FetchedResultsController with transient attribute for comparing dates

I have a JOURNEY entity in CoreData. It has arrivalDate and departureDate as attributes.
arrivalDate and departureDate both are of type NSDate?
I want to fetch only those journey whose duration is more than an hour.
To achieve this I created a transient attribute in my Journey+CoreDataClass
public var isJourneyLongEnough: Bool{
if let arrival = arrivalDate as Date?, let departure = departureDate as Date?{
let duration = departure.timeIntervalSince(arrival)
return duration >= 3600 ? true : false
} else{
return false
}
}
When I tried fetching it crashes with an error: *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'keypath isJourneyLongEnough not found in entity.
On researching more about this issue I figured out that we can't use transient property in predicate as these doesn't exist when fetching managed objects from persistentStore.
You cannot fetch using a predicate based on transient properties (although you can use transient properties to filter in memory yourself). ... To summarize, though, if you execute a fetch directly, you should typically not add Objective-C-based predicates or sort descriptors to the fetch request. Instead you should apply these to the results of the fetch.
What is the best way to achieve this? I am using a FetchedResultsController to display all journeys in a UITableView
Timestamps are stored as seconds (since the reference date Jan 1, 2001)
in the Core Data SQLite file, and one can simply check if the difference between
departure and arrival date is at least 3600 seconds:
let predicate = NSPredicate(format: "arrivalDate - departureDate >= 3600")
fetchRequest.predicate = predicate
This worked as expected in my test. If the minimal journey duration
is determined at runtime, use
let minDuration = 3600
let predicate = NSPredicate(format: "arrivalDate - departureDate >= %#", minDuration as NSNumber)
Remark: The above predicates work only if used in a Core Data
fetch request. The following variant works both with Core Data
fetch requests and with directly evaluated predicates:
NSPredicate(format: "arrivalDate.timeIntervalSinceReferenceDate - departureDate.timeIntervalSinceReferenceDate >= 3600")
You could try the following NSPredicate, it‘s based on another question and essentially casts the dates to NSNumber, subtracts them and then compares the result to be bigger or smaller than 3600 seconds.
I haven‘t tried it, but maybe it helps. The original question was in ObjC though, so maybe some things changed.
NSPredicate(format: “departureDate != nil AND arrivalDate != nil AND CAST(departureDate, ‘NSNumber‘) - CAST(arrivalDate, ‘NSNumber‘) >= 3600“)

Inserting into Array and comparing Dates Swift iOS Code

I am attempting to create an array that will store 365 integers, it must be filled completely. I am using Healthkit to figure out the users steps from a year back, hence the array size. Every integer represents 1 day.
I have done this in android already and it worked perfectly, I got 365 integers back with 0's for the days with no steps, however, the problem is with iOS health kit I get nothing from days with no data, which I need. In order to do this I thought I would compare the date variable I get with the date of the current day + 1 and loop through the array to see if it find any matching cases, if not put a 0 into it at the end.
So in order to do this I created an array of 365, at the line var ID = 0 is where I attempt to store the integers correctly into the array. I am using Swift 4.
struct stepy {
static var step = [365]
}
This is where I enumerate through the stepData, first at var ID I attempt to compare the date I get in the enumerate loop with the current date (basically index 0 in the array, which represents the first day, the current day).
However I got a problem, currently I believe I would overwrite the days which already has been inputted into the date at the second step enumeration? Also I can't get the date code to compile properly, I just get the Date has no valid member called "add"
stepsQuery.initialResultsHandler = { query, results, error in
let endDate = NSDate()
let startDate = calendar.date(byAdding: .day, value: -365, to: endDate as Date, wrappingComponents: false)
if let myResults = results{
myResults.enumerateStatistics(from: startDate!, to: endDate as Date) { statistics, stop in
if let quantity = statistics.sumQuantity(){
var date = statistics.startDate
let steps = quantity.doubleValue(for: HKUnit.count())
var id = 0
var dateToInsert = date
var today = Date()
var todaytwo = Date()
for index in 0..<stepy.step.count {
if dateToInsert != today {
id = index + 1
today.(Calendar.current.date(byAdding: .day, value: -1, to: today)
stepy.step.append(0)
}
if date == dateToInsert as Date {
today.add(Calendar.current.date(byAdding: .day, value: -1, to: today)
stepy.step.append(Int(steps))
id = index + 1
}
}
}
}
}
}
static var step = [365]
The above doesn't make sense. It does not create an array of 365 integers, it creates an array with one integer in it that is 365. What you need is
static var step: [Int] = []
which creates an empty array you can just append your results to
currently I believe I would overwrite the days which already has been inputted into the date at the second step enumeration?
because your code appends to the array, which is the same as in Java: myArrayList.add(element), this is not a problem.
Also I can't get the date code to compile properly, I just get the Date has no valid member called "add"
Correct, it doesn't. Also this line:
today.(Calendar.current.date(byAdding: .day, value: -1, to: today)
does not make any sense. That should be causing a compiler error to.
Anyway, I don't see what the point of all that is. Your outer loop presumably loops through the statistics, one per day, so just do your calculation and append to the array. It'll be oldest first, but you can then just reverse the array to get newest first.

How can I sort a UITableView using CoreData information

Right now I use this func to fetch my UITableView and it works fine, but I want to sort it by "dataUltimei" which is a date (ex: 07.08.2017). I don't know why but it doesn't sort the table. This is my code:
func fetch() {
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else { return }
let managedObjectContext = appDelegate.persistentContainer.viewContext
let sortDescriptor = NSSortDescriptor(key: "dataUltimei", ascending: true)
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName:"Contact")
fetchRequest.sortDescriptors = [sortDescriptor]
do {
contacts = try managedObjectContext.fetch(fetchRequest) as! [NSManagedObject]
} catch let error as NSError {
print("Could not fetch. \(error)")
}
}
Since you're storing the date as a string, you will not be able to get Core Data to sort it as a date. Core Data doesn't know that your string is a date, so it sorts it using string rules instead of numeric date comparisons, which is what you're seeing.
If you need to sort by date, you need to have a date. Save the date in Core Data as a date field and sort on that value. Then use a DateFormatter to convert the date to a string for your UI.
Sorting would probably work if you formatted the date string as year-month-day, something like 2017-08-22. But comparing strings like this will be much much slower than comparing dates. It might give the right sorting result but only with much worse performance.
I figured out that my func sorts the tabel but not in the way I want. "dataUltimei" represents the date as a string (June,12,2017) and it sorts it alphabetically. Now I'm trying to convert my date type (June,12,2017) into something like this (06.12.2017) and maybe it will work

iterate through array and store results

I have an array called thoughtArray which is an array of a custom object called ThoughtObject. ThoughtObject has a property called 'createdDate' which holds an NSDate, the date that the object was created.
I need to filter through that array and find all the objects that match the current date and then append them to another array.
So far all attempts have been unsuccessful. This is what i've tried below.
for createdToday in thoughtArray {
if (createdToday.createdDate?.isEqualToDate(NSDate()) != nil) {
createdTodayArray.append(createdToday)
}
}
The problem is that even objects that have the createdToday property set to a few days ago get added to the array.
Help would be greatly appreciated.
There are two problems. First, the "optional chaining"
createdToday.createdDate?.isEqualToDate(NSDate()
returns nil or not, depending on whether createdToday.createdDate
is nil or not. That is not what you want.
Second, as already stated by #thelaws in his answer, isEqualToDate()
returns only yes if the two dates represent exactly the same moment in time. NSCalendar has a
func compareDate(date1: NSDate, toDate date2: NSDate, toUnitGranularity unit: NSCalendarUnit) -> NSComparisonResult
method (available since iOS 8) which can be used here:
let cal = NSCalendar.currentCalendar()
let now = NSDate()
for createdToday in thoughtArray {
if let createdAt = createdToday.createdDate {
// compare with "day granularity":
if cal.compareDate(createdAt, toDate: now, toUnitGranularity: .CalendarUnitDay) == .OrderedSame {
createdTodayArray.append(createdToday)
}
}
}
An NSDate object represents a specific moment in time. Thus, it's very unlikely that a date in your array represents exactly NOW.
There are several approaches to determine if a date is today. You could turn the date into NSDateComponents and compare the year, month and day.
Use the compareDate function mentioned by Martin and the filter function of Array:
var thoughtArray = [ThoughtObject]()
var cal = NSCalendar.currentCalendar()
var createdToday = thoughtArray.filter {
if let createdDate = $0.createdDate {
return cal.compareDate(NSDate(), toDate: createdDate, toUnitGranularity: .CalendarUnitDay) == .OrderedSame
}
else {
return false;
}
}

Resources