UINavigationController visibleViewControllers - iphone

I have a UITabBarController, and one tab is a UINavigationController. I have a search bar that goes to a certain view within the UINavigationController. The problem is that if the first view is not pushed by the UINavigationController, than it crashes because my search doesn't recognize the visibleViewController from this call:
UINavigationController *navController = [self.MainTab.viewControllers objectAtIndex:1];
FirstViewController *fVC = [navController visibleViewController];
What I don't understand is, before this code, I do this:
self.MainTab.selectedIndex = 1;
This code on its own selects the viewController in that tab, where then the view gets loaded to my knowledge. So shouldn't this be enough for the [navController visibleViewController] to get the current viewController? Thanks.

Try topViewController instead of visibleViewController.
FirstViewController *fVC = [navController topViewController];

From what you explain in your question and comments, I understand that your code tries to access an object of type FirstViewController, supposedly the first view to be pushed on to your UINavigationController, when it has not yet been created.
On the other hand, if you first programmatically select the tab, the view is created and everything works fine. Indeed, that view is created in a viewDidLoad method that is run when the tab is selected.
The solution I would suggest is avoiding accessing the UINavigationController visibleViewController directly from your search tab; instead, let your search code access the model (as in Model-View-Controller) for your app and store there the result; then, from the mentioned viewDidLoad method again access the model to read the search result and update/show the UI.
This is the clean solution, IMO. If you want a sort of workaround to your current design, then check the fVC value you get back from visibleViewController and if it is not what expected, then instantiate the view properly.
I hope this helps.

I know this has been answered, but I found another solution that might be helpful. In my case I was handling rotation differently for some viewControllers within my NavigationController, I did the following:
Subclass UINavigationController, then where needed in your new subclass you can access the current visibleViewController's title like so:
- (BOOL)shouldAutorotate
{
if ([[self visibleViewController].title isEqualToString:#"Special Case"]) {
return NO;
}
return YES;
}
This is not specific to rotation, this is just what I used it for. The only thing you have to do is set your self.title for each of the viewControllers you are checking against in their viewDidLoad, if they are set in IB or are not set they will be nil.

Related

viewwillappear and viewdidappear not called

this question is very frequent, but I am not able to solve it with any answers available.
I am working on iOS 5.1. My navigation controller is one tab amongst tab bar view controllers. There's a tableview, in which selecting of a row pushes new view controllers.
This problem occurs Only on selecting of the second row and only sometimes. It's not regular.
The Pushed view comes blank - viewWillAppear/viewDidAppear are not being called. On clicking the back button of the navigation bar - the root view's viewWillAppear/viewDidAppear are also not being called, making it blank.
I am pushing the view on select of first row/second row in exactly the same way. But the problem occurs only on the second row.
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
switch (indexPath.row) {
case 0:
AViewController *aObj = [[AViewController alloc] init];
aObj.homeObj = self;
[self.navigationController pushViewController:aObj animated:YES];
[aObj release];
break;
case 1:
BViewController *bVCObj = [[BViewController alloc] init];
bVCObj.homeObj = self;
[self.navigationController pushViewController:bVCObj animated:YES];
[bVCObj release];
break;
default:
break;
}
[tableView deselectRowAtIndexPath:indexPath animated:YES];
}
I have tried this and this but in vain.
viewDidLoad is being called on pushing the BViewController, However, viewWillAppear and viewDidAppear is not being called. Following is my viewDidLoad:
- (void)viewDidLoad
{
[super viewDidLoad];
self.view.backgroundColor = [UIColor blackColor];
NSLog(#"nav stack: %#", [self.navigationController viewControllers]);
NSLog(#"nav stack: %#", [[self.navigationController visibleViewController] description]);
//some initialization and call of methods
}
It's not regular. Sometimes I get this scenario, and this continues until I close the app from the background and restart it. But sometimes it works just fine. I am just pushing my view controller to the nab stack.
As I mentioned in the comment, It's a regular navigation controller in tab bar controller.
How are you defining your views for AViewController and BViewController? Generally you'd use initWithNibName, e.g.
AViewController *aObj = [[AViewController alloc] initWithNibName:#"mynibname" bundle:nil]`
As Carl pointed out, you can apparently use just init (though I don't see this documented in the UIViewController Class Reference), but then the system will be very particular about the name of your NIB file. The documentation does say, though, that you can use initWithNibName and pass a nil for the NIB name, in which case it will try to find it for you. Personally, if you're having inconsistent results, though, I'd try using initWithNibName and explicitly pass the name of your NIB, and see if that rectifies the situation.
Or are you building your view programmatically with loadView in your two controllers? Then you need to show us those loadView routines (not to be confused with viewDidLoad).
But according to the documentation, you need to either specify your NIB or use loadView. See the View Management discussion in the UIViewController Class Reference.
Update:
Given your feedback, I have a couple of thoughts:
Needless to say, the problem is apparently not related to the above code. You need to broaden you search and show us more code. Perhaps show us your viewDidLoad of B?
Generally when you don't get these sorts of events, it's because the view controller hierarchy has gotten out of sync with the view hierarchy. The most common way that people do this is if they've done something like "[addSubview someNewController.view]" at some point. If you're using a view controller in any context either than (a) your app delegate's initial configuration; (b) presentViewController (or dismiss); or (c) pushViewController (or pop), then you might want to share what you've done.
As andreamazz pointed out, your comment, "My navigation controller is inside a view controller of the tab bar controller," is a little disturbing if one reads it literally. You can put navigation bar in a view controller's view, but you can't put a navigation controller in a view controller (unless you're doing view controller containment, which is a whole different beast). Equally concerning is where, in another one of your questions, you said, "Embedding a UINavigationController or UITabBarController (my case) in a UIViewController somehow interrupts with the calling of these methods." Thing is, you don't embed nav controllers in other view controllers (unless it is, itself, a container controller such as a tab view controller), but rather its the other way around. But if you literally mean that you have a controller that contains a nav controller, you have to show us how you're doing that (proper view controller containment?) because that's highly unusual.
It's unusual, but I've had projects get corrupted, ending up in weird states. At a minimum, I might suggest "Product" - "Clean" and rebuild. If problem persists, and you've isolated the problem to to B's NIB, then temporarily rename the it and build a quick and dirty one from scratch.

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.

Check nibName of the previous UIViewController

I have a navigation based app. In a certain screen, I need to check from which screen the user came. I thought about something like
NSArray *viewControllers = [self.navigationController viewControllers];
int viewControllersSize = [viewControllers count];
if ([[viewControllers objectAtIndex:viewControllersSize-2] nibName] == #"Name") {
...
}
But the problem is that if the user clicks "back" from a certain screen, the view controller will be removed from the array defined above.
My current solution is having a global variable that tells me if the user came from a specific screen, but I suppose there is a more elegant solution, right?
Not sure what you meant with:
But the problem is that if the user
clicks "back" from a certain screen,
the view controller will be removed
from the array defined above.
Not all view controllers use same navigation controller?
If same UINavigationController is used for all UIViewControllers, you can use UIViewController parentViewController for this purpose. If going the opposite way, keeping reference to view controller you came from or maybe just [viewController class] (to string) would do the trick.

Navigation & View Controller questions

I'm experimenting with ViewControllers & NavigationControllers in Interface Builder trying to get a better grasp of what's tied to what and why... I'm struggling with a scenario that has confused me. Hopefully someone can set me straight...
Say I start with your typical iPhone template View-Based Application and I display a view which is handled by view controller (viewController). Then after a certain event I'd like to replace that view with a "typical" Navigation-Based View (rootVC). I'd like to create as much as possible in IB. My questions have to do with how to show rootVC and remove all traces of the previous viewController as user will never need to return and where/how to wire in the navController in IB. Currently when it's time to show the rootVC I do the following in my viewController:
RootVC *rvc = [[RootVC alloc] initWithNibName:#"RootVC" bundle:nil];
[rvc.view setFrame:[[UIScreen mainScreen] applicationFrame]];
ViewTestAppDelegate *appDelegate = (ViewTestAppDelegate *)[[UIApplication sharedApplication] delegate];
self.rootVC = rvc;
[rvc release];
[appDelegate.viewController.view removeFromSuperview];
[appDelegate.window addSubview:rootVC.view];
[appDelegate.viewController release];
rootVC displays except viewController still has a retain count of 1?!?
Also, where should rootVC's navigationController be instantiated? Having started with the View-Based template the MainWindow.xib contains an object for the viewController (which has its own ViewController.xib) an appDelegate and a UIWindow. My RootVC.xib contains a UITableView. Do I need yet another intermediary view controller that will have another ApplicationDelegate object that I wire up to a UIWindow object and a UINavigationController? The View Controller that comes along with IB's Navigation Controller object would then be set to my RootVC class?
Sorry for the verbosity. It's difficult for me to explain. Because some objects in IB are proxies and some are "real" it's sometimes confusing (to me) when trying "new" things out what's required, where & when. Basically I want to know to go about setting up one view leading to another with no way back to first view. 2nd view basically becomes the "main" root spawning off in many directions...
I would recommend using the navigation-based iPhone application template and presenting your one-time view as a modal view on top of the root view.
I was able to figure it out by putting a reference to the viewController in the MainWindow nib and then autoreleasing the viewController after I added the navigationController & rootVC to the UIWindow. Learned another thing or two about IB along the way. Pretty powerful...

Is parentViewController always a Navigation controller?

I was kind of scratching my head at this a week ago, and now with a little bit more Cocoa experience under my belt I feel like I have an inkling as to what might be going on.
I'm making an application that is driven by a UINavigationController. In the AppDelegate, I create an instance of this class, using "page 1" as the Root View Controller.
UINavigationController *aNavigationController = [[UINavigationController alloc]
initWithRootViewController:page1ViewController];
Now here's where I'm having the problem. From "page 1" I'd like to use a modal view controller that slides over the interface and then disappears once the user has made an edit. I do that using code like this, inside of Page1ViewController:
[self presentModalViewController:myModalViewController animated:YES];
When the Modal View Controller is gone, I want a value on "Page 1" to change based on what the user entered in the Modal View Controller. So, I wrote some code like this, which resides in the Modal View Controller:
[self.parentViewController dismissModalViewControllerAnimated:YES];
[self.parentViewController doSomethingPleaseWithSomeData:someData];
The update to page 1 wasn't happening, and it took me a long time to realize that the "doSomethingPleaseWithSomeData" message was not being sent to Page1ViewController, but the Navigation Controller.
Is this always to be expected when using Navigation Controllers? Did I perhaps configure something improperly? Is there an easy way to get at the View Controller that I want (in this case, Page1ViewController).
I would recommend using the delegation pattern to solve your problem. Create a property
#property (nonatomic, assign) id <MyModalViewDelegate> delegate;
And a corresponding protocol
#protocol MyModalViewDelegate
#optional
- (void)myModalViewControllerDidFinish:(MyModalViewController *)aModalViewController;
#end
When the user finishes with your view (e.g. taps the save button), send this message:
if ([self.delegate respondsToSelector:#selector(myModalViewControllerDidFinish:)])
[self.delegate myModalViewControllerDidFinish:self];
Now, set the delegate to the view controller that should manage the whole thing, and it will be notified when the view controller is finished. Note that you'll need your view controller to dismiss the modal view controller. But, logically, that makes sense, since it was the object that presented the modal view controller in the first place.
This is how Apple solves this problem in, for example, the UIImagePickerController and UIPersonPickerController.
There are a couple of ways you can handle this. The simplest is probably just to add a UIViewController property into myModalViewController and set it to page1Controller before you present it:
myModalViewController.logicalParent = self; //page1Controller
[self presentModalViewController:myModalViewController animated:YES];
Just make sure you add the appropriate instance variable #property, and #synthesize for logicalParent to myModalViewController, then you will have a way to communicate data back to the ViewController that triggered the modal dialog. This is also for passing data back and forth between different levels of navigation before you push and pop them on the stack.
The one important thing to worry about when doing this is that it is easy to get retain loops if you are not careful. Depending on exactly how you structure this you might need to use assign properties.
I just ran into this same problem. It definitely seems that if you put a UIViewController embedded in a NavigationController, then when, from that UIViewController you present another UIViewController modally, the presentee thinks that the presenter is the NavigationController. In other words, parentViewController is incorrect.
I bet this is a bug: either that, or the documentation seems incomplete. I will inquire.
Just ran into the same problem. I believe this is a bug. My scenario is the following:
A navigation hierarchy with A, B and C view controllers in this order. On C there's a button that would open a modal view controller called D. Once D is presented the navigation controller drops C from its hierarchy which is a terrible behavior. Once D gets dismissed, the navigation controller instantiates a new C type view controller and pushes it into its hierarchy to recover the original one. Terrible. My solution is hacking the navigation hierarchy this way (a very bad solution but works well. with a 2 dimension array you could implement stacking modals):
- (void)presentModalViewController:(UIViewController *)c {
[self.navigationHierarchy removeAllObjects];
[self.navigationHierarchy addObjectsFromArray:[navigation viewControllers]];
[navigation setViewControllers:[NSArray array] animated:YES];
[navigation presentModalViewController:c animated:YES];
}
- (void)dismissModalViewController {
[navigation dismissModalViewControllerAnimated:YES];
[navigation setViewControllers:[NSArray arrayWithArray:self.navigationHierarchy] animated:YES];
}
These two methods are defined where I maintain the main navigation hiererchy: the app delegate. navigation and navigationhierarchy are defined this way:
NSMutableArray *navigationHierarchy;
UINavigationController *navigation;