Is it possible with a standard UINavigationController-rooted app, to have a single ADBannerView visible at the bottom of the screen, below the view hierarchy? That is, without modifying each view-controller/view that can be pushed to the root UINavigationController, can I have a global ADBannerView be visible?
I'm not sure how to set this up, either in IB or in code. Help?
I see similar questions with vague answers. I'm looking for a concrete example.
EDIT: The better way to do this in iOS5+ is likely to use view controller containment. That is, make a root controller that contains your ad and application controller (nav, tab, etc.).
I figured out a way to do this. Here is what I did:
For my first attempt I created a new view controller called AdBannerController. For its view I created a full-screen view and two subviews. The first subview (contentView) is for regular content, the second is the AdBannerView. I used an instance of this view controller as the view controller associated with the app window ( [window addSubview: adBannerController.view] ). Then I added my UINavigationController.view as a subview of adBannerController.view: [adBannerController.contentView addSubview: navigationController.view].
This mostly worked except that viewcontrollers pushed to the UINavigationController never got their will/did-load/unload methods called. Shucks. I read in a few places that this is a symptom of the UINavigationController view not being a direct descendant of the app window.
For my second attempt I took the same AdBannerController and derived it from UINavigationController. This time, I did the following in loadView:
- (void)loadView
{
[super loadView];
_contentView = [self.view retain];
self.view = [[[UIView alloc] initWithFrame: _contentView.frame] autorelease];
[self.view addSubview: _contentView];
_adView = [[ADBannerView alloc] initWithFrame: CGRectMake(0, _contentView.bounds.size.height, 320, 50)];
_adView.currentContentSizeIdentifier = ADBannerContentSizeIdentifier320x50;
_adView.delegate = self;
[self.view addSubview: _adView];
/* for visual debugging of view layout
[[_mainView layer] setCornerRadius: 6.0];
[[_mainView layer] setMasksToBounds: YES];
[[_mainView layer] setBorderWidth: 1.5];
[[_mainView layer] setBorderColor: [[UIColor grayColor] CGColor]];
*/
}
Notice what happens - I let the superclass UINavigationController construct its regular "content" view, but I swap it out and replace it with my own view which is a container for both the content and ad views.
This works pretty well. I'm also using three20 and there were a few things required to make this work with that setup, but not too bad.
I hope this helps someone!
In Apple's dev sample code the iAdSuite project contents projects that have this done for you. Highly recommended.
In my root view controller (w/ ADBannerViewDelegate) I setup my banner by adding it to the nav controller view, which keeps it on top at all times:
banner = [[ADBannerView alloc] init];
banner.delegate = self;
banner.frame = CGRectMake(0.0, 430.0, banner.frame.size.width, banner.frame.size.height);
[self.navigationController.view addSubview:banner];
Note you will have to comment out layoutAnimated in delegate method bannerViewDidLoadAd as it will try to move the ad view up:
- (void)bannerViewDidLoadAd:(ADBannerView *)banner
{
//[self layoutAnimated:YES];
}
I adapted the approach suggested in the iAdSuite given here
http://developer.apple.com/library/ios/#samplecode/iAdSuite/Introduction/Intro.html
I downloaded the code and focused on the 'tab' example. I copied over the BannerViewController.h/.m as is into my project.
I created all my views in the usual way with the storyboard approach. However, in my AppDelegate class I then accessed the already built tab bar - containing all the storyboard built viewControllers.
The AppDelegate class implements the TabBarControllerDelegate protocol:
#interfaceAppDelegate : UIResponder <UITabBarControllerDelegate, UIApplicationDelegate>
The AppDelegate implementation didFinishLaunchingWithOptions method grabs the pre-built tabBar, setting its delegate to self (e.g. the AppDelegate class).
-(BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:
// ----------------------------------------------------------
// Set the TabBarController delegate to be 'self'
// ----------------------------------------------------------
_tabBarController = (UITabBarController*)self.window.rootViewController;
// tabController.selectedIndex = [defaults integerForKey:kOptionLastTabSelectedKey];
_tabBarController.delegate = self;
// update tab bar per iAdSuite approach
[self updateiAd];
I then built a new set of controllers per the iAdSuite approach and reset the tab bar with these new tab bar items.
-(void)updateiAd {
NSArray* viewControllers = [_tabBarController viewControllers];
NSMutableArray*newViewControllers = [[NSMutableArray alloc] init];
BannerViewController*bvc=NULL;
for(UIViewController * vc in viewControllers) {
bvc = [[BannerViewController alloc] initWithContentViewController:vc];
[newViewControllers addObject:bvc];
}
// set the new view controllers, replacing the original set
[_tabBarController setViewControllers:newViewControllers];
}
This approach puts the same 'ad' at the bottom of each view, exactly as needed. I also had to set the view title in the viewDidLoad method of each custom viewController (somehow, setting it on the bar item didn't seem to work not did setting the image; the later may reflect an issue with my images however).
My original configuration was
TabViewController
NavController1 NavController2 NavController3 ...
| | |
CustomViewController1 CustomViewController2 CustomViewController3
My final configuration is now
TabViewController
NavController1 NavController2 NavController3 ...
| | |
iAdView1 iAdView2 iAdView3
| | |
CustomViewController1 CustomViewController2 CustomViewController3
In terms of view lifecycle, I should add that only the NavControllers are in existence at the time the updateiAd method is called.
The individual CustomViewControllers1/2/3/etc get created after the call completes.
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];
}
}
NavigationControllers have ViewController stacks to manage, and limited animation transitions.
Adding a view controller as a sub-view to an existing view controller requires passing events to the sub-view controller, which is a pain to manage, loaded with little annoyances and in general feels like a bad hack when implementing (Apple also recommends against doing this).
Presenting a modal view controller again places a view controller on top of another, and while it doesn't have the event passing problems described above, it doesn't really 'swap' the view controller, it stacks it.
Storyboards are limited to iOS 5, and are almost ideal, but cannot be used in all projects.
Can someone present a SOLID CODE EXAMPLE on a way to change view controllers without the above limitations and allows for animated transitions between them?
A close example, but no animation:
How to use multiple iOS custom view controllers without a navigation controller
Edit: Nav Controller use is fine, but there needs to be animated transition styles (not simply the slide effects) the view controller being shown needs to be swapped completely (not stacked). If the second view controller must remove another view controller from the stack, then it's not encapsulated enough.
Edit 2: iOS 4 should be the base OS for this question, I should have clarified that when mentioning storyboards (above).
EDIT: New answer that works in any orientation.
The original answer only works when the interface is in portrait orientation. This is b/c view transition animations that replace a view w/ a different view must occur with views at least a level below the first view added to the window (e.g. window.rootViewController.view.anotherView).
I've implemented a simple container class I called TransitionController. You can find it at https://gist.github.com/1394947.
As an aside, I prefer the implementation in a separate class b/c it's easier to reuse. If you don't want that, you could simply implement the same logic directly in your app delegate eliminating the need for the TransitionController class. The logic you'd need would be the same however.
Use it as follows:
In your app delegate
// add a property for the TransitionController
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
MyViewController *vc = [[MyViewContoller alloc] init...];
self.transitionController = [[TransitionController alloc] initWithViewController:vc];
self.window.rootViewController = self.transitionController;
[self.window makeKeyAndVisible];
return YES;
}
To transition to a new view controller from any view controller
- (IBAction)flipToView
{
anotherViewController *vc = [[AnotherViewController alloc] init...];
MyAppDelegate *appDelegate = [UIApplication sharedApplication].delegate;
[appDelegate.transitionController transitionToViewController:vc withOptions:UIViewAnimationOptionTransitionFlipFromRight];
}
EDIT: Original Answer below - only works for portait orientation
I made the following assumptions for this example:
You have a view controller assigned as the rootViewController of your window
When you switch to a new view you want to replace the current viewController with the viewController owning the new view. At any time, only the current viewController is alive (e.g. alloc'ed).
The code can be easily modified to work differently, the key point is the animated transition and the single view controller. Make sure you don't retain a view controller anywhere outside of assigning it to window.rootViewController.
Code to animate transition in app delegate
- (void)transitionToViewController:(UIViewController *)viewController
withTransition:(UIViewAnimationOptions)transition
{
[UIView transitionFromView:self.window.rootViewController.view
toView:viewController.view
duration:0.65f
options:transition
completion:^(BOOL finished){
self.window.rootViewController = viewController;
}];
}
Example use in a view controller
- (IBAction)flipToNextView
{
AnotherViewController *anotherVC = [[AnotherVC alloc] init...];
MyAppDelegate *appDelegate = (MyAppDelegate *)[UIApplication sharedApplication].delegate;
[appDelegate transitionToViewController:anotherVC
withTransition:UIViewAnimationOptionTransitionFlipFromRight];
}
You can use Apple's new viewController containment system. For more in-depth information check out the WWDC 2011 session video "Implementing UIViewController Containment".
New to iOS5, UIViewController Containment allows you to have a parent viewController and a number of child viewControllers that are contained within it. This is how the UISplitViewController works. Doing this you can stack view controllers in a parent, but for your particular application you are just using the parent to manage the transition from one visible viewController to another. This is the Apple approved way of doing things and animating from one child viewController is painless. Plus you get to use all the various different UIViewAnimationOption transitions!
Also, with UIViewContainment, you do not have to worry, unless you want to, about the messiness of managing the child viewControllers during orientation events. You can simply use the following to make sure your parentViewController forwards rotation events to the child viewControllers.
- (BOOL)automaticallyForwardAppearanceAndRotationMethodsToChildViewControllers{
return YES;
}
You can do the following or similar in your parent's viewDidLoad method to setup the first childViewController:
[self addChildViewController:self.currentViewController];
[self.view addSubview:self.currentViewController.view];
[self.currentViewController didMoveToParentViewController:self];
[self.currentViewController.swapViewControllerButton setTitle:#"Swap" forState:UIControlStateNormal];
then when you need to change the child viewController, you call something along the lines of the following within the parent viewController:
-(void)swapViewControllers:(childViewController *)addChildViewController:aNewViewController{
[self addChildViewController:aNewViewController];
__weak __block ViewController *weakSelf=self;
[self transitionFromViewController:self.currentViewController
toViewController:aNewViewController
duration:1.0
options:UIViewAnimationOptionTransitionCurlUp
animations:nil
completion:^(BOOL finished) {
[aNewViewController didMoveToParentViewController:weakSelf];
[weakSelf.currentViewController willMoveToParentViewController:nil];
[weakSelf.currentViewController removeFromParentViewController];
weakSelf.currentViewController=[aNewViewController autorelease];
}];
}
I posted a full example project here: https://github.com/toolmanGitHub/stackedViewControllers. This other project shows how to use UIViewController Containment on some various input viewController types that do not take up the whole screen.
Good luck
OK, I know the question says without using a navigation controller, but no reason not to. OP wasn't responding to comments in time for me to go to sleep. Don't vote me down. :)
Here's how to pop the current view controller and flip to a new view controller using a navigation controller:
UINavigationController *myNavigationController = self.navigationController;
[[self retain] autorelease];
[myNavigationController popViewControllerAnimated:NO];
PreferencesViewController *controller = [[PreferencesViewController alloc] initWithNibName:nil bundle:nil];
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration: 0.65];
[UIView setAnimationTransition:UIViewAnimationTransitionFlipFromRight forView:myNavigationController.view cache:YES];
[myNavigationController pushViewController:controller animated:NO];
[UIView commitAnimations];
[controller release];
Since I just happened across this exact problem, and tried variations on all the pre-existing answers to limited success, I'll post how I eventually solved it:
As described in this post on custom segues, it's actually really easy to make custom segues. They are also super easy to hook up in Interface Builder, they keep relationships in IB visible, and they don't require much support by the segue's source/destination view controllers.
The post linked above provides iOS 4 code to replace the current top view controller on the navigationController stack with a new one using a slide-in-from-top animation.
In my case, I wanted a similar replace segue to happen, but with a FlipFromLeft transition. I also only needed support for iOS 5+. Code:
From RAFlipReplaceSegue.h:
#import <UIKit/UIKit.h>
#interface RAFlipReplaceSegue : UIStoryboardSegue
#end
From RAFlipReplaceSegue.m:
#import "RAFlipReplaceSegue.h"
#implementation RAFlipReplaceSegue
-(void) perform
{
UIViewController *destVC = self.destinationViewController;
UIViewController *sourceVC = self.sourceViewController;
[destVC viewWillAppear:YES];
destVC.view.frame = sourceVC.view.frame;
[UIView transitionFromView:sourceVC.view
toView:destVC.view
duration:0.7
options:UIViewAnimationOptionTransitionFlipFromLeft
completion:^(BOOL finished)
{
[destVC viewDidAppear:YES];
UINavigationController *nav = sourceVC.navigationController;
[nav popViewControllerAnimated:NO];
[nav pushViewController:destVC animated:NO];
}
];
}
#end
Now, control-drag to set up any other kind of segue, then make it a Custom segue, and type in the name of the custom segue class, et voilà!
I struggled with this one for a long time, and one of my issues is listed here, I'm not sure if you have had that problem. But here's what I would recommend if it must work with iOS 4.
Firstly, create a new NavigationController class. This is where we'll do all the dirty work--other classes will be able to "cleanly" call instance methods like pushViewController: and such. In your .h:
#interface NavigationController : UIViewController {
NSMutableArray *childViewControllers;
UIViewController *currentViewController;
}
- (void)transitionFromViewController:(UIViewController *)fromViewController toViewController:(UIViewController *)toViewController duration:(NSTimeInterval)duration animations:(void (^)(void))animations completion:(void (^)(BOOL))completion;
- (void)addChildViewController:(UIViewController *)childController;
- (void)removeChildViewController:(UIViewController *)childController;
The child view controllers array will serve as a store for all the view controllers in our stack. We would automatically forward all rotation and resizing code from the NavigationController's view to the currentController.
Now, in our implementation:
- (void)transitionFromViewController:(UIViewController *)fromViewController toViewController:(UIViewController *)toViewController duration:(NSTimeInterval)duration animations:(void (^)(void))animations completion:(void (^)(BOOL))completion
{
currentViewController = [toViewController retain];
// Put any auto- and manual-resizing handling code here
[UIView animateWithDuration:duration animations:animations completion:completion];
[fromViewController.view removeFromSuperview];
}
- (void)addChildViewController:(UIViewController *)childController {
[childViewControllers addObject:childController];
}
- (void)removeChildViewController:(UIViewController *)childController {
[childViewControllers removeObject:childController];
}
Now you can implement your own custom pushViewController:, popViewController and such, using these method calls.
Good luck, and I hope this helps!
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"Main" bundle:nil];
UINavigationController *viewController = (UINavigationController *)[storyboard instantiateViewControllerWithIdentifier:#"storyBoardIdentifier"];
viewController.modalTransitionStyle = UIModalTransitionStylePartialCurl;
[self presentViewController:viewController animated:YES completion:nil];
Try This Code.
This code gives Transition from a view controller to another view controller which having a navigation controller.
I realize this is a popular topic, and I've searched through many posts here but haven't found anything that has helped my issue. I'm a beginner, for what it's worth (as you'll see by my question :-)
My app has a tab bar with 3 items. The first loads a UINavigationController that is intended to have 3 "screens" to drill-down through (first: UITableView, second: filtered UITableView, third: UIView). I cannot, for the life of me, figure out how to show the UITableView on the 2nd screen, programmatically.
I'm overriding - (void)loadView since I'm not using IB. At different times, I've tried things like:
- (void)loadView
{
[super loadView];
// first option (thought for sure this would work)
[[self view] addSubview:secondTableView];
// another...
[self tableView:secondTableView];
// another...
[[[[self navigationController] topLevelController] view] addSubview:secondTableController];
}
I do have the table view setup properly with it's delegate and datasource, I just can't figure out how to show the damn thing. The 2nd controller is also inheriting from UITableViewController. Additionally, I don't know how you can say "fit this table view within the navigation title and the tab bar menu". I'm using CGRectMake() currently to guess the sizes, but it seems like there should be a better way (maybe that's why you use IB :-). Either way, that's secondary to even getting something to show up in the first place.
Thanks in advance for any suggestions.
If you're inheriting from UITableViewController your view should be a UITableView not a plain UIView, then when you want to create a view programmatically you should allocate it, initialize it, set its properties and assign it as your controller's view, I haven't tried [super loadView] before but it's creating the tableView for you and assigning dataSource and delegate to self, so you don't have to do that. So it should go like this
- (void)loadView {
UITableView *tempPointer = [[UITableView alloc] initWithFrame:CGRectMake(0, 0, 320, 480) andStyle:UITableViewStylePlain];
self.view = tempPointer; // controller retains view property so we should release it
[tempPointer release];
// If you want your view to be resized automatically (to fit)
self.view.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
self.tableView.dataSource = self;
self.tableView.delegate = self;
//...
}
or
- (void)loadView {
[super loadView];
self.tableView.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
secondTableView = self.tableView; // or change your code to cope with self.tableView instead of secondTableView...
//...
}
Clear out some uncertainties about view hierarchy creation, retaining etc in the documentation about both UIViewController and UITableViewController if you haven't already.
have you tried:
[Self.navigationController pushViewController:secondTableView];
Hope this helps, if not could you give a little more describing the situation.
I have a question about UIViewController's subview, I created a UIView subclass MainView, which has the exact size of the screen, I wonder which is a better way of adding MainView, consider the following factors:
1 As MainView has same size as the whole screen, the MainView itself may have subviews, but there is no views at the save level as MainView(ie I don't need to add other subviews to self.view).
2 If I use self.view = mainView, do I put the code in loadView(as the viewDidLoad method means the view(self.view) is already loaded)? I see the loadView method is commented out by default, if I add the code to this method, what other code do I need to put together(e.g. initialize other aspects of the application)?
3 If I add mainView via [self addSubview:mainView], are there actually two off screen buffer? One for self.view, one for mainView, both has same size as the screen and one is layered on top of the other(so it wastes memory)?
Thanks a lot!
I'm not sure I completely understand what you're asking, but I'll try to answer a few of the questions you have.
First of all, if you have multiple UIViews on the screen they are all loaded into memory. You have to do -removeFromSuperview and release them to get the memory back.
You can assign your UIView as the UIViewController's view. For example:
MainView *mainView = [[MainView alloc] initWithFrame:CGRectMake(320.0, 480.0)];
self.view = mainView;
[mainView release]; //since the .view property is a retained property
in that case, you have have the view's initialization code in the -init method. Just redefine it like:
- (id)initWithFrame:(CGRect)frame
{
if (self = [super initWithFrame:frame]) {
//initializations
}
return self;
}
You must implement loadView if you did initialize your view controller with a NIB.
UIViewController takes care of sizing its "main" view appropriately. This is all you need to do:
- (void)loadView
{
UIView* mainView = [[[UIView alloc] initWithFrame:CGRectZero] autorelease];
self.view = mainView;
}
I'd solve all of this by doing it in a xib! If you create a UIView in your xib, you can then change it's class (when you select the UIView there should be a text field in the Class Identity section of the Identity inspector* - type 'MainView' here!)
Then, create your view controller by calling
myViewController = [[MainViewController alloc] initWithNibName:#"MyNibName" bundle:nil];
That should solve your problems; it's the main subview of your view controller (directly accessable from self.view) and you don't need to worry about memory usage, there's only one view :)
Sam
NB * Click tools -> Identity Inspector. I didn't know it was called this until I had to write this answer!
Yes, the first code-snippet shown above is the "standard" approach, AFAIK, when not using (evil!) NIB files -- i.e. when alloc'ing your view in-code, via loadView.
Note it seems one can also get away with the following, instead of hard-coding the screen-rect size:
UIView *myView = [[UIView alloc] initWithFrame:[UIScreen mainScreen].applicationFrame];
self.view = myView;
[myView release];
Note you definitely want to do the [myView release] call since, indeed, as pointed out above, self.view (for UIView) is a retained property.
Cheers, -dk
Perhaps the most important thing to do is make sure you have the following:
self.view.userInteractionEnabled = YES;
While it might not be required all of the time, it fixes the issue where self.view is unresponsive. This issue pops up occasionally.
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];
}
}