lewis
lewis

Reputation: 3192

Calling popToRootViewControllerAnimated causing crash. How should I be doing this?

The app is for taking body measurements. The user can say I want to measure: legs, arms and neck, in the settings tab and in the main tab there is a view which loops round to take each measurement.
This is achieved like so:

i have tab controller the first tab has a navigation controller the first view controller on the storyboard and has one segue to itself the board loops round until it has all the measurements then it segues to a different controller

the problem is: if the user changes which measurements they are taking in the settings tab, the first tab needs to completely reload, as if the app was just starting up, clearing down the whole nav stack etc.

at the moment the tab controller calls popToRootViewControllerAnimated on the navigation controller in the measurements tab, but this is causing a crash. Each screen has a slider control and a call to titleForRow:forComponent: is being called on a deleted view causing it to crash.

What am I doing wrong?!

Here's the tab bar controller code

//  TabBarController.m
//

#import "TabBarController.h"
#import "TodaysMeasurementObject.h"
#import "AppDelegateProtocol.h"
#import "AddMeasurementViewController.h"
#import "ReadPerson.h"
#import "AppDelegate.h"

@interface TabBarController () <UITabBarControllerDelegate>



@end

@implementation TabBarController

bool resetWizardView = false;

- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
    self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
    if (self) {
    }
    return self;
}

- (void)viewDidLoad
{
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    self.delegate = self;

    [[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications];
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(orientationChanged:)
                                                 name:UIDeviceOrientationDidChangeNotification object:nil];

    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(measurementsSettingsUpdated:) name:@"MeasurementsSettingsUpdated" object:nil];

}

- (void) measurementsSettingsUpdated:(NSNotification *) notification
{
//    UINavigationController *navigationController = [self.viewControllers objectAtIndex:0];
//    AddMeasurementViewController *addMeasurement = [[AddMeasurementViewController alloc] init];
//    [navigationController setViewControllers: [[NSArray alloc] initWithObjects:addMeasurement, nil]];
    resetWizardView = YES;
}

- (void) viewDidAppear:(BOOL)animated
{
    if (![ReadPerson userHasRecords]) {
        [self setSelectedIndex:3];
    }
}

- (void)orientationChanged:(NSNotification *)notification
{
    // We must add a delay here, otherwise we'll swap in the new view
    // too quickly and we'll get an animation glitch
    [self performSelector:@selector(showGraphs) withObject:nil afterDelay:0];
}

- (void)showGraphs
{
    UIDeviceOrientation deviceOrientation = [UIDevice currentDevice].orientation;
    if (deviceOrientation == UIDeviceOrientationLandscapeLeft && !isShowingLandscapeView)
    {
        [self performSegueWithIdentifier: @"toGraph" sender: self];
        isShowingLandscapeView = YES;
    }

    else if (deviceOrientation != UIDeviceOrientationLandscapeLeft && isShowingLandscapeView)
    {
        [self dismissModalViewControllerAnimated:YES];
        isShowingLandscapeView = NO;
    }
}

- (void)viewDidUnload
{
    [super viewDidUnload];
    // Release any retained subviews of the main view.
}

- (void)dealloc
{
    [[NSNotificationCenter defaultCenter] removeObserver:self];
    [[UIDevice currentDevice] endGeneratingDeviceOrientationNotifications];
}

- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
    if(interfaceOrientation == UIInterfaceOrientationLandscapeRight)
    {
        [self performSegueWithIdentifier: @"toGraph" sender: self];
    }

    return false;
}

- (void)tabBarController:(UITabBarController *)tabBarController didSelectViewController:(UIViewController *)viewController {
    int tbi = tabBarController.selectedIndex;
    if (tbi == 0) {
        [[viewController view] setNeedsDisplay];
        if (resetWizardView) {
            [(UINavigationController*)[self.viewControllers objectAtIndex:0] popToRootViewControllerAnimated: NO]; // ******* POP CALLED HERE ******
            resetWizardView = false;
        }
    }
}

- (TodaysMeasurementObject*) theAppDataObject
{
    id<AppDelegateProtocol> theDelegate = (id<AppDelegateProtocol>) [UIApplication sharedApplication].delegate;
    TodaysMeasurementObject* theDataObject;
    theDataObject = (TodaysMeasurementObject*) theDelegate.theAppDataObject;
    return theDataObject;
}

- (BOOL)shouldAutorotate {
    return NO;
}

- (NSUInteger)supportedInterfaceOrientations {
    return UIInterfaceOrientationMaskPortrait;
}


@end

UPDATED

- (void) measurementsSettingsUpdated:(NSNotification *) notification
{
    NSMutableArray *viewControllers = [[NSMutableArray alloc] initWithArray: self.viewControllers];
    UINavigationController *navigationController = [viewControllers objectAtIndex:0];
    AddMeasurementViewController *addMeasurement = [[AddMeasurementViewController alloc] init];
    [navigationController setViewControllers: [[NSArray alloc] initWithObjects:addMeasurement, nil]];

    [viewControllers setObject:navigationController atIndexedSubscript:0];

    self.viewControllers = viewControllers;
}

and removed the code from - tabBarController:didSelectViewController:

but still the same error. I think the problem is that it's trying to get a value for the slide control after the view has been deleted. But some part of the view must still be alive...? Anyway to kill that off? Or leave it all alive??

Upvotes: 0

Views: 1282

Answers (2)

lewis
lewis

Reputation: 3192

In the end I gave up on fixing this directly. Instead I capture the notification in the view controller and then popup a UIAlertView with one button. When the user presses this popToRootViewControllerAnimated is called. I think this is actually a better user experience too as the user gets a message about why contents of their tab has changed.

Hope this saves someone some time/stress in the future.

Upvotes: 0

Michael Kernahan
Michael Kernahan

Reputation: 1462

In your code:

    [[viewController view] setNeedsDisplay];
    if (resetWizardView) {
        [(UINavigationController*)[self.viewControllers objectAtIndex:0] popToRootViewControllerAnimated: NO]; // ******* POP CALLED HERE ******

A couple of points:

  1. You probably only need setNeedsDisplay when resetWizardView == NO. I don't think you need it at all to tell you the truth.

  2. I think you want to be targeting viewController instead of [self.viewControllers objectAtIndex:0] in the last line.

[(UINavigationController*)viewController popToRootViewControllerAnimated:NO]

Upvotes: 1

Related Questions