I have an Core Data entity called Event and the two relevant attributes are called startDateTime and countryCode. My problem is that the sections in my table view need to be grouped by day AND country code. I've seen a lot of examples on how the achieve grouping by day only (DateSectionTitles by Apple for example), but it gets a lot more complicated when I add the country code.
I use a transient sectionIdentifier as recommended in the DateSectionTitles example and the sections look something like this: "20110928 SE", "20110929 SE", "20110928 GB" etc.
I first tried this:
NSSortDescriptor *sortDescriptor1 = [[NSSortDescriptor alloc] initWithKey:#"startDateTime" ascending:NO];
NSSortDescriptor *sortDescriptor2 = [[NSSortDescriptor alloc] initWithKey:#"countryCode" ascending:NO];
sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor1, nil];
[sortDescriptor1 release];
[sortDescriptor2 release];
But this doesn't work since startDateTime has a time component which leads to index mismatch. What I need is to sort the startDateTime without the time component.
The obvious solution would be to separate the date and time into two different entity attributes. But when I think about it that wouldn't work since a date is relative to a timezone. When the phone changes timezone the date and times attributes would be invalid.
I've also tried to subclass a NSSortDescriptor and create a custom compareObject function, but it never seems to be called and I can't find any working examples.
How would you solve this? Is the subclassed NSSortDescriptor the way to go, or is there perhaps a much smarter way?
Create a category on NSDate with a method that strips the time from the date returning just the day e.g. converts this:
2011-09-29 09:53:28 -0500
… to this:
2011-09-29
Then set your sort key to dateAttribute.methodName.
Related
Given a table of books published, with a date_published column of typeNSAttributeType.DateAttributeType, I would like to know how many books were published by year like this:
Year | Books
-----+------
2013 | 76
2014 | 172
2015 | 155
In plain old SQL this is simple (although it varies slightly by RDBMS):
SELECT DATEPART(yyyy, date_published) AS "Year", COUNT(*) AS "Books"
FROM books
GROUP BY DATEPART(yyyy, date_published)
I'm new to Swift and iOS in general but everything I looked at suggested either pre-computing the year and storing that, or loading all the data and counting it myself. Neither of these approaches suited me as the year is in fact an accounting year (that can vary after storage) and the amount of data is potentially large.
Most approaches revolved around adding a custom attribute to my NSManagedObject. That seems like it's too late to me because the object would not have been loaded into memory at this stage. There were also discussions around NSFetchedResultsController with sectionNameKeyPath's, but again this feels like it's too late in the fetch process. I found NSExpression convoluted so I may well have missed something but it seems like I can't invoke a custom Swift function here. Really, at the end of the day, I expected to find built-in functions for things like DATEPART, DATEADD, DATEDIFF, and I was hoping somebody could point me in the right direction.
As a more concrete example consider the UK tax year which runs 6 April to 5 April. To calculate the tax year I would subtract 3 months and 6 days (to midnight on 5 April). So for a book published on 1 March 2012 I would do the subtraction which would give me 24 November 2011, including 29 days in February for the leap year. From this I simply extract the year part, 2011. So the UK tax year for 1 March 2012 is 2011. I could precompute 2011 and store that in a new column. But then if I move from the UK to Australia the fiscal year changes to July through June. More likely I have a company with a different accounting period than the fiscal year (very likely in the UK). That company then gets taken over by a US group that uses the calendar year, and everyone is happy, except my little app that thinks March 2012 is in 2011.
Here's some boilerplate to get going... with no attempt to group by year:
// The raw date for grouping by - no attempt to extract year
let date = NSExpressionDescription()
date.expression = NSExpression(format: "date_published")
date.name = "date"
date.expressionResultType = .DateAttributeType
// The number of books
let books = NSExpressionDescription()
books.expression = NSExpression(format: "count:(publication_title)")
books.name = "books"
books.expressionResultType = .Integer32AttributeType
// Put a fetch together
let fetch = NSFetchRequest(entityName:"Book")
fetch.resultType = .DictionaryResultType
fetch.propertiesToFetch = [date, books]
fetch.propertiesToGroupBy = [date]
// Execute now
var error: NSError?
if let results = context.executeFetchRequest(fetch,
error: &error) as Array<NSDictionary>? {
for row in results {
let date = row.valueForKey("date") as? NSDate
let books = row.valueForKey("books") as? Int
NSLog("%# %d", date!, books!)
}
} else {
NSLog("Fail!")
}
Thanks for any pointers!
As you're finding, this touches on something that's a weak spot in Core Data's API. It's common to explain that one shouldn't think of Core Data in terms of SQL because it uses a different approach. Dates are where this can be really annoying, because Core Data hides some SQLite capabilities. (They do this at least partly because Core Data isn't a SQLite wrapper, and can work with other, non-SQL storage systems).
The core problem is that Core Data's "Date" type corresponds to an NSDate, and NSDate in turn is just a floating-point number representing the number of seconds since a reference date. It doesn't include year, month, or day. Those values are not even fixed, because the instant in time represented by an NSDate might mean a different date in California as opposed to Japan, for example. The word "date" in these type names is unfortunately misleading.
This is why people generally recommend using extra fields, or at least different data types, for apps using Core Data that need to consider the actual date in some time zone as opposed to a precise moment in time regardless of zone. There isn't a good way to construct a Core Data query that operates on a "Date" field that does what you need. Dealing with this comes down to storing the data you actually need instead of something that just approximates what you need-- except that calling this type "Date" confuses the choice. You don't want a Core Data "Date" type here.
So let's consider one approach to getting the result you need while making SQLite do as much of the work as possible. Let's suppose you replace your date field with an integerDate field that represents the date as an integer (Core Data "Integer 64") using the format yyyyMMDD. Today would be stored as 20151223. In theory this could be done in one step with some NSExpression wizardry, but Core Data doesn't let you group by expressions, so that's out.
Step 1: Get all distinct year values
NSExpression *yearExpression = [NSExpression expressionWithFormat:#"divide:by:(%K,10000)", #"integerDate"];
NSExpressionDescription *yearExpDescription = [[NSExpressionDescription alloc] init];
yearExpDescription.name = #"year";
yearExpDescription.expression = yearExpression;
yearExpDescription.expressionResultType = NSInteger64AttributeType;
NSFetchRequest *distinctYearsRequest = [NSFetchRequest fetchRequestWithEntityName:#"Event"];
distinctYearsRequest.resultType = NSDictionaryResultType;
distinctYearsRequest.returnsDistinctResults = YES;
distinctYearsRequest.propertiesToFetch = #[ yearExpDescription ];
NSError *fetchError = nil;
NSArray *distinctYearsResult = [self.managedObjectContext executeFetchRequest:distinctYearsRequest error:&fetchError];
if (distinctYearsResult != nil) {
NSLog(#"Results by year: %#", distinctYearsResult);
}
NSArray *distinctYears = [distinctYearsResult valueForKey:#"year"];
In the above, yearExpression gets the year portion of integerDate by simple division. When the above completes, distinctYears contains all the years represented by integerDate.
Step 2: Loop through years, getting a count for each:
NSMutableDictionary *countByYear = [NSMutableDictionary dictionary];
for (NSNumber *year in distinctYears) {
NSFetchRequest *countForYearFetch = [NSFetchRequest fetchRequestWithEntityName:#"Event"];
countForYearFetch.resultType = NSDictionaryResultType;
countForYearFetch.propertiesToFetch = #[ yearExpDescription ];
NSExpression *targetYearExpression = [NSExpression expressionForConstantValue:year];
NSPredicate *yearPredicate = [NSComparisonPredicate predicateWithLeftExpression:yearExpression rightExpression:targetYearExpression modifier:NSDirectPredicateModifier type:NSEqualToPredicateOperatorType options:0];
countForYearFetch.predicate = yearPredicate;
NSError *fetchError = nil;
NSUInteger countForYear = [self.managedObjectContext countForFetchRequest:countForYearFetch error:&fetchError];
countByYear[year] = #(countForYear);
}
NSLog(#"Results by year: %#", countByYear);
This does a separate fetch for each year, but keeps the memory overhead low by only fetching the count of results instead of the actual data. When this finishes, countByYear has the number of entries by year, based on the integerDate field.
Having said all this, keep in mind that you do have the option of using SQLite directly instead of using Core Data. PLDatabase will give you an Objective-C style wrapper while still allowing raw SQL queries for everything SQLite can do.
My data looks like:
objA date1 objnumber2
objB date2 objnumber1
objC date2 objnumber4
objD date2 objnumber3
objE date1 objnumber7
objF date3 objnumber6
objG date1 objnumber5
I am looking for all the objects which are from the Nth last date. The result objects need to be sorted using objnumber (this should be easy using NSSortDescriptor).
So if I specify N=1 (most recent date), I should get [objF] only. (date3 is most recent)
If N=3 (oldest date), I should get sorted [objA, objG, objF]
The sorting part is easy.
My question is do I really need to firstly search for the latest date (using combination of sortdescriptor and nsfetchrequest searchLimit) in the entire data? Then do a second search to find all objects from that date (using nspredicate) and sort it?
Or is there a better way to perform this type of search? How would you generalize this for Nth date instead of last date? That would be a big performance hit no??
NOTE that the date is not known beforehand.
Edit2: okay this is even more complicated since I am using NSDate. So pretty much all the objects have unique dates lol. Gotta throw nsdateformatter in there in the mix too :(
I figured out a solution to my problem.
Instead of using the date, I added a new Int64 NSNumber attribute. Everyday the obj is added, all the objects are tagged with that number for that particular day.
For retriving all objects from Nth day, I firstly do a "fetchlimit 1" nsfetchrequest for that number in a sorted fetchrequest. That gives me the last number.
Now for the Nth last objects, I subtract N from the last number. Then I perform a "nspredicted" nsfetchrequest for all objects with that number attribute. Then I simply just sort the result array.
This resolves my question :D
my app is in the appstore and after updating to ios 8 and testing the app on my new iphone 6 the app doesnt work correctly.
ive already found the problem: i stored 3 dimensional arrays in nsuserdefaults and its giving me the error:mutating object sent to immutable....
so i can forget to store 3d arrays in nsuserdefaults, because i can`t really change those.
My idea was to store all the data in core data. GREAT!
But i gotta learn!
So....
I have got 5 attributes like you can see on the image:
when pushing an add-button all of the 5 attributes get saved by core data in the sqlite file.
The attribute "datum" of type date comes from a datepicker,so i can select a date. the other attributes are hours,minutes and some note - strings.
so, i want to look my tableview like this:
2014 <--- sectiontitle with year
-October <--- rows cell.text
-November <--- rows cell.text
2015 <--- sectiontitle with year
-January <--- rows cell.text
-May <--- rows cell.text
In this example i made entries for october+november in 2014 and january+may in 2015.
This means, this tableview is an overview to all the data,that i want to be filtered in the year and month.
what is the best approach to do this.
is there an easier way than: fetching the date, putting the dates into an array, setting dateformatter and find non-equal dates to get the years and the months?
because, when pushing a cell, for example october in section 0, a detailview opens and i want to display all entries i made in october in 2014.
What is your advice? Please help!!!
If user defaults is sufficient for your needs, I'd stick with it but convert the data to something mutable when you read it. You can convert an immutable property list to a mutable one using CFPropertyListCreateDeepCopy. As the name implies, it's a deep copy, and you can have it convert mutable containers and/or leaves to mutable equivalents.
If you read from user defaults into NSDictionary *userDefaultsData, convert with
NSDictionary *mutableData = CFPropertyListCreateDeepCopy(NULL, userDefaultsData, kCFPropertyListMutableContainers);
That will recursively copy the entire thing, converting NSDictionary to NSMutableDictionary and NSArray to NSMutableArray as it goes.
Thanks again Tom, you saved me from much work!!!
In appdelegate i changed...
stdPickerArray= [[NSMutableArray alloc] initWithArray:[def objectForKey:#"gesamtStdArray"]];
to...
stdPickerArray= [[NSMutableArray alloc] initWithArray:[def objectForKey:#"gesamtStdArray"]];
stdPickerArray = CFBridgingRelease(CFPropertyListCreateDeepCopy(NULL, (CFPropertyListRef)stdPickerArray, kCFPropertyListMutableContainers));
awesome!!!
I need to obtain a list of the N most recently played songs from an iOS device, in order.
The only way I can imagine doing it, at the moment, is by getting all the songs through an MPMediaQuery and manually sort them by lastPlayedDate.
This is a potentially expensive operation and I was wondering if there was a better approach.
Edit: After some tests, this is a very expensive operation. On a test library of 2500 songs, it took around 20 seconds to:
Get all the songs.
Assign a date to all songs that had never played (January 1 1970).
Order them by date.
Fetch the first N entries.
Any suggestion of improvements would be appreciated.
Edit 2: Solved now, but just for the record here's what I was doing.
I was sorting using a block as described here: https://stackoverflow.com/a/805589/112702. Simply changing the sorting method to what's in Bryan's answer improved my speed by nearly 20 times on an iPod Touch 3.
One way is to take the array of MPMediaItems you get from the MPMediaQuery and sort it by MPMediaItemPropertyLastPlayedDate using an NSSortDescriptor:
NSTimeInterval start = [[NSDate date] timeIntervalSince1970];
MPMediaQuery *songsQuery = [MPMediaQuery songsQuery];
NSArray *songsArray = [songsQuery items];
NSSortDescriptor *sorter = [NSSortDescriptor sortDescriptorWithKey:MPMediaItemPropertyLastPlayedDate
ascending:NO];
NSArray *sortedSongsArray = [songsArray sortedArrayUsingDescriptors:#[sorter]];
NSTimeInterval finish = [[NSDate date] timeIntervalSince1970];
NSLog(#"Execution took %f seconds.", finish - start);
This sorts the new array by most recently played first. I tested this on a iPhone 4S using 2000 songs and it took .98 seconds.
I think MPMediaQuery is the only way to get recently played songs from an iOS device at this time.
You can use property MPMediaItemPropertyLastPlayedDatewhich will return you the most recent calendar date and time on which the user played the media item. Value is an NSDate object.
http://developer.apple.com/library/IOs/#documentation/MediaPlayer/Reference/MPMediaItem_ClassReference/Reference/Reference.html#//apple_ref/doc/constant_group/General_Media_Item_Property_Keys
Looking for experienced advise since I'm newbie to iOS.
I have CoreData entities for "income" and "expenses". I've a tableView to show expenses and another one to show the income. I would like to group the expenses or income by month and display the data (expenses or income) on the tableView, and change the month displayed using toolbar buttons. To show by month seems simple, using an attribute "month" on entities. My question is what's the cleanest way to implement the "monthly display" and adapt the tableView to respond the "next month" and "previous month" buttons?
For instance, I have the tableView showing the February Expenses. When I touch the next button I want to show a tableView with March Expenses.
What kind of approach do you suggest using "months"? This may be quite simple, but for someone who's starting can be quit tough if i take wrong directions. Thanks in advance
The approach I would use would be to have a typedef enum for the month (just for convenience)
typedef enum
{
January = 0,
February,
March,
April,
May,
June,
July,
August,
September,
October,
November,
December
} Month;
Then you would have each entity have an attribute on it which is a number that indicates the month that entity is associated with.
You can then get all the entities associated with a specified month like so:
NSFetchRequest *request= [[NSFetchRequest alloc] init];
[request setEntity:[NSEntityDescription entityForName:#"<<Your entity name here>>" inManagedObjectContext:managedObjectContext]];
NSPredicate *monthPredicate = [NSPredicate predicateWithFormat:#"month.integerValue == %d", (NSInteger)month];
[request setPredicate:monthPredicate];
that request should get you all the entities associated with the given month. If you want, you could also just save the month as a string. Hope this helps!