iPhone calendar EKEvent - iphone

I am currently trying to create an iPhone calendar app. In order to make sure it syncs with the existing iPhone calendars, I am using the EKEvent toolkit.
However, the events I will be storing will have more properties than the ones EKEvent allows for- e.g., my events will not just have title, details and the few other categories that are allowed for; they will also have themes, priorities...
Thus, when I load the EKEventStore every time my calendar starts up, this information will not be contained in the EKEvents that are loaded.
How can I associate this information to the existing EKEvents so that whenever my calendar is loaded, these additional properties are also loaded?
I would use the eventIdentifier but the iPhone documentation says that "If the calendar of an event changes, its identifier most likely changes as well." If I am reading this correctly, this means that I cannot consistently use eventIdentifier to identify an event..

I would use the notes property and set a string which you can parse later.
Since EKCalendarItem is EKEvent's superclass some of the properties of EKEvent are inherited from EKCalendarItem. (documentation here)
However, you can still 'set' notes on an EKEvent, even though notes is not a property of EKEvent anymore. (go figure)
So, from your question, I might set an integer for each of your additional (custom) properties, like so..
In the view controller code that creates the event:
- set an integer for each of you custom options. (priority, theme, etc.)
int priority = 0;
EKEvent *newEvent = [EKEvent eventWithEventStore:yourEventStore];
[newEvent setCalendar:yourCalendar];
if (priority == 0) {
newEvent.notes = #"0"
}
newEvent.title = #"YourTitle";
newEvent.startDate = yourStartDate;
newEvent.endDate = yourEndDate;
[youreventStore saveEvent:newEvent span:EKSpanThisEvent commit:YES error:nil];
Then if you want to check/convert the custom property, just check the notes of the event whenever you fetch them either using characterAtIndex or make a subString from the notes and compare that to another string.

Related

Reminders.app two-way synchronisation logic

I want my app to synchronise with the Reminders.app. (my app utilizes Core Data)
I've already done a prototype and it works, but there are some bugs I can't think through.
Right now my sync algorithm looks like this:
1.Listen to EKEventStoreChangedNotification notification and when called sync
2.First I'm iterating over my local Calendars, and if they don't exist in Reminders.app I'm adding them.
This is a pretty bad design because if I delete a calendar in Reminders.app and if it's added to my app, it gets added again to Reminders.app. It's going to be much better to know if a calendar has been deleted.
3.Then I iterate over each Calendar from Reminders.app, and if this calendar doesn't exist in my local records I add them to my local.app. If it does exist, then I'm updating its title.
4.I'm fetching all the reminders from my EKEventStore
5.I'm iterating through all my local reminders, and if they don't exist in Reminders.app I'm adding them.
Again the problem with deleting - same story as calendar deleting.
6.I'm iterating through all Reminders.app reminders and if they don't exist in my app I'm adding them in my local.app. If they do exist I'm updating all values.
It's all fine, until I hit this problem with deleting. I see that EKObject has some methods that can help me, but I can't figure them out. How do I do this properly and is there something that I can do for optimisation?
Do you have a date changed-property or something similar? This would make things easier.
Ideally you save the information that a cal is deleted somewhere. Then - on sync - you can check if the calendar (you're about to add) was deleted recently or not. If thats the case you can delete the local calendar and delete the info for deletion too. So the list stays clean unless you delete an item and it is successfully synced to the counterpart.
Or if you want to do it by date, you can compare lastSyncedDate and calendarModifiedDate (just two examples). But this isn't such a nice solution. You have to consider timezones, time changes and user interaction with time eg. settings changed.
What about keeping track of what reminders you have scheduled in a .plist?
So if your algorithm is checking for what's missing against the .plist, and if it shows as having already been added in the .plist but isn't in the calendar, that means it was deleted.
 
  //Create dictionary that keeps track of your reminders
NSMutableDictionary *dictionary = [self remindersDictionary];
[dictionary setValue:local.fireDate forKey:title];
[dictionary setValue:#"TRUE" forKey:addedToReminders];
[plistDictionary setValue:dictionary forKey:#"reminders"];
[plistDictionary writeToFile:plistPath atomically:YES];
+(NSMutableDictionary *)remindersDictionary
{
NSString *searchPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)objectAtIndex:0];
plistPath = [searchPath stringByAppendingPathComponent:#"settings.plist"];
plistDictionary = [NSMutableDictionary dictionaryWithContentsOfFile:plistPath];
NSMutableDictionary *dictionary = [plistDictionary valueForKey:#"reminders"];
return dictionary;
}

Properly subclassing the EKEvent Class

I have been having a bit of trouble sublclassing the EKEvent Class. The scenario is this, I am pulling all my events from an external database using a webservice, so all the events come with an ID. I then want to put these events into the device calendar and retrieve them later. The problem is, when I retrieve the event I need it to have the same id as the event on the server so I can do a quick look up to get additional info on the event.
I am aware that the identifier property of EKEvent is read only, hence the reason I want to create a subclass of the class where I can add an additional property called something like myid and store the id of the event (the one from the server) with it in the eventstore. I have tried to create a subclass and everything seems to work fine and compiles, but on runtime I get an error when I try to set the extra eventid proporty that I add in the subclass, the error message is:
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[EKEvent setEventId:]: unrecognized selector sent to instance 0x83c0770'
This is some test code I use to create the event from my EKEvent subclass:
SectureEvent *myEvent = (SectureEvent*)[EKEvent eventWithEventStore:eventDB];
myEvent.title = self.evento;
myEvent.startDate = [[NSDate alloc] init];
myEvent.startDate = [NSDate date];
myEvent.endDate = [[NSDate alloc] init];
myEvent.endDate = [[NSDate alloc] init];
myEvent.allDay = YES;
myEvent.eventId = self.eventId;
The error occurs on the last line myEvent.eventId = self.eventId;
and app crashes. So my question essentailly if I can effectively subclass the EKEvent class and if so what am I doing wrong here?
Thanks in advance!
EKEvent is not meant to be subclassed. Event Kit objects are used to represent database records. Creating a subclass of EKEvent will not magically insert new fields in the Event Kit database, nor will casting a EKEvent to something else magically change that object' class.
The only way to store extra fields into the database is to have a direct access to that database, which Apple reserves for themselves.
As you cannot add new fields to the Event Kit database, you can either use the existing fields (for example, add the event ID in the notes of the event) or extend it with a second database managed by your application.
Just create a SQLite database (or a Property List file, or whatever format you want) that associate your event IDs to EKEvent identifiers.
Creating an EKEvent and casting it as a SectureEvent is not the same as creating a SectureEvent.
Try this:
SectureEvent *myEvent = [SectureEvent eventWithEventStore:eventDB];

Get identifier from recently added Event (EKEventStore saveEvent)

I am saving a custom Event with EKEventStore saveEvent to the calendar the return value is boolean so I can handle if the storage process fails. Is there any chance to easily find out the unique identifier for this event, so I can fetch it afterwards with eventWithIdentifier? I don't want to make a selection by date cause it could return more then one result. Is there any other posibility to fetch "my" event?
(NSString *) uniqueIdentifier = myEvent.eventIdentifier;
That's it.

USe NSCoding To Create User Editable Plist

I'm trying to create a user editable plist where the user can store a custom created workout routine including strings referenced from another data.plist in the app bundle.
I'm assuming NSCoding is the best way to go about this. So far I have the interface setup as a nav bar and table view.
I want it to be blank be default and the user has to press the "+" that is in the top right of the nav bar. Then he could enter a name for an entry in an array, for example chest day, or bicep day. And within that array, will be a dictionary or another array of strings of the particular exercises for that day, for example bench press, or bicep curl.
This plist needs to be editable so it will be going in the users document folder and not in the app bundle.
Example:
Top array consists of Chest Day, Back Day, Leg Day. Within Chest Day dictionary, include bench press, chest pull, pushup, etc.
Update:
Adding this method to search for routine file;
-(void)loadData
{
if(YES)
{
NSString* documentsPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
NSString* routineFile = [documentsPath stringByAppendingPathComponent:#"routine.plist"];
BOOL fileExists = [[NSFileManager defaultManager] fileExistsAtPath:routineFile];
}
else
{
//load file
}
}
NSCoding is the protocol used by NSArchiver, NSKeyedArchiver, etc., not for serializing an array into a property list.
Forget about the idea that the user is going to edit a property list. The user is going to edit data in your app -- the fact that it's stored as a property list is just an implementation detail. When your app starts, you read the data stored in the data file. The user edits it, looks at it, whatever. At some point later, perhaps after each edit, perhaps just before the app quits, you write the data back out to the file. Since it's a property list, don't worry about updating the file; you already have all the data, so write a whole new property list and then use that file to replace the old one.
Perhaps I'm wrong and you really do intend for the user to edit the property list by hand, with a text editor. This would be a mistake. It's great the property lists are human readable, but asking your users to edit your raw data files by hand is a strong indication that your app is broken. The whole purpose of your app is to keep track of this information for the user; if they wanted to use a text editor to manage it, they wouldn't need your app. So, with that said, I hope I'm not wrong. ;-)
I don't think I'd use NSCoding for this - if all you're working with is standard plist objects like NSArray, NSDictionary, and NSString, the top array's -writeToFile:atomically: method is an easy way to do the job.

ExecuteFetchRequest intermittently throwing exception with same parameters. "not key value coding-compliant for the key"

EDIT Thanks to Matt's post I now understand that I should not be trying to access 'started' as an array. However, if that is the case, I would like to know why this code appears to be working in other places. It still seems to me that this should be "one-or-the-other." It should work or it shouldn't.
ORIGINAL
I'm using the same fetch request in various parts of my code to find the most recent game:
Game *lastGame = [[[CoreDataAccess managedObjectContext] fetchObjectsForEntityName:#"Game" withPredicate:#"started == started.#max"] anyObject];
'Game' is an NSManagedObject and 'started' is a date attribute. 'started' is set exactly once per object in awakeFromInsert. It is never changed after that. Game is never directly instantiated, but it has three subclasses. I have tried making Game both abstract and concrete, but neither has any effect on this problem.
I'm using an NSManagedObjectContext category to perform the fetch, as shown on cocoa with love here http://cocoawithlove.com/2008/03/core-data-one-line-fetch.html.
The error I am getting is the following:
Serious application error. An exception was caught from the delegate of NSFetchedResultsController during a call to -controllerDidChangeContent:. [<__NSDate 0xebb1130> valueForUndefinedKey:]: this class is not key value coding-compliant for the key #max. with userInfo {
NSTargetObjectUserInfoKey = "2010-11-06 11:16:53 GMT";
NSUnknownUserInfoKey = "#max";
}
It looks to me like the predicate might be trying to apply #max to a single NSDate, instead of all 'started' attributes in all games. I'm not sure though. I'm not very good with predicates, and it took me a lot of trial and error to make this one. I don't understand how the exact same fetch can have errors in different places, though.
The fetch is not part of an NSFetchedResultsController, but I am using a fetchedResultsController in the class where I'm getting the error. For example:
- (void)configureCell:(UITableViewCell*)cell atIndexPath:(NSIndexPath*)indexPath{
Game *game = [self.frc objectAtIndexPath:indexPath];
Game *lastGame = [[[CoreDataAccess managedObjectContext] fetchObjectsForEntityName:#"Game" withPredicate:#"started == started.#max"] anyObject]; // Sometimes we get past this line, sometimes we don't...
NSDateFormatter *format = [[NSDateFormatter alloc] init];
[format setDateFormat:#"EEE, MMM d, yyyy h:mm a"];
if (game != lastGame)
cell.detailTextLabel.text = [format stringFromDate:game.started];
else
cell.detailTextLabel.text = #"In Progress";
[format release];
...
}
and also here:
- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath {
// Return NO if you do not want the specified item to be editable.
Game *lastGame = [[[CoreDataAccess managedObjectContext] fetchObjectsForEntityName:#"Game" withPredicate:#"started == started.#max"] anyObject];
if (lastGame == [frc objectAtIndexPath:indexPath])
return NO;
return YES;
}
This exact fetch is performed several times in several places, for example on startup, but it only crashes in one class. As I said it is intermittent, but it seems to be sometime after I create a new Game object. The game is created in one tab, and the code above is from a second tab which shows history.
I have seen a similar error here. In that case the problem was solved by restarting the computer, which finally allowed XCode to realize that the attribute had been deleted from the model. I have tried that, and I'm still experiencing the problem. I have also tried deleting and recreating the 'started' attribute in the model. I have also read the Core Data Troubleshooting Guide, but was unable to find any help there either.
Predicates are applied to one source object at a time. If your source object does not have an array property, you can't use an array operator.
In your case, the predicate says:
Look at a given "Game". If its own
"started" property is equal to its own
"started" property with the #max KVC
array operator applied, then this
predicate will be true.
This is not what you want. The only situation where you'd use a KVC array operator in a predicate is where a property on an object is an array. e.g.
Fetch every "Season" where the "games.#max.homeTeamScore > 50" (i.e. seasons where a home team scored more than 50 in a game). This would work because the "games" property on a "Season" would be an array, so games.homeTeamScore would also be an array.
However, the "started" property on a single Game is not an array. The array you want to operate on is actually the array of all games, which is not a property of a game.
The only twisted way you could access the array of all games would be to fetch the array of all games first, then apply the array operator outside the predicate and then on the inside of the predicate only apply the equality test.
i.e. fetch all games first, then refetch with:
fetchObjectsForEntityName:#"Game" withPredicate:#"started == %#", [allGames valueForKey:#"#max.started"]
But this is not the smart thing to do either.
Ultimately, the correct way to fetch the game with the latest starting date as you're trying to do, can't be done with the single line fetch method.
You'll need to create a variant of the method that allows you to setSortDescriptors: on the fetch request (to sort by "started", descending) and then setFetchLimit:1 on the fetch request to only get first result.