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;
}
Related
I changed my app from a single context with sqlite store managed in the app delegate to UIManagedDocument (Like default Xcode template with Core Data activated).
Now I have some strange behavior when trying to fetch objects that has been created since the current app startup, after I closed(backgrounded) and reopened the app.
There are 2 Entities: Folder and Item.
Folder to Item is a to-many-relationship.
The predicate of my fetchedResultsController to display the items in the current folder looks like that:
[fetchRequest setEntity:[NSEntityDescription entityForName:#"Item" inManagedObjectContext:self.context]];
[fetchRequest setPredicate:[NSPredicate predicateWithFormat:#"(folder == %#)", self.currentFolder]];
This still works fine beside one situation:
- I create a new folder, add some items to it
- Then close (background) and reopen the app
- Select this folder to be displayed in the TableView
Then the fetch returns 0 results.
As soon as I kill the app and reopen it, the items of the folder are displayed again (and the fetchRequests returns the correct number of items).
I did some further testing inside the viewDidAppear with some manual fetchRequests without a fetchedResultsController.
And I really don't understand how this can happen.
Both fetchRequests are basically the same (and also identical to the fetchedResultController's one),
but the first one doesn't have the predicate and then checks in a for loop whether the item's folder ist the viewController's currentFolder.
From my understanding, this should result in the exact same behavior, but while the predicate fails after backgrounding,
the fetching of all items + testing in the for loop works fine all time.
// Returns fetchResult.count = 0 for new created folders after backgrounding the app
[fetchRequest1 setEntity:[NSEntityDescription entityForName:#"Item" inManagedObjectContext:self.context]];
[fetchRequest1 setPredicate:[NSPredicate predicateWithFormat:#"(folder == %#)", self.currentFolder]];
NSArray *fetchResult1 = [self.context executeFetchRequest:fetchRequest1 error:nil];
// This time get all items in the context withour predicate
[fetchRequest2 setEntity:[NSEntityDescription entityForName:#"Item" inManagedObjectContext:self.context]];
[fetchRequest2 setPredicate:nil];
NSArray *fetchResult2 = [self.context executeFetchRequest:fetchRequest1 error:nil];
// Filter to get only the items in the current folder
NSMutableArray *filteredFetchResults2 = [NSMutableArray mutableArrayWithCapacity:0];
for (Item *item in fetchResult2) {
if (item.folder == self.currentFolder) [filteredFetchResults2 addObject:item];
}
// filteredFetchResults2 holds the correct items even after backgrounding. Why?!?
How could that be?
I guess the answer is this somehow, as backgrounding will simply save the file:
Core Data managed object does not see related objects until restart Simulator
Lesson learned: better avoid UIManagedDocument for the moment. Even if you wanted to use it locally without iCloud.
I'm currently using a singleton as a data store for my app. I essentially store a number of events that are pulled and parsed from a web service and then added as needed. Each time I make a request from the web service, I parse the results and see if the items already exist. If they do, I delete them and add the updated version provided by the web service.
Everything appeared to be working properly until I fired up the Instruments panel to find out that my system is leaking the objects every time it loads them from the web service (from the second time on). The core method where things appear to be messing up is this one, which is located in my HollerStore singleton class:
- (void)addHoller: (Holler *)h
{
//Take a holler, check to see if one like it already exists
int i = 0;
NSArray *theHollers = [NSArray arrayWithArray:allHollers];
for( Holler *th in theHollers )
{
if( [[th hollerId]isEqualToString:[h hollerId]] )
{
NSLog(#"Removing holler at index %i", i);
[allHollers removeObjectAtIndex:i];
}
i++;
}
[allHollers addObject:h];
}
Quick explanation: I decided to copy the allHollers NSMutableArray into theHollers because it's being updated asynchronously by NSURLConnection. If I update it directly, it results in a crash. As such, I switched to this model hoping to solve the problem, however the Instruments panel is telling me that my objects are leaking. All the counts are exactly the # of items I have in my data set.
From what I can tell removeObjectAtIndex isn't effectively removing the items. Would love to get the thoughts of anybody else out there on three things:
Is my analysis correct that something else must be retaining the individual hollers being added?
Should I be using CoreData or SQLite for storing information pulled from the web service?
Do you know how long data stored in a Singleton should be available for? Until the app is killed?
Update
I think I've found the source, however perhaps someone can provide some clarity on the proper way to do this. I've created a method called parseHoller which takes a dictionary object created through SBJSON and returns my own model (Holler). Here are the last couple lines:
Holler *h = [[[Holler alloc] initFromApiResponse:hollerId
creatorId:creatorId
creatorName:creatorName
creatorImageUrl:creatorImage
comments:comments
attendees:attendees
wishes:wishes
invitees:invites
createdAt:createdAt
text:text
title:title
when:when]autorelease];
//(some other autorelease stuff is here to clean up the internal method)
return h;
I figured that since I'm returning an autoreleased object, this should be fine. Do you see anything wrong with this?
Have you tried to do a retain count on the objects that is leaking? Maybe that could clear up when or where it is being retained.
The code should be
[putObjectHere retainCount];
and then write to an NSLog
Hope it gives you something
Peter
I'm aware of NSUserDefaults for saving/restoring user preferences. What is the equivalent class for an application? For example, the application may have a "last run" field; or it may have a field for a unique identification of the device for use at the application level.
My intention is to keep the application's settings (not user's settings) out of the Settings Application, and not backup those settings in iTunes, Time Machine, {whatever}.
I'm getting a lot of noise for Java and C#, but not much for iOS/iPhone/iPad.
NSUserDefaults can be used for what you're asking.
if (![[NSUserDefaults standardUserDefaults] boolForKey:#"shownPrompt"]) {
[[NSUserDefaults standardUserDefaults] setBool:YES forKey:#"shownPrompt"];
// Show your prompt or whatever
}
That's a working code snippet. If the key is false, it sets it to true and shows the prompt. The next time this code runs, the key will already by true, so the prompt won't be shown.
NSUserDefaults is specific to the current app on the current device, and is similar to an NSMutableDictionary in that it's a key-value system, with the difference that instead of instantiating your own, there's a universal shared instance for your whole app, that doesn't get erased when the app exits.
NSUserDefaults is perfect for saving things like whether something has been shown, the date of last run, etc. Read the docs here: https://developer.apple.com/documentation/foundation/userdefaults
Don't be put off by the 'user preferences' part. You can use it to save anything you want (as long as it is or can be converted to an NSObject which implements <NSCoding>, which basically means NSString, NSDictionary, NSArray, NSNumber, UITextField, int, float, bool, etc.).
Just to clarify, stuff you put in NSUserDefaults will not, under any circumstances, automagically turn up in the Settings app. It will be kept completely private and hidden. For something to appear in Settings, you need to add a Settings bundle to your app, and manually add keys to it for each and every value that you want to be visible in the Settings app.
if you can store value by NSUserDefaults, then it is good to store application preferences too.
or add settings.plist on your project and read that (what you are not changing later)
and you can use like.,
+ (NSDictionary*)getBundlePlist:(NSString *)plistName
{
NSString *errorDesc = nil;
NSPropertyListFormat format;
NSString *plistPath = [[NSBundle mainBundle] pathForResource:plistName ofType:#"plist"];
NSData *plistXML = [[NSFileManager defaultManager] contentsAtPath:plistPath];
NSDictionary *temp = (NSDictionary *)[NSPropertyListSerialization
propertyListFromData:plistXML
mutabilityOption:NSPropertyListMutableContainersAndLeaves
format:&format errorDescription:&errorDesc];
return temp;
}
+ (id) getPropValue:(NSString *)PropertyName
{ // I am supposing you had add your app preferences on settings.plist.
return [[Property getBundlePlist:#"settings"] objectForKey:PropertyName];
//here Property is my class name, then you can use value by
//NSString *value = [Property getPropValue:#"setting1"];
}
It's hard to tell what you're asking. It sounds like NSUserDefaults is what your looking for, but you claim that you're already aware of it. So what's your problem?
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.
I have an NSMutableArray with 24 strings.
I need to save this data if a user gets a call or quits the application.
I have been looking into many examples but for some reason can’t seem to determine the best way to save the data.
The 24 strings correspond to 24 buttons and their state. When a button is clicked, it displays the corresponding Array info for that buttons tag (0 – 23). What I would need to retain is if 10 buttons where clicked and their data shown, how/what would be the best way of retaining this data so it can be reloaded when the app starts?
I am thinking I would need to store:
Button Tag,
Buttons corresponding Array value,
Button state (whether it has clicked and value is show or not)
I would store this data on exit of the application and then when app is started again, I would determine if this stored data exists, if so populate the array and examine the button states to determine if it had already been shown and if so, set it accordingly. Then when this file was loaded, I would delete the stored data (.DAT file if stored this way). This way if a user quits gracefully, on next start up, it would start a new game.
I have looked at several examples where they store data into a .DAT file but am having problem implementing this….and wondering if this is even the best way.
Any help or thoughts on this is greatly appreciated.
Geo…
you should be able to store it in NSUserDefaults
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setObject:buttonState forKey:#"buttonState"];
[defaults synchronize];
// Then to get the state back
NSMutableArray* buttonState = [[[NSUserDefaults standardUserDefaults] arrayForKey:#"buttonState"] mutableCopy];
You could save the data to a plist in the Documents directory. If the data is there, load it up, and if not, it would suggest a clean run.
To load the data:
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documents = [paths objectAtIndex:0];
NSString *filePath = [documents stringByAppendingPathComponent:#"buttonState.plist"];
NSMutableDictionary *buttonState = [[NSMutableDictionary alloc] initWithContentsOfFile:filePath];
To save the data:
[buttonState writeToFile:filePath atomically: YES];
I save my data by using NSKeyedArchiver/NSKeyedUnarchiver, since your NSMutableArray already supports the NSCoding protocol, you can just use
yourArray = [NSKeyedUnarchiver unarchiveObjectWithFile:file];
[NSKeyedArchiver archiveRootObject:yourArray toFile:file];
Edit:
I suppose NSArray's own methods work also
[NSArray writeToFile:]
[NSArray initWithContentsOfFile:]