UIViewController does not receive dealloc or viewDidUnload when dismissed - ios

UPDATE: I made a mistake in my debugging - this question is not relavent - please see comment below.
Note: I am using Automated Reference Counting
When my app starts - I present a view controller inside a UINavigationController with presentViewController:animated:completion. That view controller loads a second view controller on to the navigation stack. The second view controller uses [self.presentingViewController dismissViewControllerAnimated:YES completion:nil] to dismiss itself. My issue, is that neither dealloc nor viewDidUnload are ever called in the first view controller. However, with instruments, I can see that the view controller is no longer allocated once the presented view controllers are dismissed. The code that presents the first view controller is
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
// check if our context has any accounts
if( [self.accounts count] == 0 )
{
// Display the Add Account View Controller
MySettingsViewController *settingsVC = [[MySettingsViewController alloc] initWithNibName:#"MySettingsViewController" bundle:nil];
settingsVC.appContext = self.appContext;
UINavigationController *navVC = [[UINavigationController alloc] initWithRootViewController:settingsVC];
navVC.modalTransitionStyle = UIModalTransitionStyleCrossDissolve;
if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPhone)
{
// Display the Add Account View Controller
}
else
{
navVC.modalPresentationStyle = UIModalPresentationFormSheet;
}
[self presentViewController:navVC animated:YES completion:nil];
}
}
So, I do not have any references to settingsVC that should be sticking around but I do not know why my dealloc is not being called. Any help would be great.

They don't get called because you haven't correctly released your view controller.
You allocate both settingsVC and navVC with alloc and thus get owning references to both that you must later release which you didn't do.
You can do it like this:
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
// check if our context has any accounts
if( [self.accounts count] == 0 )
{
// Display the Add Account View Controller
MySettingsViewController *settingsVC = [[MySettingsViewController alloc] initWithNibName:#"MySettingsViewController" bundle:nil];
settingsVC.appContext = self.appContext;
UINavigationController *navVC = [[UINavigationController alloc] initWithRootViewController:settingsVC];
navVC.modalTransitionStyle = UIModalTransitionStyleCrossDissolve;
// At this points, "settingsVC" is additionally retained by the navigation controller,
// so we can release it now.
[settingsVC release];
if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPhone)
{
// Display the Add Account View Controller
}
else
{
navVC.modalPresentationStyle = UIModalPresentationFormSheet;
}
[self presentViewController:navVC animated:YES completion:nil];
// At this point "navVC" is retained by UIKit, so we can release it as well.
[navVC release];
}
}
An alternative would have been to autorelease both right away.

Related

Inherited view controller will not dismiss itself in a block #ICETutorial

I'm using ICETutorial with cocoapods.
I'm using it in a SettingsViewController where you can view the tutorial in settings.
// SettingsViewController.m
Tutorial2ViewController *vc = [[Tutorial2ViewController alloc] init];
[self.navigationController setNavigationBarHidden:YES animated:NO];
[self.navigationController pushViewController:vc animated:NO];
And Tutorial2ViewController inherits from ICETutorialController
#interface Tutorial2ViewController : ICETutorialController
ICETutorialPages have buttons that will trigger a callback. It takes in a block. So in my implementation, I have this:
- (id)init
{
ICETutorialPage *layer1 = [[ICETutorialPage alloc] initWithSubTitle:#"Page 1" description:#"Page 1" pictureName:#"Tutorial1_640x1136.png"];
NSArray *tutorialLayers = #[layer1];
self = [super initWithNibName:#"ICETutorialController_iPhone" bundle:nil andPages:tutorialLayers];
__weak Tutorial2ViewController *vc = self;
[self setButton1Block:^(UIButton *button){
NSLog(#"Button 1 pressed.");
[[vc.navigationController topViewController] dismissViewControllerAnimated:NO completion:nil];
}];
if (self != nil)
{
}
return self;
}
The reason why I put all the code in init is that I don't want SettingsViewController to know anything about how the Tutorial2ViewController works. Settings should alloc and init, push to the navigation controller stack and the Tutorial2ViewController should know how to handle itself.
I do get the NSLog that button1 is pressed but the view controller does not dismiss itself and return me to the SettingsViewController.
I will contact the creator of the library and ask him/her to see this question also. I feel that this is me not misunderstanding blocks, navigation controllers, cocoapods, etc...
Thanks
Just try [self.navigationController popViewControllerAnimated:YES];

iOS State restoration and UINavigationController modal views

I am trying to incorporate State Restoration in my app. I have it working fine for the most part, but presenting a navigation controller for a modal view on top of another navigation controller seems challenging.
For testing, I created a new split-view app on the iPad, with navigation controllers for both sides of the split view, and a Master and Detail view controller for each side, the roots of their respective navcontrollers. In the master view, you can click on a button to push a new TestViewController onto the navController stack programatically. I hook up the splitView in the storyboard, add restorationIDs to everything, opt-in to the delegate, provide a restoration class and adhere to the UIViewControllerRestoration protocol for TestViewController (since it's created programmatically) and everything works fine. If I close the app and retort it, it will start the TestViewController pushed onto the master's navcontroller. So far so good.
I then change the button handler to present the TestViewController inside a new UINavigationController, present it onto the master's navigation controller, to show a modal view (instead of pushing it on the nav stack). Now, when I relaunch the app, there is no modal view there anymore. TestModalViewController's viewControllerWithRestorationIdentifierPath:coder: is actually called correctly as before, but the modal view is never presented for some reason.
Here is the code for what I'm talking about
MasterViewController.h:
- (void)pushButton:(id)sender
{
TestModalViewController *test = [[TestModalViewController alloc] initWithNibName:#"TestViewController" bundle:nil];
test.restorationIdentifier = #"testid";
test.restorationClass = [TestModalViewController class];
UINavigationController *modal = [[UINavigationController alloc] initWithRootViewController:test];
modal.modalPresentationStyle = UIModalPresentationFormSheet;
modal.restorationIdentifier = #"ModalTestID";
[self.navigationController presentViewController:modal animated:YES completion:nil];
return;
}
TestModalViewController.m:
+ (UIViewController *) viewControllerWithRestorationIdentifierPath:(NSArray *)identifierComponents coder:(NSCoder *)coder {
TestModalViewController *test = [[TestModalViewController alloc] initWithNibName:#"TestViewController" bundle:nil];
test.restorationClass = [TestModalViewController class];
test.restorationIdentifier = [identifierComponents lastObject];
return test;
}
Perhaps the UINavigationController that is created to display modally is never preserved? Not sure why, because it does have a restorationIdentifier.
Edit:
After further testing, it turns out if I remove the UINavigationController from the the pushButton: code, and present the TestModalViewController instance directly, it gets restored correctly. So something about the UINavigationController being presented from another UINavigationController?
This works (though not what I really want):
- (void)pushButton:(id)sender
{
TestModalViewController *test = [[TestModalViewController alloc] initWithNibName:#"TestViewController" bundle:nil];
test.restorationIdentifier = #"testid";
test.restorationClass = [TestModalViewController class];
//UINavigationController *modal = [[UINavigationController alloc] initWithRootViewController:test];
//modal.modalPresentationStyle = UIModalPresentationFormSheet;
//modal.restorationIdentifier = #"ModalTestID";
[self.navigationController presentViewController:test animated:YES completion:nil];
return;
}
EDIT:
Attached link to test project: dropbox.com/sh/w8herpy2djjl1kw/vw_ZWqimgt
It's basically the Core Data master-detail template; run it on the iPad simulator. The + button in Master invokes the TestModalVC; if you then press the Home button, then kill debugger and launch again, you see the snapshot contains the TestModalVC but when the app is launched, it doesn't get restored
You can either create your own restoration class to handle this, or add the following to your app delegate:
- (UIViewController *)application:(UIApplication *)application viewControllerWithRestorationIdentifierPath:(NSArray *)identifierComponents
coder:(NSCoder *)coder
{
NSString *lastIdentifier = [identifierComponents lastObject];
if ([lastIdentifier isEqualToString:#"ModalTestID"])
{
UINavigationController *nc = [[UINavigationController alloc] init];
nc.restorationIdentifier = #"ModalTestID";
return nc;
}
else if(...) //Other navigation controllers
{
}
return nil;
}
More information in the documentation.

EXC_BAD_ACCESS using ARC when poping back to view controller

to begin with, here is some code:
- (void)viewDidLoad
{
[super viewDidLoad];
FirstViewController *first = [[FirstViewController alloc] init];
SecondViewController *second = [[SecondViewController alloc] init];
MBPullDownController *pullDownController = [[MBPullDownController alloc] initWithFrontController:first backController:second];
[self.navigationController addChildViewController:pullDownController];
}
- (void)pushAnotherViewController:(NSNotification *)notification
{
AnotherViewController *another = [self.storyboard instantiateViewControllerWithIdentifier:#"anotherViewController"];
[self pushScheduleViewController:another];
}
I use the MBPullDownController open source control. Using to seperate view controllers I load into the pull down controller. This code is in a view controller called RootViewController which is embedded in a UINavigationController. Then there's a method for pushing another view controller in the navigation controller. It's when I try to use the method (in AnotherViewController) popToRootViewController: that my app crashes and the EXC_BAD_ACCESS message comes up in the console.
EDIT
This is my code in "AnotherViewController"
- (void)popBack
{
RootScheduleViewController *root = [[RootScheduleViewController alloc] init];
[self.navigationController popToViewController:root animated:YES];
}
You are getting a bad access error when you call popBack because you are creating a new instance of the view controller and then trying to pop to it. For a navigation controller, the view controller must be part of the navigation stack in order to pop to it. So if an instance of this view controller exists, find it in the navigation stack and pop to it.
for(UIViewController * viewController in self.navigationController.viewControllers){
if([viewController isKindOfClass:[RootScheduleViewController class]]){
[self.navigationController popToViewController:viewController animated:NO];
break;
}
}

How to present a TabBarController modal

From one view(LoginTesteInicial) I can go to two tabBarControllers, but when I run the code, it crashes with this error:
Attempt to present <UITabBarController: 0x8a4a870> on <LoginTesteInicial: 0x8a46970> whose view is not in the window hierarchy!
here is my code from LoginTesteInicial.m:
UITabBarController *vc;
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
vc = [[UIStoryboard storyboardWithName:#"Main_iPad" bundle:nil] instantiateViewControllerWithIdentifier:#"TabBarSemLogin"];
} else {
vc = [[UIStoryboard storyboardWithName:#"Main_iPhone" bundle:nil] instantiateViewControllerWithIdentifier:#"TabBarSemLogin"];
}
[vc setModalTransitionStyle:UIModalTransitionStyleFlipHorizontal];
[self presentViewController:vc animated:YES completion:nil];
The answer to your question is that when -viewDidLoad is called, the view controller's view is not in the view hierarchy. You need to wait until the view is placed into the view hierarchy. This can been done either in -viewWillAppear: or -viewDidAppear:.
The "Unbalanced calls to begin/end appearance transitions" warning you get is because a view controller has not be fully loaded before being replaced with another view controller. To avoid that warning, you can use -performSelector:withObject:afterDelay: to schedule the present view controller in the next run loop.
- (void)viewDidAppear:(BOOL)animated
{
…
[self performSelector:#selector(showTabBarController) withObject:nil afterDelay:0.0];
}
- (void)showTabBarController
{
UITabBarController *vc;
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
vc = [[UIStoryboard storyboardWithName:#"Main_iPad" bundle:nil] instantiateViewControllerWithIdentifier:#"TabBarSemLogin"];
} else {
vc = [[UIStoryboard storyboardWithName:#"Main_iPhone" bundle:nil] instantiateViewControllerWithIdentifier:#"TabBarSemLogin"];
}
[vc setModalTransitionStyle:UIModalTransitionStyleFlipHorizontal];
[self presentViewController:vc animated:YES completion:nil];
}
Move [self presentViewController:vc animated:YES completion:nil] and related code it either of these
1 viewWillAppear
2 viewWillLayoutSubviews
3 viewDidLayoutSubviews
You will need to keep one flag which will take care that even if one of these method triggered multiple times your view will get presented only once.
Or you can avoid presenting view and add as subview, do this instead of presenting if you didn't like keeping flag etc.
[self.view addSubview:vc.view];
[self addChildViewController:vc];
[vc didMoveToParentViewController:self]

Issue with definesPresentationContext / UIModalPresentationCurrentContext - Current context view controller gets lost

This only accours if you are presenting in a view controller that is managed by a navigation controller.
The reproduction steps are:
1 - Present a view controller using UIModalPresentationCurrentContext
self.definesPresentationContext = YES;
ViewController* viewController = [[ViewController alloc] init];
viewController.modalPresentationStyle = UIModalPresentationCurrentContext;
[presentOnViewController presentViewController:viewController animated:YES completion:nil];
2 - Present a view controller over the top using the default full screen presentation style
ViewController* viewController = [[ViewController alloc] init];
[self presentViewController:viewController animated:YES completion:nil];
3 - Dismiss the top presented view controller (the full screen one)
[self dismissViewControllerAnimated:YES completion:nil];
Now the problem is the 2nd view controller (presented using UIModalPresentationCurrentContext) disappears. Also it is impossible to present another view controller using UIModalPresentationCurrentContext, because the system thinks its still there.
I believe the issue is a bug in the framework. As mentioned it only occurs when the presenting in a view controller managed by a navigation controller. There is a nasty work around which uses the containment API. It creates a dummy view controller which views are presented from. The steps are:
1 - When presenting a view in context who's parent is a navigation controller, use a dummy view controller:
- (void)presentInContext
{
UIViewController* presentOnViewController = self;
if ([self.parentViewController isKindOfClass:[UINavigationController class]])
{
// Work around - Create an invisible view controller
presentOnViewController = [[DummyViewController alloc] init];
presentOnViewController.view.frame = self.view.frame;
// Containment API
[self addChildViewController:presentOnViewController];
[self.view addSubview:presentOnViewController.view];
[presentOnViewController didMoveToParentViewController:self];
presentOnViewController.definesPresentationContext = YES;
}
ViewController* viewController = [[ViewController alloc] init];
viewController.modalPresentationStyle = UIModalPresentationCurrentContext;
[presentOnViewController presentViewController:viewController animated:YES completion:nil];
}
2 - When dismissing the view controller tidy up
- (void)dismissSelf
{
__weak UIViewController* presentingViewController = self.presentingViewController;
[self dismissViewControllerAnimated:YES completion:^{
// Remove the dummy view controller
if ([presentingViewController isKindOfClass:[DummyViewController class]])
{
[presentingViewController willMoveToParentViewController:nil];
[presentingViewController.view removeFromSuperview];
[presentingViewController removeFromParentViewController];
}
}];
}
Thats it... The fix is dirty, but does the trick with no visual flicker.

Resources