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];
}
}
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];
}
}
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.
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.
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.
In a view based app, I display a view and once the user is finished with that view, Done is clicked and the view is removed. However, that does not dealloc the view, which is what I want to do. What is the proper way to dealloc this type of view?
Currently, I'm nil'ing out the second view before it is called. That seems to work and the second view is reinitialized. However, isn't it more appropriate for the second view to destroy itself (nil itself after removeFromSuperview)?
In first view:
//show next view
aView = nil;
if(aView == nil)
{
aView = [[AView alloc] initWithNibName:#"aView" bundle:nil];
}
[self.view addSubview: aView.view];
Click Done in aView
[self.view removeFromSuperview];
The method removeFromSuperview will automatically release "aView.view", so you shouldn't release aView in the first view controller. I think you've declared AView *aView in head file, but you don't need to. You may declare the aView as a local variable like this:
// go to second view
SecViewController *sec = [[SecViewController alloc] initWithNibName:#"SecView" bundle:nil];
[self.view addSubview:sec.view];
// go back
[self.view removeFromSuperview];
Immediately after
[self.view addSubview: aView.view];
You should add:
[aView release];
Your subview has been retained by your view controller's view so can be release.
If you previously set your viewController to first responder status (like to respond to motion events) the responder chain will retain the controller. You must resign the first responder to destroy the controller completely.
[self resignFirstResponder];
[self.view removeFromSuperview];
both of the above answers are correct theory for how you should memory manage in objective c.
as per the dev documentation:
http://developer.apple.com/iPhone/library/documentation/UIKit/Reference/UIView_Class/UIView/UIView.html#//apple_ref/occ/instm/UIView/removeFromSuperview
callling removeFromSuperview will actually call release FOR you, so you are okay, I think.