How to Retrieve the CKRecord from the CKSubscriptionID that came from fetchAllSubscriptionsWithCompletionHandler? - cloudkit

and thank you for your advice!
I am using fetchAllSubscriptionsWithCompletionHandler, and I do see the subscritionID from each CKSubscription after the push notification is sent. How do I retrieve the CKRecord from the subscriptionID?
I do not see the Remote Push Notification from a CKReference that was created. I can see the CKRecord and its related record via CloudKit DashBoard. I do receive a Push Notification from when its parent record is created, but not when the CKReference is created on the child record.
-(void)SubscribeForReference:(CKRecord *)record
{
NSUserDefaults *trackSubscription = [NSUserDefaults standardUserDefaults];
BOOL hasSubscribed = [[NSUserDefaults standardUserDefaults] objectForKey:#"alreadySubscribedForReference"] != nil;
//BOOL hasSubscribed = [trackSubscription objectForKey:#"alreadySubscribedForReference"];
if (hasSubscribed == false) {
//creates a subscription based on a CKReference between two ckrecords
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"sentences == %#", record.recordID];
// 1) subscribe to record creations
CKSubscription *subscriptionRelation =
[[CKSubscription alloc] initWithRecordType:#"RecordTypeName"
predicate:predicate
options:CKSubscriptionOptionsFiresOnRecordCreation | CKSubscriptionOptionsFiresOnRecordUpdate | CKSubscriptionOptionsFiresOnRecordDeletion | CKSubscriptionOptionsFiresOnRecordUpdate];
//http://stackoverflow.com/questions/27371588/cloudkit-notifications
CKNotificationInfo *notificationInfo = [[CKNotificationInfo alloc] init];
// I added this because of apple's documentation
notificationInfo.desiredKeys = #[#"word",#"identifier"];
notificationInfo.alertLocalizationArgs = #[#"word"];
notificationInfo.alertLocalizationKey = #"%1$#";
notificationInfo.shouldBadge = YES;
subscriptionRelation.notificationInfo = notificationInfo;
[self.privateDatabase saveSubscription:subscriptionRelation completionHandler:^(CKSubscription * _Nullable subscription, NSError * _Nullable error) {
if (error == nil) {
[trackSubscription setObject:subscription.subscriptionID forKey:#"alreadySubscribedForReference"];
[trackSubscription synchronize];
}else
NSLog(#"something went wrong with saving the CKSubcription with error...\n%#\n",[error localizedDescription]);
}];
}
else{
NSLog(#"\nSubscribeForReference: ALREADY has subscription: %# set for key 'alreadySubscribedForReference' \n\n ", [trackSubscription objectForKey:#"alreadySubscribedForReference"]);
}
}
The code below is ran when the app is launched, provided there is an Internet connection:
-(void)runWhenAppStarts
{
CKFetchSubscriptionsOperation *fetchSubscriptionsOperation = [CKFetchSubscriptionsOperation fetchAllSubscriptionsOperation];
fetchSubscriptionsOperation.fetchSubscriptionCompletionBlock = ^(NSDictionary *subscriptionsBySubscriptionID, NSError *operationError) {
if (operationError != nil)
{
// error in fetching our subscription
CloudKitErrorLog(__LINE__, NSStringFromSelector(_cmd), operationError);
if (operationError.code == CKErrorNotAuthenticated)
{
// try again after 3 seconds if we don't have a retry hint
//
NSNumber *retryAfter = operationError.userInfo[CKErrorRetryAfterKey] ? : #3;
NSLog(#"Error: %#. Recoverable, retry after %# seconds", [operationError description], retryAfter);
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(retryAfter.intValue * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[self subscribe];
});
}
}
else
{
if (self.subscribed == NO)
{
// our user defaults says we haven't subscribed yet
//
if (subscriptionsBySubscriptionID != nil && subscriptionsBySubscriptionID.count > 0)
{
// we already have our one CKSubscription registered with the server that we didn't know about
// (not kept track in our NSUserDefaults) from a past app install perhaps,
//
NSLog(#"\nsubscriptionsBySubscriptionID (dictionary) = %#\n",subscriptionsBySubscriptionID);
NSArray *allSubscriptionIDKeys = [subscriptionsBySubscriptionID allKeys];
NSLog(#"\nallSubscriptionIDKeys (array) = %#\n",allSubscriptionIDKeys);
if (allSubscriptionIDKeys != nil)
{
[self updateUserDefaults:allSubscriptionIDKeys[0]];
for (NSString *subscriptions in allSubscriptionIDKeys) {
NSLog(#"subscriptionID: %#\n",subscriptions);
}
}
}
else
{
// no subscriptions found on the server, so subscribe
NSLog(#"...starting subscriptions on server...\n");
[self startSubscriptions];
}
}
else
{
// our user defaults says we have already subscribed, so check if the subscription ID matches ours
//
NSLog(#"...our user defaults says we have already subscribed, with subscriptionsBySubscriptionID = %#\nso check if the subscription ID matches the one already stored in NSUserDefaults...\n",subscriptionsBySubscriptionID);
if (subscriptionsBySubscriptionID != nil && subscriptionsBySubscriptionID.count > 0)
{
// we already have our one CKSubscription registered with the server that
// we didn't know about (not kept track in our NSUserDefaults) from a past app install perhaps,
//
//NSDictionary *subscriptionsBySubscriptionID has a structure of #{key: value} == #{NSString: CKSubscription}
NSArray *allSubscriptionIDKeys = [subscriptionsBySubscriptionID allKeys];//contains the NSString representation of the subscriptionID.
NSArray *allSubscriptionIDVALUES = [subscriptionsBySubscriptionID allValues];// the values are the corresponding CKSubscription objects
for (CKSubscription *values in allSubscriptionIDVALUES) {
NSLog(#"\nCKSubscriptionValue = %#\n",values);
}
NSLog(#"\n...we already have our one CKSubscription registered with the server that..so lets look at allSubscriptionIDKeys =%#.\n\nvalues ...\nallSubscriptionIDVALUES = %#\n\n",allSubscriptionIDKeys,allSubscriptionIDVALUES);
if (allSubscriptionIDKeys != nil)
{
NSString *ourSubscriptionID = [[NSUserDefaults standardUserDefaults] objectForKey:kSubscriptionIDKey];
if (![allSubscriptionIDKeys[0] isEqualToString:ourSubscriptionID])
{
// our subscription ID doesn't match what is on the server, to update our to match
NSLog(#"...our subscription ID doesn't match what is on the server, going to update our NSUserDefaults...\n");
[self updateUserDefaults:allSubscriptionIDKeys[0]];
}
else
{
// they match, no more work here
NSLog(#"...iCloud server already has this subscriptionID, so do nothing.i.e. don't subscribe again..\n");
}
}
}
}
}
};
[self.privateDatabase addOperation:fetchSubscriptionsOperation];
}

When you fetch subscriptions, you don't get a specific record ID. Instead, the subscription's .recordType will tell you the type of record this subscription monitors. Typically, if you have 1,000 users, then you would have 1,000 instances of the record, and each user would create subs to monitor when their instance is modified.
When the subscription is triggered, you'll receive a notification. You can use CKFetchNotificationChangesOperation to retrieve the notifications. Each notif includes a .recordID value that tells you specifically which record changed and caused the subscription to fire.
You're currently querying the subs to make sure the user has properly subscribed. Next, you also need to query the notifications with CKFetchNotificationChangesOperation to see which records have been updated when the subscription(s) fired.

You may extract RecordID from the predicate of subscription.
For example - if subscription predicate was:
NSPredicate* predicate = [NSPredicate predicateWithFormat:#"(author == %#)", artistRecordID];
where artistRecordID is reference to another recordType - then you may extract RecordID in the following way:
[publicDatabase fetchAllSubscriptionsWithCompletionHandler:^(NSArray<CKSubscription *> *subscriptions, NSError *error){
if(error){
// handle error
}
else {
[subscriptions indexOfObjectPassingTest:^BOOL(CKSubscription * obj, NSUInteger idx, BOOL *stop){
if ([obj.predicate isKindOfClass:[NSComparisonPredicate class]]) {
NSComparisonPredicate *p2a = (NSComparisonPredicate *)obj.predicate;
NSExpression *e6 = p2a.rightExpression;
CKReference* ref=e6.constantValue;
// you may extract RecordID here from ref
// for example - to compare against another RecordID:
if([ref.recordID.recordName isEqualToString:artistRecordID.recordID.recordName]){
*stop=YES;
return YES;
}
}
return NO;
}];
}
}];

Related

CKError code: 12, No operations present in request

I keep seeing this error when performing a query in CloudKit. Here is the code that usually sets it off:
if (self.userID) {
CKReference *referenceToUser = [[CKReference alloc]initWithRecordID:self.userID action:CKReferenceActionDeleteSelf];
NSPredicate *userSearch = [NSPredicate predicateWithFormat:#"%K == %#", #"User", referenceToUser];
CKQuery *findHoneymoon = [[CKQuery alloc]initWithRecordType:#"Honeymoon" predicate:userSearch];
CKQueryOperation *findHMOp = [[CKQueryOperation alloc]initWithQuery:findHoneymoon];
findHMOp.resultsLimit = 1;
__block CKRecord *userHoneyMoon;
findHMOp.recordFetchedBlock = ^(CKRecord *record){
userHoneyMoon = record;
self.userHoneymoon.honeymoonID = record.recordID;
};
__block BOOL errorOccured = NO;
findHMOp.queryCompletionBlock = ^(CKQueryCursor *cursor, NSError *operationError){
if (operationError)
{
NSLog(#"Error searching for user honeymoon, description: %#, and code: %lu, and heck, heres the domain: %#", operationError.localizedDescription, operationError.code, operationError.domain);
[[NSNotificationCenter defaultCenter]postNotificationName:#"HoneymoonError" object:nil];
errorOccured = YES;
}
if (!userHoneyMoon && !errorOccured)
{
[self createBlankHoneymoon];
}
else
{
[self.userHoneymoon populateHoneymoonImages];
}
};
[[[CKContainer defaultContainer] publicCloudDatabase] addOperation:findHMOp];
}
What's really vexing is that this error only occurs about 1 in 5 times I try to run the program.
Here is what gets logged when the error occurs:
Error searching for user honeymoon, description: No operations present in request, and code: 12, and heck, heres the domain: CKErrorDomain
As you can see from the code there ARE operations in the request, does anyone know any other reason this error might occur and how to handle it?

Cloudkit: CKNotificationInfo badge value never decrease

I set subscription notifications for cloudkit. Here is my code:
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"TRUEPREDICATE"];
CKSubscription *subscription = [[CKSubscription alloc]
initWithRecordType:recordType
predicate:predicate
options:CKSubscriptionOptionsFiresOnRecordCreation];
CKNotificationInfo *notificationInfo = [CKNotificationInfo new];
notificationInfo.alertLocalizationKey =#"New record in cloudKit";
notificationInfo.shouldBadge = YES;
notificationInfo.soundName = UILocalNotificationDefaultSoundName;
notificationInfo.shouldSendContentAvailable = YES;
subscription.notificationInfo = notificationInfo;
CKContainer *container = [CKContainer defaultContainer];
CKDatabase *publicDatabase = [container publicCloudDatabase];
[publicDatabase saveSubscription:subscription
completionHandler:^(CKSubscription *subscription, NSError *error) {
if (!error)
{
NSLog(#"no error");
}
else
{
NSLog(#"error%#", error);
}
}];
and works just fine. The problem is the badges, they seem like cloudKit doesn't reset the badge number and the keep increasing even when I set the badge count to zero.
- (void)applicationDidBecomeActive:(UIApplication *)application
{
application.applicationIconBadgeNumber = 0;
}
When the app received a new notification goes from 0 to 5 (and every new notification increase by 1, the next time would be 6)
Any of you knows how keep track of the right count of badges from cloudkit (in Objective-C )
This is a duplicate of CloudKit won't reset my badge count to 0
The answer there was:
You need to do a CKModifyBadgeOperation after processing your notifications.
Here is my Swift function which I call after marking all the notifications as read. I add the operation to the defaultContainer instead of just starting it - I wonder does that make any difference.
func resetBadgeCounter() {
let badgeResetOperation = CKModifyBadgeOperation(badgeValue: 0)
badgeResetOperation.modifyBadgeCompletionBlock = { (error) -> Void in
if error != nil {
println("Error resetting badge: \(error)")
}
else {
UIApplication.sharedApplication().applicationIconBadgeNumber = 0
}
}
CKContainer.defaultContainer().addOperation(badgeResetOperation)
}
This will help.
CKModifyBadgeOperation *badgeResetOperation = [[CKModifyBadgeOperation alloc] initWithBadgeValue:0];
[badgeResetOperation setModifyBadgeCompletionBlock:^(NSError * operationError) {
if (!operationError) {
[UIApplication sharedApplication].applicationIconBadgeNumber = 0;
}
}];
[[CKContainer defaultContainer] addOperation:badgeResetOperation];

Loading value form Parse to UISwitch

In my app the user is able to add an value with an UISwitch. Once the user has selected the value the, value is uploaded to the current user in parse. When the user gets back to the view where the user selected the value using the UISwitch, i want the UISwitch to be loaded the way the current user selected it last time. But i just cant get it to work.
I use this query, to get the value from the currentUser
PFQuery *query= [PFUser query];
[query whereKey:#"username" equalTo:[[PFUser currentUser]username]];
PFObject *object = [query getFirstObject];
And this is where i want to put the value in.
(void)viewDidAppear:(BOOL)animated; {
[super viewDidAppear:animated];
PFQuery *query= [PFUser query];
[query whereKey:#"username" equalTo:[[PFUser currentUser]username]];
PFObject *object = [query getFirstObject];
NSNumber *logNSNumber = [object valueForKey: #"Active"];
bool switchValue = [logNSNumber boolValue];
BOOL switchValue;
if (switchValue)
{
switchLog.on = TRUE;
legoTextView.hidden = NO;
}
else
{
switchLog.on = FALSE;
lTextView.hidden = YES;
}
}
And this is how i send the boolean value up to parse.
-(IBAction)changeBoolean; {
Boolean switchVaule;
if (switchLog.on)
{
switchVaule = TRUE;
logTextView.hidden = NO;
PFUser *log = [PFUser currentUser];
log[#"Active"] = #YES;
[log saveInBackground];
}
else
{
switchVaule = FALSE;
logTextView.hidden = YES;
PFUser *log = [PFUser currentUser];
log[#"Active"] = #NO;
[log saveInBackground];
}
}
Does any one know how to solve this problem, is this the right way to do this, or am i trying to do this the wrong way? Thx!
There are lots of things that are wrong in your code. For one; you are getting the PFUser object for the current user by querying for it using data from the user object you're already having ([PFUser currentUser]). When you log in, Parse gives you this object automatically, and you don't need to query for it. To store a value in it:
[[PFUser currentUser] setObject:[NSNumber numberWithBool:YES] forKey:#"Active"];
[[PFUser currentUser] saveInBackground];
to get value from it:
[[PFUser curentUser] objectForKey:#"Active"];
In your code, you're not getting the stored value anywhere to use in your switch. One solution:
switchLog.on = [[PFUser curentUser] objectForKey:#"Active"];
You have also, as jrturton points out, lots of typos in your code, and less-than-ideal condition handling. Clean that up and try using the code I provided.

iOS Gamecenter Programmatic Matchmaking

I'm trying to implement a real-time multiplayer game with a custom UI (no GKMatchMakerViewController). I'm using startBrowsingForNearbyPlayersWithReachableHandler:
^(NSString *playerID, BOOL reachable) to find a local player, and then initiating a match request with the GKMatchmaker singleton (which I have already initiated).
Here's where I'm having trouble. When I send a request, the completion handler fires almost immediately, without an error, and the match it returns has an expected player count of zero. Meanwhile, the other player definitely has not responded to the request.
Relevant code:
- (void) findMatch
{
GKMatchRequest *request = [[GKMatchRequest alloc] init];
request.minPlayers = NUM_PLAYERS_PER_MATCH; //2
request.maxPlayers = NUM_PLAYERS_PER_MATCH; //2
if (nil != self.playersToInvite)
{
// we always successfully get in this if-statement
request.playersToInvite = self.playersToInvite;
request.inviteeResponseHandler = ^(NSString *playerID, GKInviteeResponse
response)
{
[self.delegate updateUIForPlayer: playerID accepted: (response ==
GKInviteeResponseAccepted)];
};
}
request.inviteMessage = #"Let's Play!";
[self.matchmaker findMatchForRequest:request
withCompletionHandler:^(GKMatch *match, NSError *error) {
if (error) {
// Print the error
NSLog(#"%#", error.localizedDescription);
}
else if (match != nil)
{
self.currentMatch = match;
self.currentMatch.delegate = self;
// All players are connected
if (match.expectedPlayerCount == 0)
{
// start match
[self startMatch];
}
[self stopLookingForPlayers];
}
}];
}
Figured it out! I needed to call - (void)matchForInvite:(GKInvite *)invite completionHandler:(void (^)(GKMatch *match, NSError *error))completionHandler in my invitation handler so that both players have the same match data.

I need help restructuring my method with better logic

Update:
I' having this problem with this method:
By default, the view sets the day to today and creates a session what today's date as its timeStamp unless the user changes the date manually.
If I add exercise to today's date, everything is fine.
If I choose a previous date and add an exercise to it, it seems fine, but it seems that exercise is added not only to this previous date, but also to today.
So no matter what previous date I choose, the exericse entity is added to that session and to today's session.
Therefore when I view today's session in tableview, it has all exercises every done because everything is added to today along with its manually chosen date.
How can I fix this?
Original:
I have a detailViewController that is responsible for dealing with Core Data objects Session, Exercise, & Set.
The view's purpose is for the user to enter detail about their fitness workout. By default, the Date is set to today but the user can change it to a previous date. In createSession, a new Session object with timestamp of that date is created when the user presses the done button for that date and if the date doesn't already exist. If it already exists, it fetches that object with that timeStamp date.
The problem is, this creates a new object of Session even if the user does not enter the necessary data later. After the Session is created, the user is prompted to enter weight and reps used for the exercise. If the user doesn't enter this information (object Set), there is a Session object that has no purpose because their are no exercises and set objects related to it.
But the reason I create the Session object before checking for weight/rep is because if a Session is fetched, I want to display its property data in the view, thus I need to work with it before adding sets/reps.
My current code is:
-(void)createSession
{
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
[fetchRequest setPredicate:[NSPredicate predicateWithFormat: #"(timeStamp >= %# && timeStamp <= %#)", targetDateBegins, targetDateEnds]];
[fetchRequest setEntity:[NSEntityDescription entityForName:#"Session" inManagedObjectContext:managedObjectContext]];
NSError *error = nil;
NSArray *results = [managedObjectContext executeFetchRequest:fetchRequest error:&error];
NSLog(#"Fetch error: %#", error);
if ([results count])
{
session = (Session *)[results objectAtIndex:0];
}
else
{
session = (Session *)[NSEntityDescription insertNewObjectForEntityForName:#"Session" inManagedObjectContext:managedObjectContext];
session.timeStamp = self.picker.date;
}
NSSet *filteredExercisesFromSession=[session.exercises filteredSetUsingPredicate:[NSPredicate predicateWithFormat: #"name == %#",selectedExerciseName]];
if ([filteredExercisesFromSession count] > 0)
{
self.exercise=[filteredExercisesFromSession anyObject];
}
else
{
self.exercise = (Exercise *)[NSEntityDescription insertNewObjectForEntityForName:#"Exercise" inManagedObjectContext:managedObjectContext];
self.exercise.name = selectedExerciseName;
[session addExercisesObject:exercise];
}
if (![managedObjectContext save:&error])
{
// Handle the error.
}
NSLog(#"Save error: %#", error);
[fetchRequest release];
self.fetchedResultsController = nil;
[setsTableView reloadData];
}
-(IBAction)createSet
{
Set *set = (Set *)[NSEntityDescription insertNewObjectForEntityForName:#"Set" inManagedObjectContext:managedObjectContext];
set.weight = [NSNumber numberWithFloat:weightSelected2];
set.reps = [NSNumber numberWithInt:repSelected];
set.timeStamp = self.picker.date;
[self.exercise addSetsObject:set];
NSError *error = nil;
if (![managedObjectContext save:&error])
{
// Handle the error.
}
NSLog(#"error: %#", error);
self.fetchedResultsController = nil;
[setsTableView reloadData];
}
I'm trying to keep the new code not too different from this, maybe if I could just adjust a few things instead of creating something entirely new.
Edit:
Here is kinda what I have so far, a little stuck but still working on it. Thanks
//Session.h
#interface Session : NSManagedObject {
NSString * const kSessionLeavingActiveSession = #"kSessionLeavingActiveSession";
NSString * const kSessionChangingActiveSession = #"kSessionChangingActiveSession";
static Session *activeSession = nil;
//Session.h
#implementation Session
// a static method to retrieve & create if nil the active session
+ (Session *)activeSession {
if (activeSession = nil)
{
session = (Session *)[NSEntityDescription insertNewObjectForEntityForName:#"Session" inManagedObjectContext:managedObjectContext];
}
}
+ (Session *)sessionForDate:(NSDate *)date andSetActive:(BOOL)isActive {
NSCalendar *calendar = [NSCalendar currentCalendar];
unsigned unitFlags = NSYearCalendarUnit | NSMonthCalendarUnit | NSDayCalendarUnit;
NSDateComponents *dateComponentsForToday = [calendar components:unitFlags fromDate:self.picker.date];
[dateComponentsForToday setHour:0];
[dateComponentsForToday setMinute:0];
[dateComponentsForToday setSecond:0];
NSDate *targetDateBegins = [calendar dateFromComponents:dateComponentsForToday];
NSDate *targetDateEnds = [targetDateBegins dateByAddingTimeInterval:(60 * 60 * 24 - 1)];
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
[fetchRequest setPredicate:[NSPredicate predicateWithFormat: #"(timeStamp >= %# && timeStamp <= %#)", targetDateBegins, targetDateEnds]];
[fetchRequest setEntity:[NSEntityDescription entityForName:#"Session" inManagedObjectContext:managedObjectContext]];
NSError *error = nil;
NSArray *results = [managedObjectContext executeFetchRequest:fetchRequest error:&error];
NSLog(#"Fetch error: %#", error);
if ([results count])
{
session = (Session *)[results objectAtIndex:0];
NSLog(#"Already have session for date: %#", session.timeStamp);
}
else
{
session = (Session *)[NSEntityDescription insertNewObjectForEntityForName:#"Session" inManagedObjectContext:managedObjectContext];
session.timeStamp = self.picker.date;
NSLog(#"New session for date: %#", session.timeStamp);
}
    //send Notification kSessionChangingActiveSession if isActive == YES
    //if isActive is true, set it to the active session.
}
+ (void)saveOrDestroyActiveSession {
    //register this function as an observer on kSessionChangingActiveSession
    //before the active session is changed, you will be able to determine
    //if the session has anything associated with it, and persist it, or
    //destroy it.
    //determine if it has exercises assigned to it.
    //if it does, save the context.
    //if it doesn't destroy the active session and set activeSession to nil
}
+ (void)saveActiveSession {
    //register this function as an observer on kSessionLeavingActiveSession.
    //If you are leaving an area of the application where you are working on
    //the session, you want to send this notification so that you can save
    //the session if you have added exercises.
    //you don't want to delete it though, if you can still go back to the
    //active session, for example, if it's the active session view controller
    //on a tab controller.
}
// the rest of your session methods
#end
//your app delegate
- (void)applicationWillResignActive:(UIApplication *)application {
    //send a notification that you are leaving the active session
}
//viewController.m
//modify your fetched results controller to pull it's list of exercises from the
//active session, in case it's not saved you can still retrieve the exercises associated
//with it.
- (void)viewDidUnload {
    //send a notification that you are leaving the active session
}
- (void)createSession {
    Session *activeSession = [Session sessionForDate:self.picker.date andSetActive:YES];
NSSet *filteredExercisesFromSession=[session.exercises filteredSetUsingPredicate:[NSPredicate predicateWithFormat: #"name == %#",selectedExerciseName]];
if ([filteredExercisesFromSession count] > 0)
{
self.exercise=[filteredExercisesFromSession anyObject];
}
else
{
self.exercise = (Exercise *)[NSEntityDescription insertNewObjectForEntityForName:#"Exercise" inManagedObjectContext:managedObjectContext];
self.exercise.name = selectedExerciseName;
[session addExercisesObject:exercise];
}
    //if you've added exercises go ahead and save.
    //if you haven't let the observers take care of persisting or destroying it.
}
This is probably a little dirtier than I would have liked to write it, but it should give you a good idea of my view on how to solve your problem.
//Session.m
//Check out the link to PSFoundation and the ActiveRecord Core Data methods
//https://github.com/steipete/PSFoundation/tree/master/activerecord-coredata
NSString * const kSessionLeavingActiveSession = #"kSessionLeavingActiveSession";
NSString * const kSessionChangingActiveSession = #"kSessionChangingActiveSession";
static Session *activeSession = nil;
#implementation Session
// ....
// a static method to retrieve & create if nil the active session
+ (Session *)activeSession {
//if activeSession = nil create a new instance
//reading the code it looks like you only create a new session if there
//isn't a session that already exists for the day chosen in the date picker?
//If that's the case, then it is only possible for there ever to be
//a single instance of an active session, one which is currently in view.
}
+ (Session *)sessionForDate:(NSDate *)date andSetActive:(BOOL)isActive {
//wrap all of your create session logic in here.
//search for a session that exists for this date, and return it if
//it exists, or create a new session if it doesn't.
//send Notification kSessionChangingActiveSession if isActive == YES
//if isActive is true, set it to the active session.
}
+ (void)saveOrDestroyActiveSession {
//register this function as an observer on kSessionChangingActiveSession
//before the active session is changed, you will be able to determine
//if the session has anything associated with it, and persist it, or
//destroy it.
//determine if it has exercises assigned to it.
//if it does, save the context.
//if it doesn't destroy the active session and set activeSession to nil
}
+ (void)saveActiveSession {
//register this function as an observer on kSessionLeavingActiveSession.
//If you are leaving an area of the application where you are working on
//the session, you want to send this notification so that you can save
//the session if you have added exercises.
//you don't want to delete it though, if you can still go back to the
//active session, for example, if it's the active session view controller
//on a tab controller.
}
// the rest of your session methods
#end
//your app delegate
- (void)applicationWillResignActive:(UIApplication *)application {
//send a notification that you are leaving the active session
}
//viewController.m
//modify your fetched results controller to pull it's list of exercises from the
//active session, in case it's not saved you can still retrieve the exercises associated
//with it.
- (void)viewDidUnload {
//send a notification that you are leaving the active session
}
- (void)createSession {
Session *activeSession = [Session sessionForDate:self.picker.date andSetActive:YES];
//code to add exercises to session
//if you've added exercises go ahead and save.
//if you haven't let the observers take care of persisting or destroying it.
}