how to change UIViewController in a Tab of TabBarController - iphone

I have an iPhone app with TabBar and tabs. Each tab is loaded with UIViewControllers, what I want for a particular tab is to change UIViewController associated with tab. When I call PresentViewController it changes UIViewController but also hides the TabBar which i dont want.
Can anybody please explain what needs to be done ?
Thanks

UITabBarController keeps a collection of it's view controllers in a property aptly named viewControllers. You can modify this at runtime. There are side effects that probably are fine for your app, but read the docs to be sure.
A convenience method (and illustration of how to modify that immutable array) would look like this:
- (void)replaceTabBarViewControllerAtIndex:(NSUInteger)index with:(UIViewController *)newVC {
NSMutableArray *newVCs = [NSMutableArray arrayWithArray:self.tabBarController.viewControllers];
if (index < newVCs.count) {
newVCs[index] = newVC;
self.tabBarController.viewControllers = [NSArray arrayWithArray:newVCs];
}
}
Call this with the new vc instead of presenting it.

Related

Disable/hide uitabbaritem on an uitabbar

I found this code here that i think will do the job.
`/* suppose we have a UITabBar *myBar, and an int index idx */
NSMutableArray modifyMe = [[myBar items] mutableCopy];
[modifyMe removeObjectAtIndex:idx];
NSArray *newItems = [[NSArray alloc] initWithArray:modifyMe];
[myBar setItems:newItems animated:true];`
The question is, where should i write this code in order to work?
I tried putting it in viewDidLoad of the UITabBarController but it didn't work.
If you're modifying the tabbar items from the UITabbarController, you can't use setItems:animated:. From the docs:
In iOS 3.0 and later, you should not attempt to use the methods and properties of this class to modify the tab bar when it is associated with a tab bar controller object. Modifying the tab bar in this way results in the throwing of an exception. Instead, any modifications to the tab bar or its items must occur through the tab bar controller interface.
Instead, swap out the viewControllers property of your UITabbarController, removing the UIViewController that corresponds to the tabbar item you want removed. For example, if you want to remove the 2nd tabbar item:
NSMutableArray *newViewControllers = [NSMutableArray arrayWithArray:self.viewControllers];
[newViewControllers removeObjectAtIndex:1];
[self setViewControllers:newViewControllers];

Reference view to app delegate using storyboards

I am trying to create a UITableView using storyboard but I came to something that at the end may be easy but I have no idea how to solve it.
First of all let me point out that I know that one of the limitations of storyboards is that you will have to dig through the storyboard to find information about a view you have and link it to the app delegate.
I have create my mutable array and the information that I will use in the table in the app delegate and now I want to reference that UITableView to the app delegate. The hierarchy goes like that
First I have the root view that once you click on a button it will redirect you to the second view
Inside the second view there is another button that once you press it it will redirect you to the UINavigationController
The UINavigationController contains the UITableView.
Therefore as you can see there are two views before the navigation control and the UITableView.
Here is the code I am trying to use but it does not work
UIViewController *viewController = (UIViewController *)self.window.rootviewController;
// The next line refers to the second view but does not work at all
UIViewController *secondView = [[UIViewController viewController] objectAtIndex:1];
//Then the following line is to redirect from the second view to the navigation controller
UINavigationController *navigationController =[[secondView viewController] objectAtIndex:0];
//Then is the table view
BuildingsViewController *buildingsViewController = [[navigationController viewControllers] objectAtIndex:0];
The above code does not work. Can anyone please help me?
Thanks a lot
If this code is in the app delegate there are a variety of reasons why it will probably not work. Firstly you appear to be mixing up View's, ViewControllers and Navigation controllers with what you are trying to do. Secondly there is no guarantee at the time you are trying to do this that all of the views/viewcontrollers have yet been created yet or are joined in the way they will be when the final building view controller is rendered.
What you could try instead is in your BuildingsViewController (which you say is your table view controller) you can get a handle to the App Delegate by using
MyAppDelegate *myAppDelegate = (MyAppDelegate *)[UIApplication sharedApplication].delegate
Once you have a handle to the delegate you can simply reference your mutable array structure etc. that you created on it from within your BuildingsViewController.
e.g. in the 'numberOfRowsInSection' method:
MyAppDelegate *myAppDelegate = (MyAppDelegate *)[UIApplication sharedApplication].delegate
NSMutableArray *myBuildings = myAppDelegate.buildingArray;
return [myBuildings count];
Or in the cellForRowAtIndexPath method:
// something like this but using your names for app delegate, building array and the accessor for the building name
MyAppDelegate *myAppDelegate = (MyAppDelegate *)[UIApplication sharedApplication].delegate
NSMutableArray *myBuildings = myAppDelegate.buildingArray;
cell.textLabel.text = [myBuildings objectAtIndex indexPath.row].theBuildingName;

Prevent viewcontroller being reset - UINavcontroller + Storyboard + Segue's

I need some help in picking the 'right' solution for the following 'problem'.
I use the new storyboard feature to link all the screens of my application together. Basically the structure drills down to:
[Navigation Controller] => [View Controller #1] => [Tabbar Controller] => [View Controller #2]*
**(and some other tabs which are for now not important)*
I have attached a segue (push) from the first View Controller (#1) to the View Controller behind the Tab Bar Controller. This push is triggered when the users presses something on the first controller and works fine.
// Execute preset segue
[self performSegueWithIdentifier:#"segueEventDetail" sender:self];
When the user (which is now in the View Controller #2) presses the back button in the navbar the user goes back. Suppose he now triggers the segue again, the second view controller is shown again but is now 'resetted' (empty). (I believe after reading several fora and articles this is standard behavior when using segue's because these destroy and reinitiliaze the view controller's every time?)
This (the view controller being resetted) poses a problem because the contents of the second view controller is dynamic (depend on a JSON response from the server) and thus it is 'needed' that the view controller remains intact (or is restored) when the user comes back.
I have found several sources (see bottom) describing the same issue, but the solutions vary and I need some help picking the right one.
Summarize:
How can I 'retain'/save the state of a View Controller when the users presses back, while preserving the use of Storyboard & preferably also Segue's
Own Thoughts:
#1 I'm now thinking of caching the JSON Response to my singleton class (and from there to a PLIST) and checking within the second view controller if this data is present and than rebuild the view after which I check for any new data (resume normal operation).
#2 Another one I'm thinking of is 'bypassing' the segue and manually handle the switch of views , partially explained in (Storyboard - refer to ViewController in AppDelegate) - Is this also possible?
But maybe there is an easier/better option?
http://www.iphonedevsdk.com/forum/iphone-sdk-development/93913-retaining-data-when-using-storyboards.html
Storyboard - refer to ViewController in AppDelegate
How to serialize a UIView?
Yess!! I got the solution. Do the following:
In you're .h file:
#property (strong, nonatomic) UITabBarController *tabController;
In you're .m file:
#synthesize tabController;
tabController = [self.storyboard instantiateViewControllerWithIdentifier:#"tabbar"];
The selected index is the tab you want to go
tabController.selectedIndex = 1;
[[self navigationController] pushViewController:tabController animated:YES];
For anyone coming across this (my) question in the future, this is how I ended up 'coding' it.
Open the storyboard and select your 'Tab Bar Controller' and open the Attributes Inspector
Fill in an 'identifier' in the field
With the first view controller (see scenario in original post) I create an global reference to the viewcontroller:
firstviewcontroller.h
#interface YourViewController : UIViewController {
UITabBarController *tabController;
}
firstviewcontroller.m
//Fill the reference to the tabcontroller using the identifier
tabController = [self.storyboard instantiateViewControllerWithIdentifier:#"tabbar"];
Now to switch from the firstviewcontroller the following line can be used:
[[self navigationController] pushViewController:tabController animated:YES];
This might be even more simple solution (without using properties - in fact, all your class instances don't need to know about their destination controllers, so just save it as static in the pushing function):
static UIVewController *destController = nil;
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"MainStoryboard" bundle:nil];
if (!storyboard) {
DLog(Storyboard not found);
return;
}
if (destController == nil) { //first initialisation of destController
destController = [storyboard instantiateViewControllerWithIdentifier:#"{Your Destination controller identifyer}"];
if(!destController) {
DLog(destController not found)
return;
}
}
//set any additional destController's properties;
[self.navigationController pushViewController:destController animated:YES];
p.s. DLog is just my variation of NSLog.
But it's really interesting how to do this with segue?

A couple of problems customizing Apples MultipleDetailViews Example

I have been trying to add/implement this example to my existing Split View app tests.
Apple Example
I what to use the concept of replacing the detail view or right view, otherwise my app will be different. It is this difference that is causing my problems.
I have a rootviewcontroller or left view and upon choosing something here a new view is pushed onto this view. Upon choosing something in this "pushed view" I want to change the detail view or right hand view. This is the difference to apples example where the rootview does not have a pushed view on it and thus references are not broken.
Below is my change code - the new View DVCases is being initialized but the didload is not happening.
The issues are learner issues to do with my classes.
This below code is in my RootViewController implementation code but my reference to splitviewcontroller is not working if there is a new view pushed.
Second self.navigationcontroller is not correct because I have pushed a second view to the rootviewcontroller.
To centralize and simplify the code what I have done is from the delegate of the pushed view in the didselect event i call a method found in the rootviewcontroller passing the index as a parameter. The code for my custom method contains what is below.
So my question is how do I do this in my situation where I have pushed other views onto the rootview or left side. It appears that after pushing a view the reference to splitviewcontroller is gone and self.navigationcontroller is also gone/or wrong.
UIViewController <SubstitutableDetailViewController> *detailViewController = nil;
if (value == 0) {
DVCases *newDetailViewController = [[DVCases alloc] initWithNibName:#"DVCases" bundle:nil];
detailViewController = newDetailViewController;
}
// Update the split view controller's view controllers array.
NSArray *viewControllers = [[NSArray alloc] initWithObjects:self.navigationController, detailViewController, nil];
splitViewController.viewControllers = viewControllers;
[viewControllers release];
// Dismiss the popover if it's present.
if (popoverController != nil) {
[popoverController dismissPopoverAnimated:YES];
}
// Configure the new view controller's popover button (after the view has been displayed and its toolbar/navigation bar has been created).
if (rootPopoverButtonItem != nil) {
[detailViewController showRootPopoverButtonItem:self.rootPopoverButtonItem];
}
[detailViewController release];
I would appreciate any tips or help you might have.
Initialization of any viewcontroller class does not mean that it will make call to viewDidLoad method.
viewDidLoad method will only be called when you load view of that viewController. Generally we do it either by following methods.
1. Pushing it on navigation stack.
2. Presenting it using modal transition.
3. Adding it on some other view using [someView addSubView:controller.view];
4. Selecting any tabBar item for the first time Or tapping tabBar Item twice.
there may be some other scenarios.
But right now in your code I don't see any of this element.
Initialization means you are calling the direct method for intialization(calling its constructor) like here in above code initWithNibName will call this method of DVClass not any other(until this method had call for other methods inside it).
Thanks
As I am learning to properly code - my problems centres around that.
The above code is perfect as long as you call it using the same instance. I was not. Thus it was not working.
In the end I made my RootViewController a delegate for a method that has the above code. Thus when in another view - this view can call this method and the proper or real instance of RootViewController will implement it.

iPhone: How to make a proxy controller that can select different views+controllers

I have an app with a TabBar.
One of the TabBar-connected views is actually variable:
Upon first opening of that view, it shall show a login dialog. If the user logs in, the login view is finished and the actual data view is shown.
Later, if the user goes back to this tab, the data view shall appear right away, so no more login dialog view.
Until now, I've solved this by directly manipulating the tab bar item's navigation controller's view controller array: Initially, it is set to show the login view. Once the user has logged in, the login controller is removed from the navigation controller and the actual data view is inserted instead.
I am not happy with this solution, though, as it causes problems once there are more than 5 tab items.
Hence, I wonder how I can avoid this navigation controller "patching" and instead have a new root controller for this tab that will then either invoke the login view or immediately show the data view.
Note: There are design reasons why the data view can't just pop up a modal view controller for the login. Therefore, I really like to provide something like a proxy controller that can direct the functionality to one of two other controllers of its choice.
How would I accomplish that?
Or are there other concepts I employ use here?
My suggestion would be to have two completely separate view controllers. Once login is complete, change the tab bar's view controller array by removing the login view controller and adding the content view controller. Something like this:
- (void) didLogin {
UITabBarController *tabBarController = self.tabBarController;
NSMutableArray *array = [NSMutableArray arrayWithARray:tabBarController.viewControllers];
int idx = [array indexOfObject:self];
UIViewController *contentViewController = //Create or get a chached copy of the content view controller
//Optionally here you could copy the tab bar item rather than setting it up within the contentViewController.
contentViewController.tabBarItem = self.tabBarItem;
[array replaceObjectAtIndex:idx withObject:contentViewController];
tabBarController.viewControllers = array;
tabBarController.selectedViewController = contentViewController;
}
Edit:
If you're within the More view controller, it automatically detects if it's showing a navigation controller when choosing a tab normally. If you're doing it programmatically, I think you'd just have to do the same thing, but manually. Something like this:
- (void) didLogin {
UITabBarController *tabBarController = self.tabBarController;
NSMutableArray *array = [NSMutableArray arrayWithARray:tabBarController.viewControllers];
if (array.count > 5 && [array indexOfObject:self] >= 5) {
[tabBarController popToRootViewControllerAnimated:NO];
[tabBarController.moreNavigationController pushViewController:contentViewController animated:NO];
//Note: Not sure if this next line should be contentViewController or moreNavigationController.
tabBarController.selectedViewController = contentViewController;
} else {
//Normal method above
}
}
Thomas: > This requires that the controller handles both functionality in the same class
A cleaner approach might be to create a proxy controller class. The class would employ login class and data display class and would switch functionality between them underneath. The user of the class wouldn't tell any difference.