UIViewController custom titleView crashes app - iphone

I have a navigation based iPhone app.
When you press on a cell in the tableview a new UIViewController is pushed to the navigation stack. In this view controller I am setting a custom titleView in the viewDidLoad method:
- (void)viewDidLoad {
[super viewDidLoad];
// Setup custom navigation title
[self setTitle:#"Mediaportal"];
navItem = [[NavigationBarTitleItemViewController alloc] initWithNibName:#"NavigationBarTitleItem" bundle:nil];
[navItem setTitle:[theServer name]];
[navItem setSubTitle:#""];
[self.navigationItem setTitleView:navItem.view];
…
}
Once I switch back to the RootViewController:
[self.navigationController popToRootViewControllerAnimated:YES];
the app crashes with the following error (NSZombieEnabled=YES):
*** -[CALayer retain]: message sent to deallocated instance 0x5a5fd80
From what I can see the RootViewController still tries to access the custom titleView, which was deallocated with the second view controller. Once I comment out the custom titleView part in my code the app works.
I tried to set the navigationItem.titleView to nil (as found in the apple docs) before the second ViewController is deallocated, but that doesn't help.
Do you have a hint what I can do to prevent this crash?
Thanks,
Mark.

I had this exact same error a month or so ago, exactly the same situation. It drove me NUTS.
I discovered that the viewController i was popping too hadn't been deallocated at all. I had a custom UIButton subclass added to that view however that had been deallocated when the second view had been pushed. So when popping back, the UIButton wasn't there.
Check the view you are popping back to, to ensure you've not got any classes that you are deallocating, or are being autoreleased without you knowing.
Hope this helps.

I finally found the solution for it (a quite simple one).
I have to alloc and init the navItem through its property then it is being retained:
self.navItem = [[NavigationBarTitleItemViewController alloc] initWithNibName:#"NavigationBarTitleItem" bundle:nil];

Related

Trigger a seque from the AppDelegate to popup a view in the StoryBoard

I am trying to convert my App to a Storyboard, but am having some problems.
In de previous model I could have an 'actionClass' in my AppDelegate which I called when I needed to pop-up a view.
E.g.
DOArticleViewController *articleView = [[DOArticleViewController alloc] initWithArticle:article notification: notification nibName:#"DOArticleViewController" bundle:nil];
[[self navigationController] pushViewController:articleView animated:YES];
But now with the storyboard it does not work anymore.
Then I tried the following code in the AppDelegate:
id currentController = [[[[self window] rootViewController] navigationController] visibleViewController];
[currentController performSegueWithIdentifier:#"settingsSeque" sender:nil];
Don;t think this is the best anyway, as only the rootViewController has all the seques needed, and might not be the visibleViewController, but one step at a time.
With this the only thing I see happening is the message and no action:
Unbalanced calls to begin/end appearance transitions for UINavigationController: 0xb428e00.
I spend a view hours now on trying to figure out to get this to work, but am realising that it might be better to go back to the traditional independent XIB files....
I solved with the answer given in this question: ios: Accessing a navigation controller from app delegate
I tried to get the Navigation Controller, but this was nil and I didn't realise it.
My navigation controller was the rootViewController, so that is also the NavigationController.
Casting the rootViewController to NavigationController and invoking 'visibleViewController' worked fine after that!

viewController nib's view is nil?

I used to create viewController/views programmatically only.
Using xib is harder than I expected.
I found that self.view of an xib or any other subview is initially nil.
I created the viewController by [[MyViewController alloc] init]
and tried to [myViewController.imageView setImage: image]; //imageView is nil
NSLog(#"%p", myViewController.view); // access view here
[myViewController.imageView setImage: image]; //works now
I feel I'm missing something very basic. What would it be?
As described in the documentation for UIViewController, the views are lazily loaded when you first access the view property. The earliest you can reference them is in viewDidLoad.
In my experience this happens when I try to access the view before viewDidLoad fires, for instance trying to access this in the init method of the view controller. If this is the case, wait until after viewDidLoad fires to access the view.
Try
[[MyViewController alloc] initWithNibname:#"mynibname" bundle:nil]
in this format to allocate the proper view ....try it

Set the Delegate on SubView

I have a splitViewController that has a master and detail view controllers. The code below is from the master and it creates the new view in the detail:
UIViewController *controller = [self.storyboard instantiateViewControllerWithIdentifier:[NSString stringWithFormat:#"%#",[self.defaultSettingsMenuItems objectAtIndex:indexPath.row]]];
[self.detailViewController.view addSubview:controller.view];
detailViewController is a global instance of DetailViewController. In the detailViewController, I have many textFields and need to utilize the UITExtFieldDelegate. However, I think that the detailViewController isn't self at that point, and that's why I'm getting EXC_BAD_ACCESS errors on using the TextFieldDelegate methods in detailViewController.
EDIT: I have now found that the subView delegate methods only work for the viewController I setup as the rootViewCOntroller relationship from within Storyboard. Ex. If I have 6 views in the default menu settings above, whichever one I have setup as the first and root view in storyboard will work correctly. Any and all other subviews shown (from making a new selection in the master view) will not work properly. I think this will help diagnose the problem.
I am not familiar with storyboards, but I don't see you setting the detailViewController's delegate anywhere. You probably need to have something like self.detailViewController.delegate = self; somewhere before you yield control over to the subview.
This all I needed, the second line:
UIViewController *viewController= [self.detailViewController.storyboard instantiateViewControllerWithIdentifier:[NSString stringWithFormat:#"%#",[self.defaultSettingsMenuItems objectAtIndex:indexPath.row]]];
if (self.detailViewController.childViewControllers.count >= 1) {
NSLog(#"childViewControllers: %#",self.detailViewController.childViewControllers);
[[self.detailViewController.childViewControllers objectAtIndex:0] removeFromParentViewController];
}
[self.detailViewController addChildViewController:viewController];
[self.detailViewController.view addSubview:viewController.view];
EDIT: I've updated my answer with the if look to remove viewControllers from the stack. Slightly hacky, but functional.

Crash when instantiating ViewController from Storyboard

I have got a single view in my storyboard, which I add to my current view by doing the following :
MainViewController *mvc = [self.storyboard instantiateViewControllerWithIdentifier:#"MainController"];
[self.view addSubview:mvc.view];
The view appears, but everything I do after it appears, leads to a crash. What am I doing wrong ?
Here is an example when it crashes:
-(IBAction)showUsername:(id)sender{
[testLabel setText:#"username"];
}
Everything is hooked up in storyboard as well, so falsely linked connections should not cause the problem.
You instantiate a new view controller:
MainViewController *mvc = [self.storyboard instantiateViewControllerWithIdentifier:#"MainController"];
But you do not retain it. Your view hierarchy is, as soon you added it to another view.
[self.view addSubview:mvc.view];
So when a button is clicked, a message is sent to you IBAction, but your view controller has been released already. To prevent this from happening, retain your mvc variable, for example somewhere in a property.
#property(nonatomic, strong) MainViewController *controller;
self.controller = mvc;
I can think all reason before you show log...
Turn NSZombie on in the Product>>Edit Scheme you should get more descriptive Error showing then. Then you can add it.
Make sure your method is declared and implemented correctly. Also make sure you have IBOutlet UILabel * testLabel in your .h. The only other problem I can think of other than that is how you hooked it up. Does it only crash when you press the button?
This line is wrong this will be why you are getting the error.
MainViewController *mvc = [self.storyboard instantiateViewControllerWithIdentifier:#"MainController"];
[self.view addSubview:mvc.view];
replace it with this
MainViewController *mvc = [self.storyboard instantiateViewControllerWithIdentifier:#"MainController"];
[self presentModalViewController:mvc animated:YES];
In storyboards you are not adding a subview you are doing one of three things presenting a modal, pushing it on to the navigation controller stack or making a custom one of these.

UINavigationController and viewWillDisappear

So I have a UINavController in my app and am trying to execute a method when the user presses the back button. I have searched everywhere and can only find bits and pieces that don't really make sense out of context.
Is there a way to implement some sort of check that catches when the user presses the back button to dismiss the current view? (the viewWillDisappear method for the view being popped never gets called for some reason. I did read that it doesn't unless you forward that call?) Does that sound right, and does anyone have any ideas or suggestions? Thanks in advance.
Take a look at the UINavigationControllerDelegate. There are the only two methods that get called when a UIViewController is pushed to the navigation controller stack. Similarly, if one is being pushed then something probably was just popped. This is what I did to call viewDidDisappear and viewWillDisappear.
# pragma mark - UINavigationControllerDelegate Methods
- (void)navigationController:(UINavigationController *)navigationController
willShowViewController:(UIViewController *)viewController animated:(BOOL)animated
{
static UIViewController *vcPointer = nil;
// If the previous view controller is still around then let's send a viewWillDisappear message
if (vcPointer != nil) {
if ([vcPointer respondsToSelector:#selector(viewWillDisappear:)]) {
[vcPointer viewWillDisappear:animated];
}
}
// Keep track of a pointer to the current viewController
vcPointer = viewController;
[viewController viewWillAppear:animated];
}
This code keeps a pointer reference to the last view controller that was pushed so that once we push another one we can pop the last one (if it still exists).
AFAIK, if you add a UINavigationController to a UIView via code, it won't send those messages to it's subviews by default. It will only do this if the UINavigationController received these calls itself. Maybe this is your problem (I don't know your view setup).
So, when adding the view of the UINavigationController, be sure to manually send it these messages.
UINavigationController *navigationController = [UINavigationController alloc] initWithRootViewController:rootViewController];
[navigationController viewWillAppear:NO];
[aView addSubview:navigationController.view];
[navigationController viewDidAppear:NO];
At least, this is what I found during development. Been searching for this for a long time and I still don't understand the rationale behind it.
You can always hide the default back navigation button and create your own with its own method to be called when pressed.
Execute whatever code you want there then pop the view.
I used this solution:
Add a custom button on the left side in the navigation bar
Let that button activate a custom method.
Disadvantage of this workaround: you will lose that nice arrow shaped "back" button. That can be solved as well with a custom image.
So here is my code.
Put this in your viewDidLoad:
// LeftButton in Navigation Bar
UIBarButtonItem *leftBarButton = [[UIBarButtonItem alloc] initWithTitle:#"Back" style:UIBarButtonItemStylePlain target:self action:#selector(backButtonPushed:)];
self.navigationItem.leftBarButtonItem = leftBarButton;
[leftBarButton release];
Then add this method in the same .m file:
- (void) backButtonPushed: (id)sender {
// do what you want to do
}
dont forget in the .h file
- (void) backButtonPushed: (id)sender;
The viewWillDisappear & viewDidDisappear is called when a controller is popped or dismissed. The function is called on the fore-front view controller not on the UINavigationController itself. Did you possibly subclass and forget to call the super on something?