quick question - I have my "first view" which is going to be the ONLY view in my application. I've added a UITabBar to this view using Interface Builder. I am simply wanting to use this as a menu to control the contents of a scroll view.
For example, the user clicks on the first icon in the UITabBar - I get its tag, then based on that, will add a subview to the scrollview. This is working ok....
...but, I have been viewing a few tutorials on tabbars and it seems that 99% of the time they are used to control views. I simply want it to return my tags.
So my question is this: is what I am doing ok?? Can it be used for simply returning a value rather than changing a view? If this is common/OK practice, how on earth do I reference it?
I can get the selected item tag, but cannot actually reference the uiTabBar to make the first button selected. In my .h file, I tried to specify an IBOutlet for the controller, but I cannot link this in IB.
thanks for any info!
To receive notifications that a tab bar item has been clicked you need to modify your view controller to implement the UITabBarDelegate protocol and add an outlet for the tab bar. To do this, modify your declaration in MyViewController.h to something like this:
#interface MyViewController : UIViewController <UITabBarDelegate> {
UITabBar *tabBar;
...
}
#property (nonatomic, assign) IBOutlet UITabBar *tabBar;
Then implement the tabBar:didSelectItem method in MyViewController.m as follows:
- (void)tabBar:(UITabBar *)tabBar didSelectItem:(UITabBarItem *)item
{
NSLog(#"Tab clicked: %d", item.tag);
}
You must also set your view controller as the delegate of the tab bar in IB. (hint: connect up the 'delegate' outlet from the tab bar to File's Owner).
To access the tab bar from your view controller use the tabBar property and do things like:
self.tabBar.selectedItem = [self.tabBar.items objectAtIndex:0];
As to whether this is a good idea - why not? All the tutorials show a tab bar being used with a UITabBarContoller to switch views, but it is designed to operate as a stand-alone control as well. As long as you are not breaking any HIG rules then how you implement your interface switching is up to you.
Related
I am using UITabBarController so that it can be displayed on all views once declared in delegate, but my requirement is that when any tab bar button clicked it should work like a button works on pushViewController:. Is it possible, can anyone guide here.
Thanks in advance.
you can just create a UINavigationController with toolbar hidden.
Then create a root view controller which has toolbar or custom view on bottom and add buttons on it.
Add targets to that buttons to push view controllers which you want.
This is not a typical behavior, however you can try as follows:
Declare a delegate (UITabBarDelegate) for your UITabBar, and implement the method -(void)tabBar:(UITabBar *)tabBar didSelectItem:(UITabBarItem *)item to get notified of the user selecting tabs. In this method you will push a new view controller in the navigation controller's stack (or pop one that is not desired).
Depending on your needs, you may also need to manually set the currently selected tab bar item. To do so you can manipulate the tab bar's #property(nonatomic) NSUInteger selectedIndex. Be aware that changing this property will trigger another call to tabBar:didSelectItem: which may or may not be wanted.
How can I enable editing in the more tab? I can click on edit and see the items but I can't move them. Is there a option in UITabbarController?
Set tabBarController.customizableViewControllers to something, the easiest case would be:
tabBarController.customizableViewControllers = tabBarController.viewControllers;
after you set up the viewControllers.
You use the UITabBarControllerDelegate protocol when you want to augment the behavior of a tab bar. In particular, you can use it to determine whether specific tabs should be selected, to perform actions after a tab is selected, or to perform actions before or after the user customizes the order of the tabs. After implementing these methods in your custom object, you should then assign that object to the delegate property of the corresponding UITabBarController object.
First of all define outlets in the app delegate and attach them on Interface Builder, i assume you know how to do this:
IBOutlet UITabBarItem *tabBarItem1;
IBOutlet UITabBarItem *tabBarItem2;
IBOutlet UITabBarItem *tabBarItem3;
IBOutlet UITabBarItem *tabBarItem4;
Then your class, probably the view controller must be UITabBarControllerDelegate and use the following hook:
- (void)tabBarController:(UITabBarController *)tabBarController willEndCustomizingViewControllers:(NSArray *)viewControllers changed:(BOOL)changed
Hope that helps !
I'm having some problems because I don't know how it can be done. Someone help me please.
Problem:
I have a project with a UIViewController class with created all time when I create a new project in Xcode. In this viewController, I have a xib file for designing in Xcode.
What I need is to create some views and design them in Interface Builder like this:
But I need these views to be UIViewControllers, not UIViews.
The project should look like:
So I don't know how I can do this in IB but I can do it from source code in ViewController.m
tab = [[TabBarController alloc] initWithNibName:#"mytestview" bundle:nil];
[tab.view setFrame:CGRectMake(100, 100, 400, 600)];
[self.view addSubview:tab.view];
But it's not my views it's a different object, and if I want to change position or size I must do it from code. How I can do same things in Interface Builder?
Let's consider following example based on Master-Detailed Application for iPhone only.
So, add new view controller in newly created project in Xcode:
I called it NewInsideViewController. Create it without xib:
Open DetailViewController.xib. Drag View Controller and View(*) objects from library to Objects area like this (I changed view's color to LightGray):
Choose this View Controller and change its Class from UIViewController to NewInsideController at the Identity Inspector:
Assign our View(*) to NewInsideController as a view:
Now all main actions in IB finished. We need to create instance of NewInsideController in our DetailViewController. You can do it by hand, but Xcode has a nice feature - drag-n-drop ;)
I called this property as myNewInsideController and DetailViewController.h looks like this:
#import <UIKit/UIKit.h>
#import "NewInsideController.h"
#interface DetailViewController : UIViewController
#property (strong, nonatomic) id detailItem;
#property (strong, nonatomic) IBOutlet NewInsideController *myNewInsideController;
#property (strong, nonatomic) IBOutlet UILabel *detailDescriptionLabel;
#end
Well, now our NewInsideController ready to work and manage its view. Let's add button and action to that view in order to verify this:
Write some code in IBAction in NewInsideController.
- (IBAction)insideButtonClick:(id)sender {
float rPart = arc4random()%100/100.0f;
float gPart = arc4random()%100/100.0f;
self.view.backgroundColor = [UIColor colorWithRed:rPart green:gPart blue:0.5f alpha:1.0f];
}
Run program.
If I understand the question clearly:
You have a parent view and controller, coming from a XIB.
You have placed subviews into the parent view.
You're wanting each subview placed into the parent view to have it's own (custom) controller, but you don't know how to add view controller's to your hierarchy (XCode will not let you drag view controllers into a view's canvas).
So, to answer the question succinctly: Let's assume you have a handful of custom UIViewController's in your project (each view controller consisting of a .h and a .m). Remember that you if you are laying these out in the context of the parent, they shouldn't have their own XIBs (you cannot nest XIBs in IB). What is important to note here is that you should only "layout" the interface in one location. If you want to have a XIB for each subview, this is not the correct approach. What you can (not should) do, however, is have several custom viewControllers, each connected to it's own view sitting within your parentView, and you can have the outlets of your sub view controller's set to objects in this parentView. Phew, kinda messy. The other thing you'd need to be aware of is that your parent view controller would need a reference to each of it's sub view controllers in order for you to be able to access those sub-controllers and their outlets programmatically, so for each sub view controller you add, you would also need to add an IBOutlet in your parent view controller pointing to each subviewController:
ParentViewController.h
#property (nonatomic, weak) IBOutlet CustomUIViewController *firstCustomController;
And then for example to set the background color on the view of your first custom subview/controller:
ParentViewController.m
[[[self firstCustomController] view] setBackgroundColor:[UIColor whiteColor]];
Open up your parent view controller in IB. If you look in your drawer of available objects, you'll find a generic UIViewController object. Drag this into your parent view controller (NOT onto it's views canvas, rather into the parent UIViewController object itself as seen in the left-column of IB builder). Select the generic view controller you've added and set it's class to your desired UIViewController subclass. Now, when your XIB loads, it will instantiate an instance of your custom view controller along with whatever you've added to it's canvas.
Finally, drag a generic UIView onto your canvas, placing it inside your existing controller's view (your screenshot already shows this as done). Right-click your custom view controller, and connect it's 'view' outlet to the view you added.
Now when you run, your custom view controller has a view that is on the screen that is the view of your custom controller subclass, and you didn't do any of it in code.
So now that you've done it, consider whether or not it is the best choice: Nested view controllers are messy to build (as you've seen) and aren't necessarily a good design decision: http://blog.carbonfive.com/2011/03/09/abusing-uiviewcontrollers/
Although iOS5 does support nested view controllers, personally I'd avoid using them. I value a best practice dictating one screen = one view controller.
You never, EVER, want to take views which are already under the control of a certain view controller, and make them subviews of another view controller's view.
View Controllers are the C part in the MVC design pattern - They are in charge of controlling a view (and its subviews). You can't take a view which is being managed by a controller, and stick it as a subview of a different controller's view - If you do that, it becomes ambigous who is responsible to manage this view.
Even though this "sticking" might be possible technically, it creates code which is hard to understand, hard to maintain, and most importantly - will cause bugs due to the unclarity of who is responsible to manage the view, and due to the fact that Apple's View/Controller framework doesn't support this.
For example: If the device is low on memory, Apple makes sure to unload views which are not currently displayed. Apple relies on the view controllers hierarchy to know when it can unload views. If you stick the view controller's view in another view controller's view, it's very likely that the view will never be unloaded even if it isn't visible.
Instead of breaking the view controller hierarchy, do one of the following:
Just add subviews to your view normally, either in interface builder, or in -viewDidLoad: to add them programatically, or (rarer) override -loadView.
Use view controller containment, either with Apple's ready-made ones (UINavigationController,UISplitViewController etc.) or with your own container view controllers (iOS>5).
Display view controllers modally.
The bad idea of breaking a view controller hierarchy is indeed very common and often seen in 3rd parties, probably because it's so easy and seemingly straightforward. Unfortunately this causes the aforementioned bugs :-(
I really recommend to everyone participating in this thread and comments to read Apple's View Controller Programming Guide, and watch WWDC 2011 "View Controller Containment" video.
I have an application which has 5 tabs on a TabBarController. For simplicity sake lets say they are Tab A, B, C, D, and E. Each tab takes the user to a TableViewController which is embedded in a Navigation controller. Each tab also has its own specific .h and .m files. The code for the most part is very similar between the 5 tabs. I want to do away with these 5 sets of class files and just use 1 set. This will make it much easier for me to make changes to the application (in 1 place instead of 5 places). How can I detect in the single implementation file which tab was selected? Once I know that I can put logic in place to render the tableview specifically for which tab was selected...
Another thing I should mention is that I need to detect the selected Tab in the TableViewController. The TabBarController is the point of entry for the application and I do not have a TabBarController subclass.
I tried this code in the TableViewController however it does not get accessed and/or used.
in .h file:
#interface MyController : UITableViewController <UITabBarDelegate>
in .m file:
- (void)tabBar:(UITabBar *)tabBar didSelectItem:(UITabBarItem *)item
{
//NSLog(#"selectedIndex: %d", self.tabBarController.selectedIndex);
NSLog(#"didSelectItem: %d", item.tag);
}
Easy, You already have the solution!
tabBarController
A parent or ancestor that is a tab bar controller.
(read-only)
#property(nonatomic, readonly, retain) UITabBarController *tabBarController
Discussion If the receiver is added to a tab bar controller, this property is the tab bar controller. If the receiver’s
navigation controller is added to a tab bar controller, this property
is the navigation controller’s tab bar controller. If no tab bar is
present or the receiver is a modal view, this property is nil.
That means that any viewController you add to a tab bar controller has this property filled in by the system.
Then in the view controller you want for the tab you implement viewWillAppear
-(void)viewWillAppear:(BOOL)animated{
[super viewWillAppear:animated];
NSUInteger selectedIndex = self.tabBarController.selectedIndex;
switch (selectedIndex) {
case 0:
//configure me
break;
case 1:
//configure me differently!!
break;
default:
break;
}
}
In light of the comments this property of tabBarController doesn't seem to be reliable.
The problem you describe sounds like something that could solved by subclassing. Make a subclass of UIViewController for the code in common with each tab and then subclass your subclass for each tabs viewController to make modifications unique to the tab.
Alternatively you could load each tab with the same class but a different xib. You can set properties on your view controller in the "user defined runtime attributes" section in interface builder. Then in the viewWillAppear block just check the property set by the xib on that instance.
If I understood you correctly, you have many choices:
- you may want to override the init method in your m file which I guess initializes a UITableViewCOntroller and pass an additional parameter to it depending on which tab you are in.
you may also want to add a tabid property to this class and set that when you are creating it for each tab (to something that shows which tab you are in).
you mat also use notifications (but it wont be the easiest or best solution, unless you have good reason not to use the first two)
I am sure there are lots of other ways.
My app is structured like this:
- Window
- Navigation controller
- Table view
- Button (in one of the rows)
- Tab bar controller
- tab..
- tab..
- tab..
(The tab bar controller isn't added as a sub view so it can't be seen)
How would you make the button able to manipulate the Navigation controller and tab bar controller objects?
Well you could have them as properties in your AppDelegate like this:
#property (nonatomic, retain) UINavigationController *navigationController;
#property (nonatomic, retain) UITabBarController *tabBarController;
And than in your table view you can get a pointer to them like this:
UINavigationController *navigationController = [(YourAppDelegateClass *)[[UIApplication sharedApplication] delegate] navigationController];
UITabBarController *tabBarController = [(YourAppDelegateClass *)[[UIApplication sharedApplication] delegate] tabBarController];
After this you can have your button do whatever you want to them. Let me know if this works for you.
Your description is not accurate. Both UITabbarController and UINavigationController are controllers of multiple view controllers. So, in your structure, you should add a UITableViewController between "Navigation controller" and "Table view".
According to Apple's View Controller Programming Guide for iOS, you can assign both view controllers or tab bar controllers to the items to be controlled by a tab bar controller.
Thus, you could have this structure:
- Window
- Tabbar Controller
-Navigation Controller
- Table View Controller
- Table view
- Button
- tab (another View Controller)
- tab
- etc.
You can hide the UITabbar when the table view is visible.
You could then access everything the way you would expect: from the UITableViewController it would work with self.navigationController, or self.tabbarController.
Given this description of what you would like to do:
The navigation controller is showing at the moment (its a login screen). I'd like to hide it and add the Tab bar controller as a subview
I think that you do not really need that the button has got references to the navigation controller and to the tab bar.
You can define a method in your app delegate that you can access directly from the button, like this:
[[(MyAppDelegate*)[[UIApplication sharedApplication].delegate] doSwapNavAndTabBar];
the app delegate is the most natural point to do this, since it is in a way "responsible" for the window object where you want to do the swap.
If the tab bar that you want to show depends in some way from the button, then I would suggest to pass the button reference as an argument to that method:
[[(MyAppDelegate*)[[UIApplication sharedApplication].delegate] doSwapNavAndTabBar:button];
as it is usually done to manage events generated through controls. The app delegate can then ask the button for the information necessary to decide on which tab bar to show or how to initialize it.