Using most recent saved data with Core Data - ios

I am completing some items on a check list. I am counting the completed items using the following method:
- (NSUInteger)completedCount {
return [DIDTask MR_countOfEntitiesWithPredicate:[NSPredicate predicateWithFormat:#"completed == YES && list == %#", self]];
}
The problem I have is when I'm calling the method on a list - list.completedCount - immediately after the data is saved it does not give me the correct count but rather the value - 1. Only after when the app change screen for instance or display a pop-up (as per below), then list.completedCount gives me the correct value. But this is too late for me.
[UIAlertView showAlertViewWithTitle:#"Are you OK?" message:task.name cancelButtonTitle:#"Stop" otherButtonTitles:#[#"Yes", #"No"] handler:^(UIAlertView *alertView, NSInteger buttonIndex) {
if (buttonIndex > 0) {
[MagicalRecord saveWithBlock:^(NSManagedObjectContext *localContext) {
[[task MR_inContext:localContext] setCompletedValue:buttonIndex == 1];
} completion:^(BOOL success, NSError *error) {
}];
[self continueAutomaticModeWithList:list taskIndex:index + 1];
}
}];
My question is how can I update or refresh the app immediately when the data is saved so that list.completedCount gives me the correct count right away?

It doesn't work, because [self continueAutomaticModeWithList:list taskIndex:index + 1]; is performed before the save completes. You have to move it to the completion block:
[UIAlertView showAlertViewWithTitle:#"Are you OK?" message:task.name cancelButtonTitle:#"Stop" otherButtonTitles:#[#"Yes", #"No"] handler:^(UIAlertView *alertView, NSInteger buttonIndex) {
if (buttonIndex > 0) {
[MagicalRecord saveWithBlock:^(NSManagedObjectContext *localContext) {
[[task MR_inContext:localContext] setCompletedValue:buttonIndex == 1];
} completion:^(BOOL success, NSError *error) {
[self continueAutomaticModeWithList:list taskIndex:index + 1];
}];
}
}];

Related

Set text in Active Conversation in iMessage

I wrote some code to add text to the Messages.app input field in my iMessage extension.
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
NSLog(#"didSelect called");
NSLog(#"%d", 1);
[[self activeConversation] insertText:#"https://google.com" completionHandler:^(NSError * error) {
NSLog(#"Error happened");
NSLog(#"Error: %#", error);
}];
NSLog(#"%d", 2);
}
The strange part is that all of the normal logs are happening. The app will log "didSelect called", "1" and "2". However, the message - the Google url - isn't being inserted, and the error logs aren't being shown. So I don't really have a clue as to what's going wrong. Any idea's what I'm doing wrong?
Solution #1
Send correct reference from MessagesViewController to your view controller.
Check activeConversation value for nil:
if ([self activeConversation] != nil) {
[[self activeConversation] insertText:#"Some text" completionHandler:^(NSError * _Nullable error) {
NSLog(#"error: %#", error.localizedDescription);
}];
} else {
NSLog(#"Conversation is nil");
}
Solution #2
Create Singleton in iMessage extension name space.
In MessagesViewController in - (void)viewDidLoad setup reference to
your MSConversation: [[Conversation shared] activeConversation] = [self activeConversation];
Use [[Conversation shared] activeConversation] insertText: .... ];
for sending messages from any controllers.

How to update progress with CKModifyRecordsOperation.perRecordProgressBlock

This is related to a recent thread Update progress with MRProgress. I converted my cloudkit queries from the convenience API to CKOperations as a result of previous thread (Thanks Edwin!). So while using CKModifyRecordsOperation to save a record, I can see the record's progress via logging in the perRecordProgressBlock, which is great. However, I'm trying to send this progress back to the viewcontroller and I cannot figure out how to do that. I have created a class for all of my CloudKit methods - CKManager. The other problem I'm having is that I'm unsure when to update the progress indicator (using MRProgress framework) in the VC. Do I call it before, during or after the save operations call in the CKManager? Should it be called recursively until the progress == 1.0? Here is the code I have so far...every works fine except for updating/animating the progress indicator (it appears and shows 0% and then disappears when the save operation is completed). Also, I'm using a property (double progress) in my CKManager class and I know that is incorrect, but I wasn't sure how else to do it. And I do not feel that the callback method I've declared/defined in my CKManager class below for this is correct either. Any guidance is appreciated!
CKManager.h
#property (nonatomic, readonly) double progress;
- (void)recordProgressWithCompletionHandler:(void (^)(double progress))completionHandler;
CKManager.m
#property (nonatomic, readwrite) double progress;
- (void)recordProgressWithCompletionHandler:(void (^)(double))completionHandler {
completionHandler(self.progress);
}
- (void)saveRecord:(NSArray *)records withCompletionHandler:(void (^)(NSArray *, NSError *))completionHandler {
NSLog(#"INFO: Entered saveRecord...");
CKModifyRecordsOperation *saveOperation = [[CKModifyRecordsOperation alloc] initWithRecordsToSave:records recordIDsToDelete:nil];
saveOperation.perRecordProgressBlock = ^(CKRecord *record, double progress) {
if (progress <= 1) {
NSLog(#"Save progress is: %f", progress);
self.progress = progress;
}
};
saveOperation.perRecordCompletionBlock = ^(CKRecord *record, NSError *error) {
NSLog(#"Save operation completed!");
completionHandler(#[record], error);
};
[self.publicDatabase addOperation:saveOperation];
}
Viewcontroller.m - this is from the method that takes the photo from the camera and calls the CKManager class to prepare the record and save it to CK as well as display the MRProgress indicator...
if (self.imageDataAddedFromCamera) {
self.hud = [MRProgressOverlayView showOverlayAddedTo:self.myCollectionView animated:YES];
self.hud.mode = MRProgressOverlayViewModeDeterminateCircular;
self.hud.titleLabelText = UPLOADING_MSG;
// prepare the CKRecord and save it
[self.ckManager saveRecord:#[[self.ckManager createCKRecordForImage:self.imageDataAddedFromCamera]] withCompletionHandler:^(NSArray *records, NSError *error) {
if (!error && records) {
NSLog(#"INFO: Size of records array returned: %lu", (unsigned long)[records count]);
CKRecord *record = [records lastObject];
self.imageDataAddedFromCamera.recordID = record.recordID.recordName;
NSLog(#"INFO: Record saved successfully for recordID: %#", self.imageDataAddedFromCamera.recordID);
[self.hud dismiss:YES];
[self.hud removeFromSuperview];
[self.imageLoadManager addCIDForNewUserImage:self.imageDataAddedFromCamera]; // update the model with the new image
// update number of items since array set has increased from new photo taken
self.numberOfItemsInSection = [self.imageLoadManager.imageDataArray count];
[self updateUI];
} else {
NSLog(#"Error trying to save the record!");
NSLog(#"ERROR: Error saving record to cloud...%#", error.localizedDescription);
[self.hud dismiss:YES];
[self.hud removeFromSuperview];
[self alertWithTitle:YIKES_TITLE andMessage:ERROR_SAVING_PHOTO_MSG];
}
}];
// where does this call belong?
[self.ckManager recordProgressWithCompletionHandler:^(double progress) {
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(#"Updating hud display...");
[self.hud setProgress:progress animated:YES];
});
}];
You should include the progress handler in your saveRecord call like this:
- (void)saveRecord:(NSArray *)records withCompletionHandler:(void (^)(NSArray *, NSError *))completionHandler recordProgressHandler:(void (^)(double))progressHandler {
NSLog(#"INFO: Entered saveRecord...");
CKModifyRecordsOperation *saveOperation = [[CKModifyRecordsOperation alloc] initWithRecordsToSave:records recordIDsToDelete:nil];
saveOperation.perRecordProgressBlock = ^(CKRecord *record, double progress) {
if (progress <= 1) {
NSLog(#"Save progress is: %f", progress);
progressHandler(progress)
}
};
saveOperation.perRecordCompletionBlock = ^(CKRecord *record, NSError *error) {
NSLog(#"Save operation completed!");
completionHandler(#[record], error);
};
[self.publicDatabase addOperation:saveOperation];
}
Then you can call that save record like this:
[self.ckManager saveRecord:#[[self.ckManager createCKRecordForImage:self.imageDataAddedFromCamera]] withCompletionHandler:^(NSArray *records, NSError *error) {
if (!error && records) {
NSLog(#"INFO: Size of records array returned: %lu", (unsigned long)[records count]);
CKRecord *record = [records lastObject];
self.imageDataAddedFromCamera.recordID = record.recordID.recordName;
NSLog(#"INFO: Record saved successfully for recordID: %#", self.imageDataAddedFromCamera.recordID);
[self.hud dismiss:YES];
[self.hud removeFromSuperview];
[self.imageLoadManager addCIDForNewUserImage:self.imageDataAddedFromCamera]; // update the model with the new image
// update number of items since array set has increased from new photo taken
self.numberOfItemsInSection = [self.imageLoadManager.imageDataArray count];
[self updateUI];
} else {
NSLog(#"Error trying to save the record!");
NSLog(#"ERROR: Error saving record to cloud...%#", error.localizedDescription);
[self.hud dismiss:YES];
[self.hud removeFromSuperview];
[self alertWithTitle:YIKES_TITLE andMessage:ERROR_SAVING_PHOTO_MSG];
}
}, recordProgressHandler:^(double progress) {
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(#"Updating hud display...");
[self.hud setProgress:progress animated:YES];
});
}];
So that the code for updating the progress is part of your saveRecord call.
The code above is not tested by me. So I hope I made no typo's

iOS- Saving data inside a block

I've been struggling on this for several days so any would be appreciated. I'm trying to save the players array below and display it in a UITableView. I'd like to save it so I can display the local player's friends. I've tried several different things but something that looks it's working for others is this.
__block NSArray *friends;
- (void) loadPlayerData: (NSArray *) identifiers {
[GKPlayer loadPlayersForIdentifiers:identifiers withCompletionHandler:^(NSArray *players, NSError *error) {
if (error != nil) {
// Handle the error.
}
if (players != nil) {
friends = players;
NSLog(#"Inside: %#", friends); //Properly shows the array
}
}];
NSLog(#"Outside: %#", friends): //Doesn't work, shows nil
}
But friends is still nil/null afterwards. Am I doing something wrong? Is there any way to save players and use it in a UITableView? Thanks.
***EDIT***
So here's the solution I put together.
typedef void(^CallbackBlock)(id object);
+ (void) retrieveFriends: (CallbackBlock)callback {
GKLocalPlayer *lp = [GKLocalPlayer localPlayer];
if (lp.authenticated) {
[lp loadFriendsWithCompletionHandler:^(NSArray *friends, NSError *error) {
if (friends != nil) {
[self loadPlayerDataWithIdentifiers:friends callback:^(NSArray *playersInfo) {
if (callback) callback(playersInfo);
}];
}
}];
}
}
+ (void) loadPlayerDataWithIdentifiers: (NSArray *) identifiers callback:(CallbackBlock)callback {
[GKPlayer loadPlayersForIdentifiers:identifiers withCompletionHandler:^(NSArray *players, NSError *error) {
if (error != nil) {
// Handle the error.
}
if (players != nil) {
if (callback) callback(players);
}
}];
}
The only thing is, my UITableView is in another class so I tried doing this and making the two methods above public. info isn't printing out anything. Any ideas?
[GameCenterHelper retrieveFriends:^(NSArray *info) {
NSLog(#"Friends Info: %#", info);
}];
Use callback blocks.
typedef void(^CallbackBlock)(id object);
- (void)loadPlayerDataWithIdentifiers:(NSArray *)identifiers callback:(CallbackBlock)callback {
[GKPlayer loadPlayersForIdentifiers:identifiers withCompletionHandler:^(NSArray *players, NSError *error) {
if (error != nil) {
// Handle the error.
}
if (callback) callback(players);
}];
}
You imply in your question that this is for a table view. If so, you need to reload your table after the data has been loaded.
[self loadPlayerDataWithIdentifiers:identifiers callback:^(NSArray *players) {
self.players = players;
[self.tableView reloadData];
}];
Crimson Chris is correct.
The other option is use GCD to wait for the response to comeback.
Or change this to synchronous call as you want to get the results immediately.

Instance method not found callDelegateOnMainThread

I'm getting the error instance method '-callDelegateOnMainThread:withArg:error:' not found what should I do here? I merged in some achievement code into Ray Wenderlichs turn based multiplayer code.
The functions are part of the following interface:
#interface GCTurnBasedMatchHelper : NSObject <GKTurnBasedMatchmakerViewControllerDelegate, GKTurnBasedEventHandlerDelegate, GKAchievementViewControllerDelegate, MFMessageComposeViewControllerDelegate> {
BOOL gameCenterAvailable;
BOOL userAuthenticated;
UIViewController *presentingViewController;
NSMutableDictionary* earnedAchievementCache;
GKTurnBasedMatch *currentMatch;
//id <GCTurnBasedMatchHelperDelegate> delegate;
}
Here are the functions
- (void) submitAchievement: (NSString*) identifier percentComplete: (double) percentComplete
{
//GameCenter check for duplicate achievements when the achievement is submitted, but if you only want to report
// new achievements to the user, then you need to check if it's been earned
// before you submit. Otherwise you'll end up with a race condition between loadAchievementsWithCompletionHandler
// and reportAchievementWithCompletionHandler. To avoid this, we fetch the current achievement list once,
// then cache it and keep it updated with any new achievements.
if(self.earnedAchievementCache == NULL)
{
[GKAchievement loadAchievementsWithCompletionHandler: ^(NSArray *scores, NSError *error)
{
if(error == NULL)
{
NSMutableDictionary* tempCache = [NSMutableDictionary dictionaryWithCapacity: [scores count]];
for (GKAchievement* score in scores)
{
[tempCache setObject: score forKey: score.identifier];
}
self.earnedAchievementCache = tempCache;
[self submitAchievement: identifier percentComplete: percentComplete];
}
else
{
//Something broke loading the achievement list. Error out, and we'll try again the next time achievements submit.
[self callDelegateOnMainThread: #selector(achievementSubmitted:error:) withArg: NULL error: error];
}
}];
}
else
{
//Search the list for the ID we're using...
GKAchievement* achievement = [self.earnedAchievementCache objectForKey: identifier];
if(achievement != NULL)
{
if((achievement.percentComplete >= 100.0) || (achievement.percentComplete >= percentComplete))
{
//Achievement has already been earned so we're done.
achievement= NULL;
}
achievement.percentComplete= percentComplete;
}
else
{
achievement = [[[GKAchievement alloc] initWithIdentifier: identifier] autorelease];
achievement.percentComplete= percentComplete;
//Add achievement to achievement cache...
[self.earnedAchievementCache setObject: achievement forKey: achievement.identifier];
}
if(achievement != NULL)
{
//Submit the Achievement...
[achievement reportAchievementWithCompletionHandler: ^(NSError *error)
{
[self callDelegateOnMainThread: #selector(achievementSubmitted:error:) withArg: achievement error: error];
}];
}
}
}
- (void) achievementSubmitted: (GKAchievement*) ach error:(NSError*) error;
{
if((error == NULL) && (ach != NULL))
{
if (ach.percentComplete == 100.0) {
//UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Achievement Earned!"
// message:(#"%#",ach.identifier)
// delegate:nil
// cancelButtonTitle:#"OK"
// otherButtonTitles:nil];
//[alert show];
//[alert release];
TRACE("achievement submitted %s\n", [ach.identifier UTF8String]);
}
}
else
{
// Achievement Submission Failed.
printf("Achievement Submission Failed\n");
}
}
Looks like this is the missing code you are looking for. Just put it inside of your GCTurnBasedMatchHelper.m file.
- (void) callDelegateOnMainThread: (SEL) selector withArg: (id) arg error: (NSError*) err
{
dispatch_async(dispatch_get_main_queue(), ^(void)
{
[self callDelegate: selector withArg: arg error: err];
});
}
- (void) callDelegate: (SEL) selector withArg: (id) arg error: (NSError*) err
{
assert([NSThread isMainThread]);
if([delegate respondsToSelector: selector])
{
if(arg != NULL)
{
[delegate performSelector: selector withObject: arg withObject: err];
}
else
{
[delegate performSelector: selector withObject: err];
}
}
else
{
NSLog(#"Missed Method");
}
}
Just add the method which doesn't exist.

Spotify EXC_BAD_EXE while second time tap on Login Button after dismiss LoginViewController

Hi i Intigrate SpotifyLib CocoaLibSpotify iOS Library 17-20-26-630 into my Project. I open its SPLoginViewController using Bellow Method:-
-(void)OpenSpotify
{
NSError *error = nil;
[SPSession initializeSharedSessionWithApplicationKey:[NSData dataWithBytes:&g_appkey length:g_appkey_size]
userAgent:#"com.mycomp.spotify"
loadingPolicy:SPAsyncLoadingImmediate
error:&error];
if (error != nil) {
NSLog(#"CocoaLibSpotify init failed: %#", error);
abort();
}
[[SPSession sharedSession] setDelegate:self];
[self performSelector:#selector(showLogin) withObject:nil afterDelay:0.0];
}
-(void)showLogin
{
SPLoginViewController *controller = [SPLoginViewController loginControllerForSession:[SPSession sharedSession]];
controller.allowsCancel = YES;
//controller.view.frame=;
[self presentViewController:controller animated:YES completion:nil];
}
At First time that Appear Spotify Login Screen. After that I tap On Cancel Button, and Try to open again login screen then i got crash EXC_BAD_EXE at this line. sp_error createErrorCode = sp_session_create(&config, &_session);
UPDATE
I Found exet where is got BAD_EXC
in this method
+(void)dispatchToLibSpotifyThread:(dispatch_block_t)block waitUntilDone:(BOOL)wait {
NSLock *waitingLock = nil;
if (wait) waitingLock = [NSLock new];
// Make sure we only queue one thing at a time, and only
// when the runloop is ready for it.
[runloopReadyLock lockWhenCondition:1];
CFRunLoopPerformBlock(libspotify_runloop, kCFRunLoopDefaultMode, ^() {
[waitingLock lock];
if (block) { #autoreleasepool { block(); } }
[waitingLock unlock];
});
if (CFRunLoopIsWaiting(libspotify_runloop)) {
CFRunLoopSourceSignal(libspotify_runloop_source);
CFRunLoopWakeUp(libspotify_runloop);
}
[runloopReadyLock unlock]; // at hear when my debug poin reach after pass this i got bad_exc
if (wait) {
[waitingLock lock];
[waitingLock unlock];
}
}
after doing lots of search i got Solution i check that whether the session already exists then i put if condition like:-
-(void)OpenSpotify
{
SPSession *session = [SPSession sharedSession];
if (!session) {
NSError *error = nil;
[SPSession initializeSharedSessionWithApplicationKey:[NSData dataWithBytes:&g_appkey length:g_appkey_size]
userAgent:#"com.mycomp.spotify"
loadingPolicy:SPAsyncLoadingImmediate
error:&error];
if (error != nil) {
NSLog(#"CocoaLibSpotify init failed: %#", error);
abort();
}
[[SPSession sharedSession] setDelegate:self];
}
[self performSelector:#selector(showLogin) withObject:nil afterDelay:0.0];
}
-(void)showLogin
{
SPLoginViewController *controller = [SPLoginViewController loginControllerForSession:[SPSession sharedSession]];
controller.allowsCancel = YES;
[self presentViewController:controller animated:YES completion:nil];
}
Now no crash and working fine.

Resources