So i wanted to build a tabbar that has more than 5 items and is scrollable and found this article.
Easy done by subclassing the UITabBarController and hide the existing tabbar:
for(UIView *view in self.view.subviews)
{
if([view isKindOfClass:[UITabBar class]])
{
view.hidden = YES;
break;
}
}
Then i just add a UIScrollView and throw some buttons in there based on the items-collection of the tabbarcontroller.
int i = 0;
for (UITabBarItem *item in self.tabBar.items)
{
UIView *tab = [[[UIView alloc] initWithFrame:CGRectMake(i*60, 0, 60, 60)] autorelease];
UIButton *btn = [[[UIButton alloc] initWithFrame:CGRectMake(7.5, 1, 45, 45)] autorelease];
[btn setImage:item.image forState:UIControlStateNormal];
btn.tag = i;
[btn addTarget:self action:#selector(didSelectTabrBarItem:) forControlEvents:UIControlEventTouchUpInside];
[tab addSubview:btn];
[self.scrollView addSubview:tab];
i++;
if(self.selectedViewController == nil)
[self setSelectedIndex:0];
}
I am overriding the setSelectedindex/ViewController since i need some addition drawing.
-(void)setSelectedViewController:(UIViewController *)selectedViewController
{
[super setSelectedViewController:selectedViewController];
[self startTimer];
}
-(void)setSelectedIndex:(NSUInteger)selectedIndex
{
[super setSelectedIndex:selectedIndex];
[self startTimer];
}
The problem is that when I am pressing button number 5, 6 or 7, the tabbarcontroller opens the More view. How do i get rid of that and make the last three items act like the other ones? - Could it be the call to the super?
My guess would be to completely kill the UITabBarController and implement my own custom tabbar. But is it possible to disable the more menu and have the UITabBarController select item 5, 6 and 7 as normal?
So, since I'm too lousy to write a completely new tab bar i decided to investigate and try to hack UITabBarController.
And here's the solution:
The actual problem is that when you touch a tab bar item with index above 4, the UITabBarController vil actually display the moreNavigationController. This is a UINavigationController containing a view of type UIMoreViewControllerList, which is a type from the private Cocoa framework together with an instance of the ViewController you selected.
So how do we get rid of the More button?
Simply remove the UIMoreViewControllerList from the moreNavigationController collection, leaving only the ViewController you selected.
-(void)setSelectedViewController:(UIViewController *)selectedViewController
{
[super setSelectedViewController:selectedViewController];
if([self.moreNavigationController.viewControllers count] > 1)
{
//Modify the view stack to remove the More view
self.moreNavigationController.viewControllers = [[[NSArray alloc] initWithObjects:self.moreNavigationController.visibleViewController, nil] autorelease];
}
}
Well that leaves us with a Edit button in the top right corner (Titlebar).
How do you get rid of that, then?
Yeah. Thats another dirty hack. To remove the Edit button I'd actually have to implement one method from the UINavigationControllerDelegate for the moreNavigationController on my custom UITabBarController.
//navigationController delegate
- (void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated
{
if ([navigationController isKindOfClass:NSClassFromString(#"UIMoreNavigationController")])
{
// We don't need Edit button in More screen.
UINavigationBar *morenavbar = navigationController.navigationBar;
UINavigationItem *morenavitem = morenavbar.topItem;
morenavitem.rightBarButtonItem = nil;
}
}
And thats the way to kill off the default More functionality. I really think Apple pissed their own pants here creating a UITabBarController that both handles logic and UI stuff.
Why not create a controller that has the logic to preload the ViewControllers and switch between then, and then an implementation you can use if you want the More thingie. - Or even better: Make it possible to disable the More functionality.
This is pretty close, it may help you
https://github.com/iosdeveloper/InfiniTabBar
I should finalize this thread. I have been having the stuff above in a App Store for a year and it has caused massive problems in the long run. It works, but it quirks when you rely on the built in features of UITabbarcontroller as it messes around with the view stack.
After going around this hot ash for over a year we decided to build our own tabbarcontroller/menucontroller. Thta took like a day and have freed us from all the fixes and quirks.
My hack works, but I recommend building your own navigation class - it will pay off in the long run :-)
I had a similar problem and solved it by using a UITabBarController and hiding the tab bar. Then I drew a custom tab bar over the top of it and when a button for a tab was clicked, call
tabbar.selectedIndex = index
Where 'tabbar' is the original UITabBarController. Setting the selectedIndex property of a UITabBarController changes the currently displayed view controller to the controller at that index. This way you still get all the functionality of a UITabBarController but you can have as many tabs as you want and customize it however you want.
Otherwise, I don't think there is a way to remove the "more" functionality.
Related
I'm quite new to iOS development and I am stuck. Currently I am using one tab controller to switch between two view controllers (list and map view). This made it easier to use storyboard to configure the look of the two views.
Now the requirements have changed and the app needs to have one view controller with a segmented control that on click, displays either the list or the map view. In order to do this, I would need to make one view controller that can display list/map view.
I understand how the segmented controller part works, but I'm just stuck on how I can go about having two views with one or the other displayed in the same area.
How can I go about having two views in one view controller (if possible, utilizing storyboard)?
Thanks in advance!
You should not have two main views in a single view controller, instead you need to create one view controller per view that you want to show. However you can certainly have multiple subviews in a single view controller, which may be what works for you.
There are a number of approaches to solve this the problem, the correct approach would be to create a container UIViewController, and add as its childs the 2 viewcontrollers you want to show, them simply set the view to the view controller you want to display, but that would probably be overly complicated since you mention you are new to iOS development.
Therefore an easy solution (not sure if you can implement this in storyboard - since I don't like it), would be to have a single view controller, with the tabs, and 2 subviews of the main view, then you can simply switch between views by doing something like this:
[self.view addSubview:view1];
//to switch
[view1 removeFromSuperview];
[self.view addSubView:view2];
alternatively, you do not really need to remove it from superview but just hide it, and then use bringSubViewToFront to show the view that you need.
If you want to use the other approach I would recommend looking for this video the WWDC 2011 video titled "Implementing UIViewController Containment". This other question should be useful to: UISegmented control with 2 views
Hope that helps.
Using storyboard api you can switch between 2 viewControllers
- (void)viewDidLoad {
[super viewDidLoad];
UIViewController *viewController = [self viewControllerForSegmentIndex:self.typeSegmentedControl.selectedSegmentIndex];
[self addChildViewController:viewController];
viewController.view.frame = self.contentView.bounds;
[self.contentView addSubview:viewController.view];
self.currentViewController = viewController;
}
- (UIViewController *)viewControllerForSegmentIndex:(NSInteger)index {
UIViewController *viewController;
switch (index) {
case 0:
viewController = [self.storyboard instantiateViewControllerWithIdentifier:#"FirstViewController"];
break;
case 1:
viewController = [self.storyboard instantiateViewControllerWithIdentifier:#"SecondViewController"];
break;
}
return viewController;
}
- (IBAction)segmentChanged:(UISegmentedControl *)sender {
UIViewController *viewController = [self viewControllerForSegmentIndex:sender.selectedSegmentIndex];
[self addChildViewController:viewController];
[self transitionFromViewController:self.currentViewController toViewController:viewController duration:0.0 options:UIViewAnimationOptionTransitionNone animations:^{
[self.currentViewController.view removeFromSuperview];
viewController.view.frame = self.contentView.bounds;
[self.contentView addSubview:viewController.view];
} completion:^(BOOL finished) {
[viewController didMoveToParentViewController:self];
[self.currentViewController removeFromParentViewController];
self.currentViewController = viewController ;
}];
self.navigationItem.title = viewController.title;
}
This is in reference to iOS tutorial by Raywenderlich. Hope this helps
With Storyboard it is possible in this way.
Create UIViewController with UISegmentControl and UITableView+UITableViewCell added to it.
Now you want to add MKMapView as well, hoverer, if you simply try to place the MapView on the ViewController, it will be added as new TableView cell, which is not what we want.
That's why you should not do it so. Instead, MapView has to be added to Storyboard's List of ViewControllers
Adjust the size and origin of MapView to be the same as TableView ones.
Now, setHidden to YES for either TableView of MapView, create and synthesize outlets for them. Then in Segment control Value Changed method implement switching:
- (IBAction)switchView:(id)sender {
self.theTableView.hidden = !self.theTableView.hidden;
self.theMapView.hidden = !self.theMapView.hidden;
if (!self.theTableView.hidden) {
[self.theTableView reloadData];
}
}
Here is the setup of my iPhone App:
I have a UITabBarController that has 4 View Controllers (1 UINavigationController & 3 UIViewControllers).
Onload my app is defaulted to the UINavigationController, where there is a grouped UITableView which gives two navigation options, when the user hits the first option the relevant UITableViewController (Contains a list of locations) is pushed onto the stack.
What I would like to happen on screen is to have a UISegmentedControl that has two options, a "List View" (which the user sees by default when the UIViewController gets pushed) ans a "Map View" that will allow the relevant locations to be represented on the map.
I use the follwing code to create the UISegmentedControl on the NavigationBar:
UISegmentedControl *segmentedControl = [[UISegmentedControl alloc] initWithItems:segmentItems];
[segmentedControl setSelectedSegmentIndex:0];
[segmentedControl setAutoresizingMask:UIViewAutoresizingFlexibleWidth];
[segmentedControl setSegmentedControlStyle:UISegmentedControlStyleBar];
[segmentedControl addTarget:self action:#selector(segmentAction:) forControlEvents:UIControlEventValueChanged];
[[self navigationItem] setTitleView:segmentedControl];
[segmentedControl release];
And this is the method that the UISegmented control will call when changed:
- (void) segmentAction:(id)sender
{
UISegmentedControl *segmentControl = sender;
if([segmentControl selectedSegmentIndex] == 0)
{
NSLog(#"LIST VIEW CHOSEN!");
}
else
{
NSLog(#"MAP VIEW CHOSEN!");
}
}
So basically my question is what is the correct/best way to implement what I am trying to achieve, I dont want to push the controller onto the navigation stack, I just want to toggle it in place (possibly with animation). All interfaces are built in IB.
One way i tried to do it, that worked but didn't feel like it was the correct was by creating two separate UIViews in my nib file (One for the List View and one one for the Map view), and then using setView: appropriately.. but then I thought shouldn't each of these views have there own controller and/or own nib?
- (void) segmentAction:(id)sender
{
UISegmentedControl *segmentControl = sender;
if([segmentControl selectedSegmentIndex] == 0)
{
NSLog(#"LIST VIEW CHOSEN!");
[self setView:listView]; //Declared as an UIView IBOutlet
}
else
{
NSLog(#"MAP VIEW CHOSEN!");
[self setView:mapView]; //Declared as an UIView IBOutlet
}
}
Some semi related examples I have come across use "removeFromSuperview" and "addSubview" and this has somewhat confued me, i'm relatively new to Cocoa Touch development, so any help getting my head around this would be appreciated.
Thanks
There are a couple of ways to handle this, if you are simply going between a TableView (that is already on the UINavigationController stack) and a mapview that goes with this SPECIFIC UITableView, you could have the segmentedControl display the mapviewController modally, that is presentModalViewController:. This does have some drawbacks as to functionality, but it could be what you are looking for if the mapview will be relatively simple.
Another way of implementing this, and probably the most robust way, is to have one view controller that handles both the table view and the map view. This is ideal if they have the same data source for certain.
One other way is to have two view controllers, one each for the table view and map view, and literally do pushes and pops on the navigation controller stack in your segmentAction method.
Ex:
- (void) segmentAction:(id)sender
{
UISegmentedControl *segmentControl = sender;
if([segmentControl selectedSegmentIndex] == 0)
{
NSLog(#"LIST VIEW CHOSEN!");
[self.navigationController popViewControllerAnimated:YES];
[self.navigationController pushViewController:listViewController animated:YES];
}
else
{
NSLog(#"MAP VIEW CHOSEN!");
[self.navigationController popViewControllerAnimated:YES];
[self.navigationController pushViewController:mapViewController animated:YES];
}
}
I would make sure to read the Human Interface Guidelines, as well as the documentation of how view controllers and navigation controllers work.
I came across this while looking for a solution to the same problem. Seems to work pretty well for me.
switch (self.menu.selectedSegmentIndex)
{
case 0 :
// setting text to name label
boynamelabel.text=[boysnamearray objectAtIndex:0];
girlnamelabel.text=[girlsnamearray objectAtIndex:0];
// setting text to percent label
boyspercentlabel.text=[boyspercentarray objectAtIndex:0];
girlspercentlabel.text=[girlsprecentarray objectAtIndex:0];
// setting image to image view
boysimage.image=[UIImage imageNamed:#"boy1.jpeg"];
girlsimage.image=[UIImage imageNamed:#"girl1.jpeg"];
break;
case 1:
// setting text to name label
boynamelabel.text=[boysnamearray objectAtIndex:1];
girlnamelabel.text=[girlsnamearray objectAtIndex:1];
// setting text to percent label
boyspercentlabel.text=[boyspercentarray objectAtIndex:1];
girlspercentlabel.text=[girlsprecentarray objectAtIndex:1];
// setting image to image view
boysimage.image=[UIImage imageNamed:#"boy2.jpeg"];
girlsimage.image=[UIImage imageNamed:#"girl2.jpeg"];
break;
case 2 :
// setting text to name label
boynamelabel.text=[boysnamearray objectAtIndex:2];
girlnamelabel.text=[girlsnamearray objectAtIndex:2];
// setting text to percent label
boyspercentlabel.text=[boyspercentarray objectAtIndex:2];
girlspercentlabel.text=[girlsprecentarray objectAtIndex:2];
// setting image to image view
boysimage.image=[UIImage imageNamed:#"boy3.jpeg"];
girlsimage.image=[UIImage imageNamed:#"girl3.jpeg"];
break;
default:
break;
}
I have a problem with switching views in an iPhone application.
I have the source code of "Beginning iPhone 3 Development" (http://books.google.com/books?id=TcP2bgESYfgC&printsec=frontcover&dq=beginning+iphone+3+development#v=onepage&q=beginning%20iphone%203%20development&f=false) - chapter 6 - Multiview Applications.
Now I have the problem, I want to create a new view which should switch by clicking the button on the blue screen "Press me". But it did not work.
I add the these lines to the IBAction that the button on the blue screen is pressed:
StartViewController *startController = [[StartViewController alloc] initWithNibName:#"StartViewController" bundle:nil];
self.startViewController = startController;
[self.view insertSubview:startController.view atIndex:1];
[startController release];
But the toolbar at the bottom won't disappear. But I want that this toolbar disappear.
If I wrote
[self.view insertSubview:startController.view atIndex:0];
instead of
[self.view insertSubview:startController.view atIndex:1];
the new xib lies behind the old one, so I see both views, the old and the new.
Why? I do not understand this.
Thanks a lot in advance & Best Regards Tim
The toolbar is in the SwitchView so you would need to hide it from the view if you want it to hide. You could make an IBOutlet for the toolbar and then call setHidden:(BOOL) to hide it. You will need to do this from BlueViewController so you will need a way to get to your super view (which is SwitchView). You will also need to remove the BlueView from the super view by calling removeFromSuperView on blueViewController before inserting the new view into place. It is basically the same code that comes from the switch button in SwitchViewController.
Update:
I looked at your code. In BlueViewController.m use this for blueButtonPressed:(id)sender
StartViewController *start = [[StartViewController alloc] initWithNibName:#"StartViewController" bundle:nil];
self.startViewController = start;
[start release];
View_SwitcherAppDelegate *appDelegate = [UIApplication sharedApplication].delegate;
SwitchViewController *switchController = appDelegate.switchViewController;
switchController.theToolbar.hidden = YES;
[self.view removeFromSuperview];
[self.view insertSubview:startViewController.view atIndex:0];
You will also need to add these two imports for "View_SwitcherAppDelegate.h" and "SwitchViewController.h".
How can I make it so when a tab is selected, the current one is unloaded, and the next one is loaded so only one loaded at a time? Or should I not even do this? I know how to do it with a normal UIViewController as the root VC, but not sure with a UITabBarController. Also, is there a way to animate the transition from one tab to the next? Any help? Thanks!!
EDIT: ... If I unload the view controllers, then their icons on the tab bar are gone... maybe I'll just unload their views..
I can answer both questions in one...
You just need a class that acts as the UITabBarController delegate, then implement a method like so:
// Animate tab selections so they fade in and fade out
-(void)tabBarController:(UITabBarController*)tbc didSelectViewController:(UIViewController*)newSelection
{
[UIView beginAnimations:#"TabFadeIn" context:nil];
[UIView setAnimationDuration:0.6];
for( UIViewController* vc in tbc.viewControllers )
vc.view.alpha = (vc==newSelection) ? 1 : 0;
[UIView commitAnimations];
}
Now my code simply makes the tab bars fade in and out, but you could also do work here to unload non-used tabs. Sometimes that is a good idea if some of the tabs will be using a ton of memory.
You cant really manage the UITabBarController unfortunaly so you cant do lazy loading. You can by managining your own TabBar but you said u knew that already,
to manage your own tab bar though all you gotta do is setup a UITabBar with its TabBarItems in a ViewController, then implement the TabBar Delegate protocol, mainly the – tabBar:didSelectItem: method which is called whenever the tabbarItem selection is changed, then based on the item id you can load your new ViewController and release any others
so: Edit: this code goes in your UIViewController
-(void)addTabBar{
NSMutableArray* items=[[NSMutableArray alloc] init];
UITabBarItem *eventsItem= [[UITabBarItem alloc] initWithTitle:#"Events" image:nil tag:0];
UITabBarItem *albumItems=[[UITabBarItem alloc] initWithTitle:#"Album" image:nil tag:1]; //the tag is how you tell what was clicked
[items addObject:homeItem];
[items addObject:albumItems];
//MyTabBar is of type UITabBar
myTabBar=[[UITabBar alloc] initWithFrame:CGRectMake(0,411,320,49)];
[myTabBar setItems:items];
myTabBar.delegate=self; //you gotta implement the UITabBar delegate protocol
[myTabBar setSelectedItem:eventItem]; //set the selected item
[homeItem release];
[eventsItem release];
[albumItems release];
[items release];
[self.view addSubview:myTabBar]
}
then the protocol method would look something like below
- (void)tabBar:(UITabBar *)tabBar didSelectItem:(UITabBarItem *)item
{
if(item.tag == 0 )
{
//load the ViewController that pertains to this item and release others
}
...etc
}
Lazy loading is not an UITabBarController task. Instead, it is responsability of your viewControllers associated with your Tab.
To release the UIView, associated with each UIViewControllers, every time you change the TabBarItem, you must implement the following method in each UIViewController subclass, associated with your UITabBarController.viewControllers property:
-(void)viewDidDisappear {
[self.view removeFromSuperview];
self.view = nil;
}
Obviously, this will remove the self.view associated with your UIViewController. However, if your code is smart enough, this will remove all the related objects.
For example, suppose that your loadView method is as follow:
-(void)loadView {
UIView *contentVew = [[UIView alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
self.view = contentView;
…
...
UILabel *aLabel = [[UILabel alloc] initWithFrame:CGRectMake(0,0,320,50)];
…
…
[contentView addSubview:aLabel];
[aLabel release];
…
[contentView release];
}
This means that every object inside the contentView and their memory responsabilities are demanded to the contentView, that is released and attached to the self.view property.
In this scenario, removing the self.view (that's the reference to the contentView) resulting in a domino-style releasing of every object, that's your goal.
Best regards
Not sure why you'd want to do this, the current tab will get unloaded anyway if there's a memory issue involved. That's what -viewWillAppear, -viewDidUnload, etc. are for.
UITabBarController does lazy load all of its view controllers. When a tab is switched out, then it's view is subject to being deallocated in a memory tight situation. It is then recreated when it is chosen the second time. Furthermore, most of your memory hits are in your views and not the view controllers. Hence, don't worry about the memory hit from the view controller. The view is the proze.
If you are running on v3 of the OS, then you can use the -viewDidUnload method to ensure the maximal amount of memory reduction.
Andrew
I'm currently using this to unload inactive view controllers in the tab bar (based on Kendall's answer)
- (void)tabBarController:(UITabBarController *)tabBarController didSelectViewController: (UIViewController *)viewController {
// reload all inactive view controllers in the tab bar
for (UIViewController *vc in tabBarController.viewControllers) {
if(vc != viewController)
[vc didReceiveMemoryWarning];
}
}
I am having some trouble with view hierarchies and drawing on the iPhone.
To be more specific, I have a tab bar application with a certain tab that contains a table view where I would like the selection of a specific cell to have a UIPickerView slide up. The sliding isn't really a problem (or at least I'm assuming it won't be once I figure this part out), but I cannot seem to get the picker (or any UIView, for that matter) to show up over the tab bar.
I think the way I have this certain tab set up may be the problem. Normally, in any other tab, I could just do something like this:
[self.tabBarController.view addSubview:pickerView];
and everything would work fine.
But for this specific tab, I have a UISegmentedControl in the navigation bar that switches between two different UITableViews. So the view controller associated with the tab (call it TabViewController) has its own instances of these two table view controllers (TableOneViewController and TableTwoViewController) and will insert the currently selected table view (based on the segmented control) as a subview of TabViewController's view.
If I didn't need to switch the views like this I could just call
[tabViewController.tabBarController.view addSubview:pickerView];
from TabViewController and the picker would show up over the tab bar. But the thing is I cannot call this in either of the table view controllers selected within this tab (well I can, but it doesn't do anything). I have tried passing this tabBarController property into the table view controller, but that doesn't work either. I have also tried messing around with the app delegate (which I'm trying to avoid) to no avail.
Is there something simple I'm missing here, or can this not be done? I feel like it should since a keyboard can slide up over the tab bar in this table view. Is there a way to just draw over all the current views and subviews?
Here is what is called when the segmented control is selected within TabViewController.m, and switches the views:
- (IBAction)switchViews:(id)sender
{
if (self.tableOneViewController.view.superview == nil)
{
if (self.tableOneViewController == nil)
{
TableOneViewController *tempOneController = [[TableOneViewController alloc] initWithNibName:#"TableOneViewController" bundle:nil];
self.tableOneViewController = tempOneController;
[tempOneController release];
}
[tableTwoViewController.view removeFromSuperview];
[self.view insertSubview:tableOneViewController.view atIndex:0];
}
else
{
if (self.tableTwoViewController == nil)
{
TableTwoViewController * tempOneController = [[TableTwoViewController alloc] initWithNibName:#"TableTwoViewController" bundle:nil];
self.tableTwoViewController = tempOneController;
[tempOneController release];
}
[tableOneViewController.view removeFromSuperview];
[self.view insertSubview:tableTwoViewController.view atIndex:0];
}
}
And here's what's going on when I try to add the picker in TableOneViewController.m:
UIPickerView *tempPicker = [[UIPickerView alloc] init];
tempPicker.delegate = self;
tempPicker.dataSource = self;
tempPicker.showsSelectionIndicator = YES;
tempPicker.clipsToBounds = NO; // thought this might work, but doesn't
self.picker = tempPicker;
[tempPicker release];
[self.view addSubview:pickerPicker]; // adds beneath tab bar!
[[[UIApplication sharedApplication] keyWindow] addSubview:someViewController];
Make sure the viewController you are loading in is 480px high!
To remove it again, use [someViewController removeFromSuperview];
Maybe you want to do just
[tabBarController presentModalViewController:someViewController animated:YES];
?
This should slide on top of anything else you have on the screen.