Why popViewController only works every time

I am completely at a standstill, here is the situation:

My application uses the Core Location infrastructure to get the user's current location, and then ping my server on TrailBehind for interesting places nearby and display them as a list. No problems.

To save batteries, I will turn off the GPS service after receiving my data from the server. If the user moves while using the application and wants to get a new list, he clicks Refresh on the navigation controller, and the CLLocation service is activated again, a new batch of data is retrieved from the server, and the table is redrawn.

While the application is capturing data from my server, I load the download screen with a spinning globe that says “Download, please wait,” and I hide the navigation bar so that it doesn't hit “backward”.

So, the initial data capture from the server goes flawlessly.

The first time I hit the update, all codes are executed to get a new location, start the server again for a new data list and update the cells. However, instead of loading the table view as it should, it restores the navigation controller panel to present the table, but still shows my loading view in the main window. This is true only on the device, everything works fine in the simulator.

The SECOND time when I click update works fine.

THIRD time, when I click update, it fails as above.

The FOURTH time when I click update works fine.

FIFTH time when I click update, it fails as above.

etc. etc., even updates successfully, and odd updates fail. I stepped over all my code line by line, and everything seems to be working fine. I really continued to move on to the basic instructions, and after a huge number of “step by step” clicks, I found that the table view was actually displayed on the screen at some point in CFRunLoopRunSpecific, but then I clicked “continue” and my boot view took up the screen .

I am absolutely baffled. Please help !! Thanks so much in advance for your understanding.

Video of strange behavior:

Relevant Code:

RootViewControllerMethods (This is the base view for this TableView project)

- (void)viewDidLoad { //Start the Current Location controller as soon as the program starts. The Controller calls delegate methods //that will update the list and refresh [MyCLController sharedInstance].delegate = self; [[MyCLController sharedInstance].locationManager startUpdatingLocation]; lv = [[LoadingViewController alloc] initWithNibName:@"Loading" bundle:nil]; [self.navigationController pushViewController:lv animated:YES]; [super viewDidLoad]; } - (void)updateClicked { //When the location is successfully updated the UpdateCells method will stop the CL manager from updating, so when we want to update the location //all we have to do is start it up again. I hope. [[MyCLController sharedInstance].locationManager startUpdatingLocation]; [self.navigationController pushViewController:lv animated:YES]; //LV is a class object which is of type UIViewController and contains my spinning globe/loading view. } -(void)updateCells { //When the Core Location controller has updated its location it calls this metod. The method sends a request for a JSON dictionary //to trailbehind and stores the response in the class variable jsonArray. reloadData is then called which causes the table to //re-initialize the table with the new data in jsonArray and display it on the screen. [[MyCLController sharedInstance].locationManager stopUpdatingLocation]; if(self.navigationController.visibleViewController != self) { self.urlString = [NSString stringWithFormat:@"http://www.trailbehind.com/iphone/nodes/%@/%@/2/10",self.lat,self.lon]; NSURL *jsonURL = [NSURL URLWithString:self.urlString]; NSString *jsonData = [[NSString alloc] initWithContentsOfURL:jsonURL]; NSLog(@"JsonData = %@ \n", jsonURL); self.jsonArray = [jsonData JSONValue]; [self.tableView reloadData]; [self.navigationController popToRootViewControllerAnimated:YES]; [jsonData release]; } } 

CLController Methods: basically just sends all the data directly to the RootViewController

 // Called when the location is updated - (void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation { NSLog(@"New Location: %@ \n", newLocation); NSLog(@"Old Location: %@ \n", oldLocation); @synchronized(self) { NSNumber *lat = [[[NSNumber alloc] init] autorelease]; NSNumber *lon = [[[NSNumber alloc] init] autorelease]; lat = [NSNumber numberWithFloat:newLocation.coordinate.latitude]; lon = [NSNumber numberWithFloat:newLocation.coordinate.longitude]; [self.delegate noteLat:lat]; [self.delegate noteLon:lon]; [self.delegate noteNewLocation:newLocation]; [self.delegate updateCells]; } } 
+6
iphone cocoa-touch uiviewcontroller uinavigationcontroller
source share
5 answers

The first thought is that you cannot send startUpdatingLocation to the CLLocationManager until you push your boot view. Often the first message is locationManager: didUpdateToLocation: fromLocation: will be displayed instantly with cached GPS data. This only matters if you act on each message and do not filter the GPS data, as shown in your sample code here. However, this will not affect the situation you described - it will cause the boot screen to get stuck.

I came across the same strange behavior as this, in another situation, when I tried to appear in the root view controller when switching to another tab, and the call was not made in the right place. I believe that popToRootViewController was called twice for me. My suspicion is that your boot view is either popped up twice or popped up twice twice.

I recommend implementing -viewWillAppear :, -viewDidAppear :, -viewWillDisappear: and -viewDidDisappear: with minimal logging in your LoadViewController.

 - (void)viewWillAppear:(BOOL)animated { NSLog(@"[%@ viewWillAppear:%d]", [self class], animated); [super viewWillAppear:animated]; } - (void)viewDidAppear:(BOOL)animated { NSLog(@"[%@ viewDidAppear:%d]", [self class], animated); [super viewDidAppear:animated]; } - (void)viewWillDisappear:(BOOL)animated { NSLog(@"[%@ viewWillDisappear:%d]", [self class], animated); [super viewWillDisappear:animated]; } - (void)viewDidDisappear:(BOOL)animated { NSLog(@"[%@ viewDidDisappear:%d]", [self class], animated); [super viewDidDisappear:animated]; } 

Then run the test on your device to see if they are always sent to the view controller and how often. You can add some entries in -updateClicked to detect double clicks.

Another thought, while your @synchronized block is a good idea, it will only keep other threads from executing these statements until the first thread exits the block. I suggest moving the -stopUpdatingLocation message to the first statement inside this synchronized block. Thus, as soon as you decide to act on some new GPS data, you immediately tell CLLocationManager to stop sending new data.

+1
source share

Can you try and debug your application to find out where the control goes when calling updateCells? Nothing seems like an app.

Make sure that there are no memory warnings in the LoadViewController class at boot time. If there is a warning about saving memory and your RootViewController view is being freed, then viewDidLoad will be called again when you put it in the RootViewController.

Save breakpoints in viewDidLoad and updateCells. Are you sure you are not using LoadViewController anywhere else?

0
source share

So, I never got this to work. I observe this behavior on the device only every time I call popViewController programmatically, instead of allowing the default back button on the navigation controller to make pop-ups.

My workaround was to create a custom download mode and flip the screen into this view every time there would be a delay due to Internet access. My method accepts the boolean variable yes or no - yes switches to the boot screen and does not switch back to normal mode. Here is the code:

 - (void)switchViewsToLoading:(BOOL)loading { // Start the Animation Block CGContextRef context = UIGraphicsGetCurrentContext(); [UIView beginAnimations:nil context:context]; [UIView setAnimationTransition: UIViewAnimationTransitionFlipFromLeft forView:self.tableView cache:YES]; [UIView setAnimationCurve:UIViewAnimationCurveEaseInOut]; [UIView setAnimationDuration:.75]; // Animations if(loading) { if (lv == nil) { lv = [[LoadingViewController alloc] initWithNibName:@"Loading" bundle:nil]; } [self.view addSubview:lv.view]; [self.view sendSubviewToBack:self.tableView]; self.title = @"TrailBehind"; } else { [lv.view removeFromSuperview]; } // Commit Animation Block [UIView commitAnimations]; //It looks kind of dumb to animate the nav bar buttons, so set those here if(loading) { self.navigationItem.rightBarButtonItem = nil; self.navigationItem.leftBarButtonItem = nil; self.title = @"TrailBehind"; } else { UIBarButtonItem *feedback = [[UIBarButtonItem alloc] initWithTitle:@"Feedback" style:UIBarButtonItemStylePlain target:self action:@selector(feedbackClicked)]; self.navigationItem.rightBarButtonItem = feedback; UIBarButtonItem *update = [[UIBarButtonItem alloc] initWithTitle:@"Move Me" style:UIBarButtonItemStylePlain target:self action:@selector(updateClicked)]; self.navigationItem.leftBarButtonItem = update; [feedback release]; [update release]; } 

}

0
source share

Looking at your source code, I strongly suspect this block:

 - (void)viewDidLoad { ... lv = [[LoadingViewController alloc] initWithNibName:@"Loading" bundle:nil]; [self.navigationController pushViewController:lv animated:YES]; [super viewDidLoad]; } 

viewDidLoad is called every time the NIB is loaded, which can happen several times, especially if you use low memory (which seems likely, given your observation that this only happens on the device). I recommend that you implement -didReciveMemoryWarning , and after calling super, at least print a log so you can see if this is happening to you.

What bothers me about the above code is that you almost certainly leak lv , which means an increasing number of LoadingViewControllers working. You say this is a class variable. Do you really mean this instance variable? ivars should always use accessors ( self.lv or [self lv] , not lv ). Do not assign them directly; you will almost always do it wrong (as you probably don here).

0
source share

I ran into this looking for the same problem, so although I’m sure that you have already solved your problem, I decided that I would send my solution in case someone else runs into it ...

This error seems to be caused when you assign two IBActions to the same UIButton in the interface builder. It turned out that the button that I used to direct the view controller to the stack was assigned to two IBActions, and each of them pressed another controller onto the navigation controller's stack (although you will only see one of them - maybe the last one of which is called). One way or another, pressing the "Back" button on the top screen does not cancel it (or perhaps rejects a second, invisible controller), and you need to press twice to return.

In any case, check your buttons and make sure they are assigned to only one IBAction. This fixed it for me.

0
source share

All Articles