I have this slideshow app that has 3 images, I've set up some strings in my .h file and I'm trying to get the ratings of the 3 images stored in core data.
I've written the code to store it and I know it works, my problem is that for the first two images I have to have the code outside of the final image which also contains the code to stop & save the ratings to core data.
Because they are outside of the stopslideshow code, the values set to rating1 and rating2 aren't transferring to the second if statement. I'm hoping this will all make sense once you see the code below...
I found another question similar to this and it said you need to declare the strings outside of both if statements first, but that hasn't worked for me.
- (void) SlideShowEngineDidNext:(SlideShowEngine *)slideShow {
countImg += 1;
// If no rating selected on first or second image then set rating to "Null"
if ((_slideshow.currentIndex == 1) || (_slideshow.currentIndex == 2)) {
if (_Sam1.enabled == YES || _Sam2.enabled == YES || _Sam3.enabled == YES || _Sam4.enabled == YES || _Sam5.enabled == YES) {
rating = #"Null";
NSLog(#"SlideShowEngineDidNext, index: %d\nUser did not rate.\nRating: %#", slideShow.currentIndex, rating);
} else {
if (_slideshow.currentIndex == 1) {
_rating1 = rating;
}
if (_slideshow.currentIndex == 2) {
_rating2 = rating;
}
NSLog(#"SlideShowEngineDidNext, index: %d\nRating: %#", slideShow.currentIndex, rating);
}
}
if (countImg == slideShow.images.count) {
// If user didn't select a rating on last image then set the rating to "Null"
if (_slideshow.currentIndex == 0) {
if (_Sam1.enabled == YES || _Sam2.enabled == YES || _Sam3.enabled == YES || _Sam4.enabled == YES || _Sam5.enabled == YES) {
rating = #"Null";
NSLog(#"SlideShowEngineDidNext, index: %d\nUser did not rate.\nRating: %#", slideShow.currentIndex, rating);
} else {
_rating3 = rating;
NSLog(#"SlideShowEngineDidNext, index: %d\nRating: %#", slideShow.currentIndex, rating);
}
}
// Stop the slideshow
[_slideshow stop];
// Display alert to end slideshow
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Survey Completed!" message:#"Press OK to exit!" delegate:nil cancelButtonTitle:#"OK" otherButtonTitles:nil];
[alert show];
NSLog(#"rating1: %#", _rating1);
NSLog(#"rating2: %#", _rating2);
NSLog(#"rating3: %#", _rating3);
// Save ratings
AppDelegate *appDelegate = [[UIApplication sharedApplication] delegate];
NSManagedObjectContext *context = [appDelegate managedObjectContext];
NSManagedObject* currentSurvey = [NSEntityDescription insertNewObjectForEntityForName:#"Ratings" inManagedObjectContext:context];
self.currentSurvey = currentSurvey;
[[self currentSurvey] setValue:_rating1 forKey:#"image1"];
[[self currentSurvey] setValue:_rating2 forKey:#"image2"];
[[self currentSurvey] setValue:_rating3 forKey:#"image3"];
NSError *error;
[[[self currentSurvey] managedObjectContext] save:&error];
}
}
From the NSLogs for rating1, 2 and 3 I'd get from this:
2014-01-11 17:06:35.145 Lab10 - Multimedia[41533:70b] rating1: (null)
2014-01-11 17:06:35.145 Lab10 - Multimedia[41533:70b] rating2: (null)
2014-01-11 17:06:35.146 Lab10 - Multimedia[41533:70b] rating3: 5
so from the if statement with currentIndex 1 and currentIndex2, the strings are being given their value but it's not transferring to the 2nd if statement where the slideshow ends and the data is saved.
if I try to save the data outside the ending if statement, then it just saves the first rating, then the first and second, and then all 3 ratings, all in different objects.
I just can't figure out the correct code placement, if there is any... to get this to work!
Thanks in advance.
Every time you run through this code you are creating a new NSManagedObject, essentially a new row in the database. Is that your intention?
Second, you are not capturing your errors on the save. This is bad. An error could be occurring and you would not know about it.
Have you walked through this code in the debugger and checked each value as you go through? Is the context !nil for example?
In trying to follow your conditional logic it appears that there are a lot of states you are not capturing and I suspect that is what is causing your nil values. You really need to walk through this, line by line, in the debugger and watch what the code is doing.
Your string assignments are just pointing to the same memory address all the time so whenever you change the value of rating any assignment also changes. Try using the following string assignments
_rating1 = [NSString stringWithString:rating];
_rating2 = [NSString stringWithString:rating];
_rating3 = [NSString stringWithString:rating];
See more here on Objective-C strings here http://www.techotopia.com/index.php/Working_with_String_Objects_in_Objective-C
THIS ANSWER WAS SUPPLIED BY THE OP:
Found the answer here
I had a problem with my NSStrings losing their value after being assigned, thanks for the guys who posted.
Found the answer here
I had a problem with my NSStrings losing their value after being assigned, thanks for the guys who posted.
Related
I am attempting to create a feature with my app that allows people to be easily added and/or removed from a group on iOS. The code below works perfectly if I:
1) add one person at a time
2) remove one person at a time
If however I select multiple people to add and/or remove simultaneously only the first action - first add if any or first remove if no additions - is committed to the address book. This method can be called over and over again with any one individual action and it will succeed. I cannot determine why I cannot get it to save multiple adds/deletes within the context of a single call to save. It should be pointed out that no error is giving and stepping through the code the booleans used to determine success indicate that everything worked perfectly.
I have attempted various combinations of adjusting code, for example: create a local address book, retrieve the groupRef, save the address book all within each of the for loops. I've also investigated reverting the address book after saving it to no avail. Thoughts or experiences?
-(IBAction)save:(id)sender
{
#synchronized(self)
{
CFErrorRef error = NULL;
BOOL success = NO;
BOOL successfulSave = NO;
ABAddressBookRef localAddressBook = ABAddressBookCreateWithOptions(NULL, &error);
group = ABAddressBookGetGroupWithRecordID(localAddressBook, groupID);
for (CFIndex addItemCount = 0; addItemCount < [theAddList count]; ++addItemCount)
{
NSNumber *addID = [theAddList objectAtIndex:addItemCount];
ABRecordRef thePersonID = ABAddressBookGetPersonWithRecordID(localAddressBook, [addID intValue]);
success = success || ABGroupAddMember(group, thePersonID, &error);
}
for (CFIndex removeItemCount = 0; removeItemCount < [theRemoveList count]; ++removeItemCount)
{
NSNumber *removeID = [theRemoveList objectAtIndex:removeItemCount];
ABRecordRef thePersonID = ABAddressBookGetPersonWithRecordID(localAddressBook, [removeID intValue]);
success = success || ABGroupRemoveMember(group, thePersonID, &error);
}
if (success)
{
successfulSave = ABAddressBookSave(localAddressBook, &error);
}
if (!(successfulSave && success))
{
UIAlertView *addressBookUpdateAlert = [[UIAlertView alloc] initWithTitle:#"AddressBook Error" message:#"An update to your address book could not be processed at this time. Try again shortly." delegate:nil cancelButtonTitle:#"Ok" otherButtonTitles:nil];
// we can't update a GUI element from a background thread - appears sensitivity didn't appear until iOS 8
[[NSOperationQueue mainQueue] addOperationWithBlock:
^{
[addressBookUpdateAlert show];
}];
}
else
{
[[self navigationController] popViewControllerAnimated:YES];
}
}
}
I can't explain it, but I made a minor change which should have done a better job of trapping an error and instead seems to caused to code to magically start working - which by the way is usually code for you over wrote memory somewhere.
Anyway I did the following and it started working:
Set success = YES before entering the loop
Changed the || test to an && test in both loops
All I changed and yet somehow it just started working...weird...I'm off to test/hammer the thing in case I'm seeing things...
My english is little short. I attach Google Play Games to iPhone5. Everything is fine except leaderboard rank. When I attempt to get rank from leaderboard, It always return zero.
Below is my code. What is problem?
- (void)viewDidLoad {
[super viewDidLoad];
[GPGManager sharedInstance].statusDelegate = self;
}
- (IBAction)signInWasClicked:(id)sender {
[[GPGManager sharedInstance] signInWithClientID:CLIENT_ID silently:NO];
}
- (IBAction)submitScore: (UIButton *)sender {
GPGScore* myScore = [[GPGScore alloc] initWithLeaderboardId:LEADERBOARD_ID];
[myScore submitScoreWithCompletionHandler:^(GPGScoreReport *report, NSError *error){
if (error) {
// error
NSLog(#"ERROR");
} else {
// Success. Score retern fine. score return right value but rank is not.
NSLog(#"%#, %lld", report.highScoreForLocalPlayerAllTime.formattedScore,
report.highScoreForLocalPlayerAllTime.rank);
}
}];
}
In Google developer's "Leaderboard in iOS" section, there is no mention about leaderboard rank. But in GPGScoreReport object, there is GPGScore object and in GPGScore object, score and rank value are on it.
Help me please.
GPGLeaderboard *myLeaderboard = [GPGLeaderboard leaderboardWithId:LEADERBOARD_ID];
myLeaderboard.timeScope = GPGLeaderboardTimeScopeAllTime;
GPGLocalPlayerScore* localScore = [myLeaderboard localPlayerScore];
GPGLocalPlayerRank* localRank = [localScore publicRank];
NSLog(#"Current rank : %#", localRank.formattedRank);
I didn't try this code, but according to the class references, it should work fine. Let me know whether it works or not.
Also, put that code "after" submitting your score.
I have created a class NetCalculator which I am calling when a button is pressed. The method calculate network it gets 2 NSStrings and returns an id object (either "Network" object or "UIAlertView". Then I am checking which object is and I present the data. When I am using the UIAlertView the app is crashing after showing 2-3 alerts.
Any ides why this happens? On terminal it doesnt show any error just some random hexadecimal.
-(IBAction)calculate:(id)sender {
id result;
Network *network = [[Network alloc]init];
NetCalculator *netCalculated = [[NetCalculator alloc] init];
result = [netCalculated calculateNetworkWithIP:ipLabel.text andSubnet:subnetLabel.text];
if([result isKindOfClass:[Network class]]){
network = result;
NSLog(#"network %#",network.networkIP);
}
else if([result isKindOfClass:[UIAlertView class]]) {
UIAlertView *alert;
alert = result;
[alert show];
}
};
Your code is quite strange to me. Your method calculateNetworkWithIP could return a Network result or a UIAlertView result. I wouldn't follow such an approach.
If the problem relies on memory you should show us hot that method is implemented.
Anyway, I would propose some changes (The following code does not take into account ARC or non ARC code). In particular, I would modify the calculateNetworkWithIP to return a Network result. An error will populated if a problem arises and it is passed as an argument.
- (Network*) calculateNetworkWithIP:(NSString *)ip subnet:(NSString*)subnet error:(NSError**)error
If all is ok, the result would be be a Network and so print it or reused it somewhere. Otherwise an NSError would be returned and based on that create and show an alert view.
So, here pseudo code to do it.
NetCalculator *netCalculated = [[NetCalculator alloc] init];
NSError* error = nil;
Network* networkResult = [netCalculated calculateNetworkWithIP:ipLabel.text subnet:subnetLabel.text error:&error];
if(error != nil) {
// create and show an alert view with the error you received
} else {
// all ok so, for example, save the result in a instance variable
}
To follow a similar approach you can take a look at why is "error:&error" used here (objective-c).
I am trying to clean up some transient property recalculation events. In development I have just used a very broad stroke of updating on almost all changes. Now I am trying to determine the best practice, checking for relevant keys and updating as little as needed. My application objects are loaded with dozens of calculations using child and parent attributes where a single change can result in many cascading recalculations.
I thought I understood fairly well, referring to Apple Documentation here and I have experimented with patterns seen on some other StackOverflow posts such as this one by #Marcus S. Zarra. I see Marcus also posted an article on the subject here.
What I have currently is this:
#pragma mark _ worksheetTotal (transient)
+(NSSet *)keyPathsForValuesAffectingWorksheetTotal
{
// local, non-transient, dependent keys here
return [NSSet setWithObjects:#"dailySustenance", nil];
}
-(void)updateWorksheetTotal
{
NSDecimalNumber *lineItemTotal = [self.items valueForKeyPath:#"#sum.lineHoursCost"];
NSDecimalNumber *total = [lineItemTotal decimalNumberByAdding:self.dailySustenance];
[self setWorksheetTotal:total];
}
-(void)setWorksheetTotal:(NSDecimalNumber *)newWorksheetTotal
{
if ([self worksheetTotal] != newWorksheetTotal) {
[self willChangeValueForKey:#"worksheetTotal"];
[self setPrimitiveWorksheetTotal:newWorksheetTotal];
[self didChangeValueForKey:#"worksheetTotal"];
}
}
-(NSDecimalNumber *)worksheetTotal
{
[self willAccessValueForKey:#"worksheetTotal"];
NSDecimalNumber *result = [self primitiveWorksheetTotal];
[self didAccessValueForKey:#"worksheetTotal"];
return result;
}
I am thinking this is straight out of the documentation use case but my observers are not getting notified of the the changes to worksheetTotal.
When a change is made to a lineItem, that notification is received and responds updateWorksheetTotal is called here. I expect this would trigger another notification by the context that worksheetTotal has now changed, but it does not. I have tried numerous variations and combinations of code I have seen but nothing I do seems to make the NSManagedObjectContext consider my update to worksheetTotal to be a change worth reporting to observers.
What am I missing here?
Here is the relevant code for listening for the change in Parent object.
- (void) objectContextDidChange: (NSNotification *) notification
{
NSSet *updatedObjects = [[notification userInfo] objectForKey:NSUpdatedObjectsKey];
// Next look for changes to worksheet that may affect our calculated fields
NSPredicate *worksheetPredicate = [NSPredicate predicateWithFormat:#"entity.name == %# && estimate == %#", #"LaborWorksheet", self];
NSPredicate *materialsPredicate = [NSPredicate predicateWithFormat:#"entity.name == %# && estimate == %#", #"MaterialLineItems", self];
NSPredicate *combinedPredicate = [NSCompoundPredicate orPredicateWithSubpredicates:[NSArray arrayWithObjects:worksheetPredicate, materialsPredicate, nil]];
NSSet *myWorksheets = [updatedObjects filteredSetUsingPredicate:combinedPredicate];
// These are the relevant keys in this estimates labor and materials worksheet
NSSet *relevantKeys = [NSSet setWithObjects:#"worksheetTotal", #"totalEquipmentcost", nil];
BOOL clearCache = NO;
if (myWorksheets.count != 0) {
for (NSManagedObject *worksheet in myWorksheets) {
NSLog(#"got some");
// Here is where it fails, worksheetTotal is not in changedValues.allKeys. It is an empty set.
// Not sure how that could be when I got it from updatedObjects set of userInfo. How could it have no changed values?
NSSet *changedKeys = [NSSet setWithArray:worksheet.changedValues.allKeys];
if ([changedKeys intersectsSet:relevantKeys]) {
clearCache = YES;
NSLog(#"found some, set to clear cache");
// no need to continue checking
break;
}
}
}
if (clearCache) {
// I would update dependent keys here but it is never reached
}
}
The documentation for the changedValues method says:
This method only reports changes to properties that are defined as
persistent properties of the receiver, not changes to transient
properties or custom instance variables. This method does not
unnecessarily fire relationship faults.
Since your property is transient, it will not appear there. I would suggest using KVO to monitor the changes in these properties.
You are returning before didAccessValueForKey. Store to a variable and return it after didAccessValueForKey like
-(NSDecimalNumber *)worksheetTotal
{
[self willAccessValueForKey:#"worksheetTotal"];
NSDecimalNumber *yourVariable = [self primitiveWorksheetTotal];
[self didAccessValueForKey:#"worksheetTotal"];
return yourVariable;
}
I hope it works.
Edit:
In
-(void)setWorksheetTotal:(NSDecimalNumber *)newWorksheetTotal
consider changing
if ([self worksheetTotal] != newWorksheetTotal) {
to
if (worksheetTotal != newWorksheetTotal) {
I want to start a task that runs on another thread "just in case it is needed" to minimize the time the user will have to wait on it later. If there is time for it to complete, the user will not have to wait, but if it has not completed then waiting would be necessary.
Something like, opening a database in viewDidLoad: that will be needed when and if the user pushes a button on the screen. If I wait to open the database until the user actually pushes the button there is a lag. So I want to open it early. Since I don't know how long it will take to open and I don't know how long until the user hits the button, I need a way of saying, if that other task has not completed yet then wait, otherwise just go ahead.
For example:
#implementation aViewController
- (void) viewDidLoad {
[self.dbManager openOrCreateDbWithCompletionHandler: ^(NSError *err) {
if( err ) NSLog( #"There was a problem opening the database" );
}];
}
- (IBAction) goButtonTouched: (id) sender {
// Wait here until the database is open and ready to use.
if( ???DatabaseNotAvailableYet??? ) {
[self putSpinnerOnScreen];
???BlockProgressHereUntilDatabaseAvailable???
[self takeSpinnerOffScreen];
}
// Use the database...
NSManagedObjectContext *context = [self theDatabaseContext];
// Build the search request for the attribute desired
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName: NSStringFromClass([Destinations class])];
request.predicate = [NSPredicate predicateWithFormat: #"dId == %#", sender.tag];
request.sortDescriptors = nil;
// Perform the search
NSError *error = nil;
NSArray *matches = [context executeFetchRequest: request error: &error];
// Use the search results
if( !matches || matches.count < 1 ) {
NSLog( #"Uh oh, got a nil back from my Destination fetch request!" );
UIAlertView *alert = [[UIAlertView alloc] initWithTitle: #"No Info"
message: #"The database did not have information for this selection"
delegate: nil
cancelButtonTitle: #"OK"
otherButtonTitles: nil];
[alert show];
} else {
MyOtherViewController *movc = [[MyOtherViewContoller alloc] init];
movc.destDetails = [matches lastObject];
[self.navigationController pushViewController: movc animated: YES];
}
}
#end
My hope is that there is never a spinner on the screen and never any delay for the user but, since I don't know how long it will take for the database connection to be established, I have to be prepared for it not being ready when the user pushes the button.
I can't use the call back for when openOrCreateDbWithCompletionHandler: completes since I don't want to do anything then, only when the user pushes the button.
I thought about using a semaphore but it seems like I would only signal it once (in the completion handler of the openOrCreateDbWithCompletionHandler: call) but would wait on it every time a button was pushed. That seems like it would only work for the first button push.
I thought about using dispatch_group_async() for openOrCreateDbWithCompletionHandler: then dispatch_group_wait() in goButtonTouched: but since openOrCreateDbWithCompletionHandler: does its work on another thread and returns immediately, I don't think the wait state would be set.
I can simply set a my own flag, something like before the openOrCreateDbWithCompletionHandler:, self.notOpenYet = YES;, then in its completion handler do self.notOpenYet = NO;, then in goButtonTouched: replace ???DatabaseNotAvailableYet??? with self.notOpenYet, but then how do I block progress on its state? Putting in loops and timers seems kludgy since I don't know if the wait will be nanoseconds or seconds.
This seems like a common enough situation, I am sure that you have all done this sort of thing commonly and it is poor education on my side, but I have searched stackOverflow and the web and have not found a satisfying answer.
I think, blocking execution is a bad habit unless you are building your own event loop, which is rarely necessary. You also don't need to do any GCD stuff in your case. Just get a feeling for async.
The following should work:
#implementation aViewController
- (void) viewDidLoad {
self.waitingForDB = NO;
self.databaseReady = NO;
[self.dbManager openOrCreateDbWithCompletionHandler: ^(NSError *err) {
if( err ){
NSLog( #"There was a problem opening the database" )
}else{
[self performSelectorOnMainThread:#selector(handleDatabaseReady) withObject:nil waitUntilDone:NO];
};
}];
}
- (void)handleDatabaseReady{
self.databaseReady = YES;
if(self.waitingForDB){
[self takeSpinnerOffScreen];
[self go];
}
}
- (IBAction) goButtonTouched: (id) sender {
// Wait here until the database is open and ready to use.
if( !self.databaseReady ) {
self.waitingForDB = YES;
[self putSpinnerOnScreen];
else{
[self go];
}
}
-(void)go{
// Use the database...
NSManagedObjectContext *context = [self theDatabaseContext];
// Build the search request for the attribute desired
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName: NSStringFromClass([Destinations class])];
request.predicate = [NSPredicate predicateWithFormat: #"dId == %#", sender.tag];
request.sortDescriptors = nil;
// Perform the search
NSError *error = nil;
NSArray *matches = [context executeFetchRequest: request error: &error];
// Use the search results
if( !matches || matches.count < 1 ) {
NSLog( #"Uh oh, got a nil back from my Destination fetch request!" );
UIAlertView *alert = [[UIAlertView alloc] initWithTitle: #"No Info"
message: #"The database did not have information for this selection"
delegate: nil
cancelButtonTitle: #"OK"
otherButtonTitles: nil];
[alert show];
} else {
MyOtherViewController *movc = [[MyOtherViewContoller alloc] init];
movc.destDetails = [matches lastObject];
[self.navigationController pushViewController: movc animated: YES];
}
}
#end
Performing the call to handleDatabaseReady on the main thread guarantees that no race conditions in setting/reading your new properties will appear.
I'd go with the flag. You don't want to block the UI, just show the spinner and return from the goButtonTouched. However, you do need to cancel the spinner, if it is active, in openOrCreateDbWithCompletionHandler:.
This is rather a simple scenario. You make a method that does the stuff. Lets call it doStuff. From main thread, you call performSelectorInBackgroundThread:#selector(doStuff). Do not enable the button by default. Enable it at the end of doStuff so that user won't tap on it until you are ready. To make it more appealing, you can place a spinner in the place of the button and then replace it with the button when doStuff completes.
There are a number of classes and APIs you can use to achieve this kind of thing. You can use NSThread with synchronization primitives like semaphores and events to wait for it to finish when the user actually presses the button. You can use an NSOperation subclass (with an NSOperationQueue), and you can use GCD queues.
I would suggest you take a look at some the information in the Concurrency Programming Guide from Apple.
In your case you would probably be best served adding the operation to a GCD background queue using dispatch_async in combination with a semaphore which you can wait on when the user taps the button. You can check out the question "How do I wait for an asynchronously dispatched block to finish?" for an example.