Duplicate UIViewControllers on UINavigationController stack after application wakes - ios

I just learnt IOS programming and am writing my first iPhone app.
My app provides info on an MKMapView, the view controller of which is the root view controller of a UINavigationController. If the mobile signal is bad i use mapViewDidFailLoadingMap:withError: to make the app push one of two different view controllers onto the navigation controller stack depending on what the user is doing. Code follows:
- (void)mapViewDidFailLoadingMap:(MKMapView *)mapView withError:(NSError *)error
{
NSLog(#"mapViewDidFailLoadingMap: %#", [error localizedDescription]);
[aiView stopAnimating];
if (mapTypeInt == 0) {
NSString *message =
[NSString stringWithFormat:#"Your signal is not currently
strong enough to download a map. Switching to table view."];
UIAlertView *av = [[UIAlertView alloc] initWithTitle:#"Maps Unavailable"
message:message
delegate:nil
cancelButtonTitle:#"OK"
otherButtonTitles:nil];
[av show];
if (currentMode == #"retracingSteps")
{
RetraceViewController *rvc =
[[RetraceViewController alloc] initWithNibName:#"RetraceViewController" bundle:nil];
[[self navigationController] pushViewController:rvc animated:YES];
}
else{
TripTableViewController *ttvc = [[TripTableViewController alloc] init];
[[self navigationController] pushViewController:ttvc animated:YES];
}
}
else{
[self setMapType:0];
NSString *message = [NSString stringWithFormat:
#"Your signal is not currently strong enough to download a satellite map.
Switching to Standard Map view."];
UIAlertView *av = [[UIAlertView alloc] initWithTitle:#"Can't Use Satellite Maps"
message:message
delegate:nil
cancelButtonTitle:#"OK"
otherButtonTitles:nil];
[av show];
}
}
Yesterday I tested the poor mobile signal in a rural valley and the correct second view controller was pushed onto the stack. What I then noticed when I locked the phone and rechecked the app after a few minutes was that on waking, the root view controller was displayed quickly followed by the view controller I expected. In effect what this did was to push an identical copy of the second view controller onto the stack. I discovered this when I had to tap the back button half a dozen times to get back to the root view controller.
What I would like the app to do on wake up is to immediately display the view controller that was live when the phone is locked rather than the root view controller. I do not know how to do this.

This is probably happened because even you lock the screen your app still receiving locations update (location services can run code in a real background mode), also when you push a view controller the previous one in the stack still exist and still receiving location update and doing everything has been designed to do, so even you push another controller, if something happen that for the implemented logic has to push another controller, this view controller has access to the navigation controller so has just to add to the stack another controller and push it (the navigation controller still the same for all the VC in the stack)
So what you have to do is stop to handle this cases when you push another controller and restart to do that when you pop back the controller

This bug was caused by the handler mapViewDidFailLoadingMap:withError: being called multiple times before control passed to the pushed view controller. I fixed the bug by making three changes to the handler method:
1) I removed the UIAlertView show and moved it to the pushed view controller;
2) I used a flag (initialized in viewDidAppear:) to return from the handler without executing the push if the handler was being called a second time;
3) I checked for the pushed view controller's existence before allocing it.
When all three of these changes were made, transitions between view controllers happened correctly. Code follows:
- (void)mapViewDidFailLoadingMap:(MKMapView *)mapView withError:(NSError *)error
{
[aiView stopAnimating];
if (mapViewFailed > 0) {
mapViewFailed ++;
return;
}
if (mapTypeInt == 0) {
[map setUserTrackingMode:MKUserTrackingModeNone];
[self removeAsNoticationsObserver];
if ([currentMode isEqualToString:#"retracingSteps"])
{
if (!rvc) {
rvc = [[RetraceViewController alloc] initWithNibName:#"RetraceViewController" bundle:nil];
}
[[self navigationController] pushViewController:rvc animated:YES];
}
else{
if (!ttvc) {
ttvc = [[TripTableViewController alloc] init];
}
[[self navigationController] pushViewController:ttvc animated:YES];
}
mapViewFailed ++;
}
else{
[self setMapType:0];
NSString *message = [NSString stringWithFormat:#"Your signal is not currently strong enough to download a satellite map. Switching to Standard Map view."];
UIAlertView *av = [[UIAlertView alloc] initWithTitle:#"Can't Use Satellite Maps"
message:message
delegate:nil
cancelButtonTitle:#"OK"
otherButtonTitles:nil];
[av show];
}
}
No doubt a more elegant solution is available which I don't know about yet, being an iOS newb.

Related

PresentViewController only working once

I'm a bit dazzled here. And I do think it might be something stupid, but here it goes.
I'm using ABPadLockScreen to set a password and it works just fine when I open the app first time and if the app is closed (terminated) and reopened, but if I just go home and return to it I get the following warning
Warning: Attempt to present <ABPadLockScreenViewController: 0x7fdc70f1d5a0> on <ViewController: 0x7fdc70f18e00> whose view is not in the window hierarchy!
Just for more information here is the code that triggers it, it's inside the viewDidAppear
AppDelegate *delegate = (AppDelegate*)[[UIApplication sharedApplication] delegate];
if (!self.pin && delegate.terminated) {
// [[[UIAlertView alloc] initWithTitle:#"No Pin" message:#"Please Set a pin before trying to unlock" delegate:nil cancelButtonTitle:#"OK" otherButtonTitles:nil] show];
//return;
[self dismissViewControllerAnimated:YES completion:nil];
} else if (!self.isPin && !delegate.terminated) {
ABPadLockScreenViewController *lockScreen = [[ABPadLockScreenViewController alloc] initWithDelegate:self complexPin:YES];
[lockScreen setAllowedAttempts:3];
lockScreen.modalPresentationStyle = UIModalPresentationFullScreen;
lockScreen.modalTransitionStyle = UIModalTransitionStyleCrossDissolve;
[self presentViewController:lockScreen animated:YES completion:nil];
}
If you're wondering about the delegate, it was so the Password Screen would show up when the app was simply closed and reopened and that's when the warning occurs.
Also, if I simply switch from one view to another, the damn thing shows up again.
Thanks in advance

unable to redirect to particular screen from local notification in iOS

I have built notification based application in iOS now I am getting issue is that when notification appears on screen, unable to redirect to particular screen containing its data. One thing I have observed is that If I NSLog it,I get all the values of that particular notification but screen does not get load. Am i missing something while integrating local notification hierarchy code ?
Thanks in advance for your help !!
Here is code in Appdelegate.
- (void)application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification *)notification
{
UIApplicationState state = [application applicationState];
if (state == UIApplicationStateActive) {
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Reminder"
message:notification.alertBody delegate:self cancelButtonTitle:#"OK" otherButtonTitles:nil];
[alert show];
}
if([[notification.userInfo valueForKey:#"note"]isEqualToString:#"note"])
{
// nsuserdefault...
NSDateFormatter *dateFormatterN = [[NSDateFormatter alloc] init];
[dateFormatterN setDateFormat:#"dd-MM-yyyy HH:mm:ss"];
NSString *currentTimeN = [dateFormatterN stringFromDate:notification.fireDate];
NSUserDefaults *prefnote=[NSUserDefaults standardUserDefaults];
[prefnote setObject:currentTimeN forKey:#"dateobject"];
[prefnote setObject:#"notification" forKey:#"notification"];
[prefnote synchronize];
NSBundle *bundle = [NSBundle mainBundle];
NSString *storyboardName = [bundle objectForInfoDictionaryKey:#"UIMainStoryboardFile"];
UIStoryboard *storyboardNote = [UIStoryboard storyboardWithName:storyboardName bundle:nil];
UIViewController *vc1Note = [storyboardNote instantiateViewControllerWithIdentifier:#"Home"]; //if you assigned this ID is storyboard
UIViewController *vc2Note = [storyboardNote instantiateViewControllerWithIdentifier:#"AddNote"]; //if you assigned this ID is storyboard
NSArray *controllersNote = #[vc1Note,vc2Note];
UINavigationController *navControllerNote = (UINavigationController *)self.window.rootViewController;
[navControllerNote setViewControllers:controllersNote];
}
}
I have used IF condition as I have 3 categories and based on particular category It will be redirected to different screen.
You have created a new UINavigationController instance and set view controllers in this new navigation controller.
However you have not presented the nav controller.
You need to either
show you new navigation controller by either adding it to an existing view or replacing existing view
or
push the view controllers to an existing navigation controller (which is already visible) instead of creating a new one. But note that only the last view controller you push will be visible.

Push view programmatically from UIAlertView button?

I would open a UIViewController from a UIAlertView button but it doesn't work.
When user terminate a game I show a UIAlertView with some text and two buttons: "Ok" to dismiss alert and "Score" to open the score page.
This is the actual UIAlertView:
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"YOU WIN!"
message:[NSString stringWithFormat:#"You win a game in %d seconds!",seconds]
delegate:self
cancelButtonTitle:#"Ok"
otherButtonTitles:#"Score", nil];
[alert show];
and this is the code for push the view:
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
NSString *title = [alertView buttonTitleAtIndex:buttonIndex];
if([title isEqualToString:#"Score"]){
ScoreViewController *score = [[ScoreViewController alloc]init];
[[self navigationController] pushViewController:score animated:YES];
}
}
What I obtain is a completely black screen. Can someone help me? I can't figure it out what I'm doing wrong.
PS: ScoreViewController has a segue from the root view but of course I can't create a new one from storyboard because I want to perform segue programmatically, from the alert view button.
Hope I've been clear, any help will be very appreciate!
It looks like you're instantiating an instance of ScoreViewController and pushing onto your navigation controller. While this is a legitimate way of presenting another controller, it's different from performing a Storyboard segue.
First, obviously, you'll have to make sure you have a segue connecting your view controllers in the Storyboard. It sounds like you've made it that far, so next you'll want to make sure the segue has an identifier. Select the segue and set its identifier in the Attributes inspector. Then perform the segue in code:
[self performSegueWithIdentifier:#"YourIdentifier" sender:self];

Runtime error - "Nested Push animation" When popToRootViewController called

Im calling popToRootViewController in a UiViewController that is acting as a sign-in page. The method is called once the backend has authenticated the user and will now allow the user to access their account. When the root view Controller is shown again, I get the following two errors.
nested push animation can result in corrupted navigation bar
Finishing up a navigation transition in an unexpected state. Navigation Bar subview tree might get corrupted.
Here is the code for signing in a user.
- (IBAction)signIn:(id)sender
{
NSString *userName = [self.emailTextField.text stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
NSString *password = [self.passwordTextField.text stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
if (userName.length == 0 || password.length ==0){
UIAlertView *alertView = [[UIAlertView alloc]initWithTitle:#"Invalid Login Credentials" message:#"Make sure you have entered a valid Username and Password" delegate:nil cancelButtonTitle:#"Ok" otherButtonTitles:nil];
[alertView show];
}else{
[PFUser logInWithUsernameInBackground:userName password:password block:^(PFUser *user, NSError *error) {
if (error){
UIAlertView *errorAlertView = [[UIAlertView alloc]initWithTitle:#"Something went wrong" message:[error.userInfo objectForKey:#"error"] delegate:nil cancelButtonTitle:#"ok" otherButtonTitles:nil];
[errorAlertView show];
}else{
//dispatch_sync(dispatch_get_main_queue(), ^{
[self.navigationController popViewControllerAnimated:YES];
//});
}
}];
}
}
The rootViewController is the initial view that the app launches into, and there it checks to see if there is a current user and if not the user will be sent to the login page. I do notice that by going into the Login page, there is a back arrow at the top of the navigation bar to go back to the rootViewController. Once The user signs in, there is now a back button in the same place that wants to take the user back to the LoginViewController which has been popped. I think this is where the issue is occurring.
This error is raised when you try to push several view controllers or pop more than one view controller. Are you sure the viewWillAppear method of your rootViewController is not attempting to push a view controller ? If so, you're attempting to push a view controller while another one is being popped. Hope this may help you!

UIAlertView crash issue on another view controller

I have 2 view controllers, ViewController 1(VC1) and 2(VC2). In VC2 I have a back and done button. On clicking back button it goes directly to VC1 and on done it makes an api call and when it gets a response it shows an alert view and clicking ok goes back to VC1. Now when I make a api call a loading bar shows up and disappears when I get response and shows the AlertView. But if during that fraction of second when the loading disappears and AlertView is going to be popped up if I click on back and the view changes to VC1, the alert appears on VC1 and results in a crash.
This is a rare case as no user will purposely try for it but I was wondering if that crash can be managed without disabling the back button. I think there can be other instance such cases like if we are making an asynchronous calls and if the user is allowed to use UI while waiting for response and if any error alert that was suppose to show on one ViewController shows up in another may result in crash since the delegate that alert is referring to is that of the previous view controller. So is there any way to handle this kind of crash efficiently?
//Alert View sample
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:[[message objectAtIndex:1] capitalizedString] message:[message objectAtIndex:0] delegate:self cancelButtonTitle:#"OK" otherButtonTitles:nil] ;
[alert setTag:701];
[alert show];
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
if ([alertView tag] == 701)
if (buttonIndex == 0)
{
[self.navigationController popViewControllerAnimated:YES];
}
}
The proper way to fix this problem is to use an instance variable to keep a reference to the alert view.
This instance variable should be set to nil in the alertView:didDismissWithButtonIndex: delegate method.
In the view controller's dealloc method, you call dismissWithClickedButtonIndex:animated: if the instance variable is still set.
Assume _alertView is the instance variable.
Create the alert:
_alertView = [[UIAlertView alloc] initWithTitle:[[message objectAtIndex:1] capitalizedString] message:[message objectAtIndex:0] delegate:self cancelButtonTitle:#"OK" otherButtonTitles:nil] ;
[_alertView setTag:701];
[_alertView show];
Update your existing alertView:clickedButtonAtIndex: method:
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex {
if ([alertView tag] == 701) {
_alertView.delegate = nil;
_alertView = nil;
if (buttonIndex == 0) {
[self.navigationController popViewControllerAnimated:YES];
}
}
}
Add:
- (void)alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex {
_alertView = nil;
}
Add:
- (void)dealloc {
if (_alertView) {
_alertView.delegate = nil;
[_alertView dismissWithClickedButtonIndex:_alertView.cancelButtonIndex animated:NO];
_alertView = nil;
}
}

Resources