Dismissing the last tab ViewController - ios

I have a UITabBarController to which I add and remove UIViews without any issue in the following way:
Adding
FileTabViewController* newTab = [[FileTabViewController alloc] initWithNibName:#"File" bundle:nil];
NSMutableArray* array = [NSMutableArray arrayWithArray:bar.viewControllers];
[array addObject:newTab];
[bar setViewControllers:array animated:YES];
[bar setSelectedIndex:bar.viewControllers.count-1];
Removing (within FileTabViewConroller.m)
NSMutableArray* newArray = [NSMutableArray arrayWithArray:self.tabBarController.viewControllers];
[newArray removeObject:self];
[self.tabBarController setViewControllers:newArray animated:YES];
This code does exactly as I'd expect but there's on anomaly which I cannot explain. When the app launches and the view controller array is empty (nil) then there are (as you'd expect) no views shown in the tab view controller. As I add views the tabs increase correctly, but when I remove the very last one (i.e. index 0 of count 1) then the view appears to stay loaded, the buttons and labels on the final tab remaining visible - there seems to be no way to remove everything back to the 'nil' state the app launches with - despite the fact that there aren't actually any tabs shown on the controller.
Launch (as you'd expect)
Opening and closing a tab (this is where I get confused) - you see the tab disappears but the view remains open...?
Thanks in advance for any input!

I made a little test app, and I see the same behavior you're seeing. I don't know whether this is an unintended consequence of the way a tab bar controller removes controllers and views, or whether Apple intended for this to happen -- after all, why would you want a black screen? Something seems to be keeping a strong pointer to that last view controller, because if I have a button that removes the controller (and it's the last one), I can still click that button and get the action to happen. The first time I click it, the tab bar controller still lists that controller as the selected controller, but if I click again, the selected controller is nil, but my view controller still lives.
The solution to getting rid of the view is easy, just remove the view from its superview after you remove the last controller from the array.
[self.view removeFromSuperview];
However, this doesn't get rid of the view controller. Something is still holding a reference to it, and so far, I haven't found out what is doing that.

I haven't tested this hypothesis, but I wonder if the `moreNavigationController is coming into play here. Check its value before adding any tabs, and again after removing the last tab. Also take a look at the visibleViewController property of both the moreNavigationController and the UITabBarController.

Related

Move to start of storyboard from button on last screen

I'm trying to get a storyboard to bring the user back to the first screen of a storyboard when a button is pressed.
I have the button click hooked up and the method is hit when it's tapped but it's either doing nothing or crashing the app depending on what I've got in that method (I've tried so many things at this stage that I can't remember the original setup)
The best I can achieve is that once tapped the user gets brought to the third screen in the storyboard, rather than the first.
Here's the storyboard, I want to get the user to move from the button circled in red back to the very first view controller (top left of screenshot).
Maybe the storyboard layout will help people, I'll post some of the various methods I've tried as well.
Here's the method
- (IBAction)done:(id)sender
{
//Multitude of attempts have been in here, either they don't do
//anything or they just send the user back to the NEW REPORT screen.
//Button does nothing in these following events
[self.navigationController popToRootViewControllerAnimated:YES];
[self.navigationController popToViewController:[self.navigationController.viewControllers objectAtIndex:0] animated:YES];
}
If you want to start fresh, have the button instantiate a new instance of the first controller, and set it as the window's root view controller:
FirstViewController *first = [self.storyboard instantiateViewControllerWithIdentifier:#"First"];
self.view.window.rootViewController = first;
It's not clear to me exactly how you're getting to that controller with the button in question, but you might want to put dealloc methods in all your controllers with a log to see if all are getting deallocated at some point in your navigation, and when you go back to the new first controller as I've outlined above.

Modify (Storyboard)TabBarController tabs before the first tab loads

So my app decides at runtime which TabBarItems are available.
Problem is, [self.tabBarController viewControllers] is empty in the actual TabBarController class (in viewDidLoad and viewWillAppear that is). It is filled after the first ViewController (tab) did load, but that is too late as i dont even know if i want to load this particular one.
I know this can be solved by dumping storyboard, is it possible relying on it too?
Best solution that comes to mind so far is creating a dummy ViewController that gets swapped out immediately after loading is done...
It's empty because the tab bar controller doesn't have a tabBarController property. It should just be self.viewControllers.

pushing a view into stack below current root view

I'm trying to implement a calendar like interface, with left and right arrows that allow users to scroll back in time/forward in time. To do this, I'm using a navigation controller, and pushing/popping views on the stack.
However, what if I'm currently viewing the root view, and I can't pop the view in order to get the correct animation direction if I'm trying to navigate back in time?
I found a post on Stack Overflow last week that demonstrated a method where the new view would be pushed onto the stack below the current root view, allowing the root view to be popped off. It was just a couple lines of code -- primarily getting an array of the current items in the stack and somehow pushing the new view below the currently active view. Unfortunately, I can't seem to find that particular post any longer...
Has anyone happened across the same link, or might be able to point me in the right direction? I had this working correctly earlier, and due to a computer malfunction lost a bit of my work.
EDIT:
With some experimenting, I have this partially working...
// create new view controller
ViewController *viewController = [[ViewController alloc] initWithNibName:#"ViewController" bundle:nil];
// add below root
NSMutableArray *allViews = [[NSMutableArray alloc] initWithArray:self.navigationController.viewControllers];
[allViews insertObject:viewController atIndex:0];
[viewController release];
self.navigationController.viewControllers = allViews;
[[self navigationController] popViewControllerAnimated:YES];
[allViews release];
It does seem to be leaking memory, however -- if I do an NSLog of the allViews array, every time I go forward in time and then back to the previous view it seems to add an extra view to the array that doesn't get taken off later. This will work for now, but hopefully I can get this issue fixed.
UINavigationController Class Reference
I think that you are talking about the #property(nonatomic, copy) NSArray *viewControllers
Quoting the reference :
Discussion
The root view controller is at index 0 in the array, the back view controller is at index n-2, and the top controller is at index n-1, where n is the number of items in the array.
Assigning a new array of view controllers to this property is equivalent to calling the setViewControllers:animated: method with the animated parameter set to NO.
Is that the information you are looking for?
For what you are trying to do you might consider something in UIView instead.
transitionFromView:toView:duration:options:completion:
Creates a transition animation between the specified views using the given parameters.

iOS: Setting text in nib subview from view in UITabBar/UINavigationController application

I'm having a problem getting a UISearchDisplay's text value to be set programatically on load of the view by another view and my question is have I overcomplicated my problem and missed something or am I on the right track of thinking.
Here's the situation: I have a UITabBarController as my root view, there are 2 tabs, both have a UINavigationController setup so I can push views as needed.
Tab 1 has a UITableViewController which is populated with a list of categories.
Tab 2 has a MapView in it's main view but I have done a custom UINavigationItem view to put various buttons and a UISearchDisplay on the rightBarButtonitem area.
The mapview layout and custom navigation item are stored in the same nib as two separate view objects. In Tab 2's viewDidLoad(), I initialise the rightBarButtonItem programatically with:
UIBarButtonItem *btnItem = [[UIBarButtonItem alloc] initWithCustomView:buttonBar];
self.navigationItem.rightBarButtonItem = btnItem;
[btnItem release];
Everything fires up, buttonBar is wired up to an IBOutlet as searchWhat and I can talk to this object from within the mapview's controller class.
If the user is in Tab 1 and taps a cell, I want it to switch to Tab 2 and populate the searchWhat.text and then execute the search code as if someone had typed in the search themselves.
What i'm having trouble with is the order of load and populate on a view.
I can access the 2nd tab from the 1st without any problem and get it to appear with something like:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
NSLog(#"Quick Category cell tapped at row %d", indexPath.row);
self.tabBarController.selectedIndex = 1; // change to the search view controller
//[self.tabBarController.selectedViewController viewDidAppear:YES];
UINavigationController *nav = (UINavigationController *)self.tabBarController.selectedViewController;
SearchViewController *srch = [nav.viewControllers objectAtIndex:0];
//NSLog(#"%#", [srch description]);
[srch queueSearchByType:kSearchTypeQuickCategories withData:[catList objectAtIndex:indexPath.row]];
[srch viewDidAppear:YES];
}
Don't worry about catList and SearchViewController, they exist and this bit works to switch tabs.
Here's the problem though, if the user starts the application and selects an item in tab 1, tab 2 appears but the values of the search display text don't get set - because viewDidLoad and viewDidAppear are called in another thread so the execution of queueSearchByType:withData: gets called while the view is still loading and setting up.
If the user selects tab 2 (therefore initialising the subview) and then selects tab 1 and an item, it can populate the search display text.
I can't just change the order of the tabs so that tab2 is first and therefore loads it's subviews to the navigation bar as the project specification is category search first.
Have I missed something very simple? What I need to do is wait for the second tab to fully appear before calling queueSearchByType:withData: - is there a way to do this?
At the moment, i've implemented a queue the search, check for a queue search approach, this seems to be a bit long winded.
Ok, I don't like answering my own question but it appears my fears were right, basically if you want a UINavigationItem that is a custom view (ie, to put a search bar and various other buttons up on the nav controller) and be able to switch to and populate them from another tab on a tab bar controller, then you need to put the subview in it's own class which is a subclass of UIViewController and then make delegation your friend (which it already is), i've provided an example in case anybody needs to repeat it which i've put on my blog HERE.
http://www.jamesrbrindle.com/developer/ios-developer/howto-add-a-custom-uinavigationitem-to-a-uinavigationcontroller-with-delegation.htm
If anyone disagrees and thinks this can be simpler, please let me know or rate this post

Automatically drill down through navigation hierarchy?

I have a navigation-based app that allows the user to drill down through hierarchies. Some of the child hierarchies have only one element in them, e.g.,
TopLevel1----->Level2a
TopLevel2 |->Level2b----->Level3a----->Level4a
|->Level2c
Instead of making the user tap 'Level3a', I just want to jump from Level2b to Level4a, but keep the Level3a view in the stack so when the user backtracks, it is visible.
I found some code here to simulate a row tap:
Simulate a Detail Disclosure Button press
When each level is loaded, I check to see if there is only one element in it. If so, I simulate the row tap. This all works initially, and the final view is loaded. But when I start backtracking through the view hierarchy, I get problems (it appears that the skipped views aren't loaded).
I think what I'm trying to accomplish is fairly simple, so I'm hoping someone on here can point me in the right direction.
You should be able to place a [self.navigationController pushViewController:level4 animated:NO] call in the viewWillAppear method for your level3 view controller. This will automatically push level4 on top of level3.
If it only happens some of the time, level3 can have a property to indicate when this behavior takes place.
I'm not 100% sure that would work, but that's what I would do.
You could directly [self.navigationController pushViewController:level4a animated:NO] and when that's done, set a new array of viewControllers, the navigationController propriety (an array that includes Level3a).
Here is a code sample, in you didSelectRowAtIndexPath:
[self.navigationController pushViewController:level4a animated:NO]; //Push the level 4 first
NSMutableArray* mutableViewControllers = [self.navigationController.viewControllers mutableCopy];
[mutableViewController addObject:level3a atIndex:3]; //Add the level 3 manually
self.navigationController.viewControllers = mutableViewControllers;
[mutableViewControllers release];

Resources