Change tableView when changing CoreData attribute criteria - uitableview

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!

Related

Core Data equivalent of SQL DATEPART

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.

HKStatisticsCollectionQuery pulling data for each week of the month

I'm trying to create a chart that displays a month's worth of data vs the past month's data broken up by week.
For example viewing the chart today would show July vs June, there would be 5 points on each graph with the following dates:
June: 1-8, 9-15, 16-22, 23-29, 30
July: 1-8, 9-15, 16-22, 23-29, 30-31
The predicate looks like
NSPredicate *predicate = [HKQuery predicateForSamplesWithStartDate:self.pastStartDate endDate:self.currentEndDate options:HKQueryOptionStrictStartDate];
where self.pastStartDate is the first of last month and self.currentEndDate is the last of the current month.
We then set up the query like so
HKStatisticsCollectionQuery *query = [[HKStatisticsCollectionQuery alloc] initWithQuantityType:quantityType quantitySamplePredicate:predicate options:HKStatisticsOptionDiscreteAverage anchorDate:self.pastStartDate intervalComponents:self.interval];
So for the month, self.interval.day = 7 is set so that we pull a week's worth of data at every interval. The problem is that this interval is not calendar aware, so it doesn't know that the final data point for the last month should only have 1 day, therefore we are getting some overlap.
We have also tried self.interval.weekOfMonth = 1 but again, the interval does not know which month it is in so this is not working either.
Is there a way to create the interval based on the calendar so that it doesn't include more than the end of the month in a given interval?

How do I create a hash of weekdays matched with dates of objects for a calendar?

I'm building a cashflow management tool as an exercise.
I've a table to display a budget for a week. It has weekdays as column headers, and Income/Expenditure Categories as headers for rows (the first ). Each row is then filled with cells from the Incomes/Expenditures for that category and for that week.
The problem is the Income/Expenditure Cells aren't mapped to their weekdays, so if a day doesn't have an income or expenditure value the layout of the row breaks - the cell values don't match the column headers for weekdays.
I have Income, Expenditure and Category models. Category's have many Expenditures and Incomes etc.
I think I need to create an #week_of_incomes_by_category object in my show controller method that takes a week of previous Incomes from Monday to Friday, and then puts the results into an array of ["Monday", "Tuesday", "Wednesday, "Thursday, "Friday"] - however I'm not sure how best to do this.
I can get a week of values using .where and .order, I don't know how to create that matched Income.date with the right day value. I'm also open to alternative solutions for my weekday calendar problem.
Thanks for the help.

Array of times for each day of the week in iOS

I have times that I would like to compare to the current time for each day of the week. From what I've come across, it sounds like the best thing to do would be to have an array for each day of the week with the given times I want. For example:
mondayTimes(2:00:00, 5:00:00, 9:00:00, 14:00:00)
tuesdayTimes(3:00:00, 6:00:00, 10:00:00, 15:00:00)
etc...
I want to find out the given day of the week using the current date, and then depending on what day it is, use the array of times for that given day. Then use the current time to find which time is next in the array.
Basically it is like an "alarm clock" that always has set times for every day of the week.
Do I use NSStrings to populate the dates in each array and convert them so I am able to compare them to the current time? What is the best route to go about this?
Thanks!
To get the current date, use NSDate currentDate = [NSDate date];
Then, to extract the weekday: initialize a NSCalendar of your choice, then call [calendar components:NSWeekdayCalendarUnit fromDate:currentDate];, where calendar is your calendar instance.
This produces a number between 1 and 7 (for the Gregorian calendar) where 1 is Sunday and 7 is Saturday. I would then advise you put all of your times in an array of arrays, with the 1 index containing an array of your sundayTimes, 2 containing an array of mondayTimes, ... 7 containing an array of your saturdayTimes.
Then using [allTimes objectAtIndex weekday] will return a NSArray of your times. All that's left is to compare the times, which I'm sure you can figure out.

Grouping table view sections by day and country

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.

Resources