I have an iPhone application that's using Navigation Controller to display the top bar (with title and back button, and such...).
I added a UITabBar to the application window, that enables to switch between the parts of it. Instead of adding the tab bar to each of ViewController's view I added the bar to app window.
(When I had it in the ViewController, switching between controllers made the tab bar to swipe left/right, when animated pop/push occured, together with whole view).
So, I added the UITabBar to the MainWindow.xib, and tied it to the app delegate's variable. In my didFinishLaunchingWithOptions method, I added the following code:
[self.window addSubview:navigationController.view];
CGRect frame = navigationController.view.frame;
frame.size.height -= tabbar.frame.size.height;
navigationController.view.frame = frame;
tabbar.selectedItem = [tabbar.items objectAtIndex:0];
to resize the main (navigationController's) view, in order to make the TabBar visible.
The problem shows up when I rotate the device -- my view gets stretched to full window and I loose the ability to show the TabBar.
I added a - (void) didRotateFromInterfaceOrientation:(UIInterfaceOrientation) fromInterfaceOrientation method to my ViewController, with the following code:
- (void) didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation {
CGRect frame = self.view.frame;
frame.size.height -= [AppState shared].tabBar.frame.size.height;
//frame.origin.y = [AppState shared].tabBar.frame.size.height;
//frame.origin.x = 100;
self.view.frame = frame;
frame = [AppState shared].tabBar.frame;
frame.origin.y = [UIScreen mainScreen].bounds.size.height - frame.origin.y - frame.size.height;
[AppState shared].tabBar.frame = frame;
}
It resizes the view, and moves the tab bar to up/down part of the view (I allow only Portrait/Portrait upside down orientations here). The problem is, my TabBar is turned upside down as well, and also, it's no longer clickable.
It looks like the image below:
Anyone knows how to handle this kind of situation? Or, how to make the tab bar not tied to view controller, but also able to handle interface's rotation smoothly?
You are using the tabbar in an unintended way. You seem to be using the UITabBarView as an uncontrolled element of other views. That is not it's function.
The UITabBarView should be controlled directly by a UITabBarController which in turn should be controlling all the view controllers for the views displayed in the tabbar i.e. the tabbar controller is a type of navigation controller that controls subcontrollers.
Suppose you have three tabs and the third one is a navigation controller. Your controller hierarchy would look like this:
TabbarController:
-->tab1ViewController
-->tab2ViewController
-->tab3ViewController(UINavigationController):
-->rootViewController-->secondViewController
You are trying to move and manage the tabbar view without its controller and the proper controller hierarchy. That isn't going to work.
Related
I am trying to emulate the way TweetBot/NetBot animates the tabBar in after a push from the tableView of Accounts action. When the view is fully pushed, only then does the taBar animate in from the bottom. I have tried all sorts of hide/show methods and all seem to fail when it comes to the "show" part.
Does anyone have a suggestion as to how this can be done?
First of all, I presume you are not using a UITabViewController since it cannot be pushed into a UINavigationController stack, so I think you are using a standalone UITabBar embedded in a UIViewController. Is this assumption right?
Try with this code (I didn't try it).
- (void)viewDidAppear {
[super viewDidAppear];
// Calls showTabBar method after SOME_DELAY. You can also call directly [self showTabBar] if you want zero delay.
[self performSelector:#selector(showTabBar) afterDelay:SOME_DELAY];
}
- (void)showTabBar {
// Before the animation begins, your UITabBar must be outside the view controller's view frame.
CGRect tabBarFrame = CGRectMake(0,
CGRectGetHeight(self.view.bounds),
CGRectGetWidth(self.view.bounds),
CGRectGetHeight(self.tabBar.frame);
self.tabBar.frame = tabBarFrame;
// Let's start with the animation, setting a new frame for tab bar inside an animation block
[UIView animateWithDuration:ANIMATION_DURATION animations:^{
// Change origin Y. It assumes that the height of self.tabBar is right, otherwise put the height you want instead of CGRectGetHeight(self.tabBar.frame).
tabBarFrame.origin.y = CGRectGetHeight(self.view.bounds) - CGRectGetHeight(self.tabBar.frame);
self.tabBar.frame = tabBarFrame;
}];
}
I have a multiview app with a Toolbar. The Root view controller controls the toolbar, but the other views have their own view controller class. I use code like this to load the views:
-(IBAction)loadBand1Start:(id)sender{
[self clearView];
Band1Start *band1Controller = [[Band1Start alloc] initWithNibName:#"Band1Start" bundle:nil];
self.band1Start = band1Controller;
[band1Controller release];
[self.view insertSubview:band1Start.view atIndex:0];
if (UIDeviceOrientationIsLandscape([UIDevice currentDevice].orientation)) {
band1Start.view.frame=CGRectMake(0, 0, 480.0, 260.0);
} else {
band1Start.view.frame=CGRectMake(0, 0, 320.0, 400.0);
}
}
The IF statement is to make it load in the correct orientation. Without this, although it rotates ok, it always loads in Portrait, even if the phone is in landscape.
Autosize and Auto Rotate works. I used IB to build my nibs in portrait. When the phone is rotated to landscape, they do rotate and resize, however with some nibs, the rotated layout does not look right. There is overlaps etc, which is a common problem, and I know can be fixed using CGRectMake.
However, it does not matter where I put the CGRectCode, it does not execute. I have tried putting it in willRotateToInterfaceOrientation, willAnimateRotationToInterfaceOrientation, and viewWillAppear, but the rotated layout is not changed.
If I use the code below to switch views, the rotated layout is how I want it, but the toolbar does not appear:
-(IBAction)loadBand1Start:(id)sender{
[self clearView];
Band1Start *band1Controller = [[Band1Start alloc] initWithNibName:#"Band1Start" bundle:nil];
[self presentModalViewController:band1Controller animated:NO];
[band1Controller release];
}
I think this is because the view controller for the toolbar needs to be the root view controller for the toolbar to appear, but only the root view controller receives the orientation change notifications.
So if I keep my toolbar controller as the root view controller, the subview's controller does not receive the orientation notifications, so does not execute my CGRectMake code, but if I make the subview's controller the root view controller, it receives the notifications ok, but the toolbar does not appear because it's controller is not the root controller.
Am I correct about the problem here? Is there a way around it?
Apple's tab bar controller has a lot of limitations. One important limitation is that you can't modify the tab bar in a rejection safe mode. My tab bar has a simple sliding movements and it's multi row.
For those reasons I decided to build a TBVC from the beginning; everything seems to work correctly, but I'm really messing around with rotation. Every time that I change orientation main view frames are changed.
Here is my hierarchy from top to the container view:
-MainView--contains-->TabBarView+containerView
The containerView is the view used to contain views loaded from the other controllers.
Here is the -loadView method of my CustomTabBaViewController
- (void)loadView
{
UIView *theView=[[UIView alloc]initWithFrame:[[UIScreen mainScreen]bounds]];
theView.autoresizingMask=UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
theView.backgroundColor=[UIColor greenColor];
containerOfControllersView=[[UIView alloc] initWithFrame:theView.bounds];
containerOfControllersView.backgroundColor=[UIColor blueColor];
containerOfControllersView.autoresizingMask=UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
[theView addSubview:containerOfControllersView];
ideoloTabBar=[[IdeoloTabBar alloc]initWithNumberOfControllers:[controllers count]];
[theView addSubview:ideoloTabBar];
self.view=theView;
[theView release];
}
When I set a new view from another controller I use this method:
-(void)setCurrentViewWithView:(UIView*)theView{
if ([[self.containerOfControllersView subviews] count]>0) {
UIView *tagView=[self.containerOfControllersView viewWithTag:555];
tagView.tag=0;
[tagView removeFromSuperview];
}
theView.tag=555;
theView.autoresizingMask=UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
theView.frame=[[UIScreen mainScreen]applicationFrame];
[self.containerOfControllersView addSubview:theView];
[self.view bringSubviewToFront:ideoloTabBar];
}
As you can see the views from other view controllers are applied using the applicationFrame.
When I rotate the device happens something wrong, the mainview not only is resized according to the new orientation but also moved by 20px (status bar size) to the botton, thus leaving a gap between the status bar and the container view. Since I gave the mainview the screen bounds I can't understand with it should be moved.
UPDATE
I'm trying a different approach so I've modified the loadView like that:
- (void)loadView
{
[super loadView];
containerOfControllersView=[[UIView alloc] initWithFrame:self.view.bounds];
containerOfControllersView.backgroundColor=[UIColor blueColor];
containerOfControllersView.autoresizingMask=UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
self.view.backgroundColor=[UIColor greenColor];
[self.view addSubview:containerOfControllersView];
ideoloTabBar=[[IdeoloTabBar alloc]initWithNumberOfControllers:[controllers count]];
[self.view addSubview:ideoloTabBar];
}
And in the setCurrentViewWithView:(UIView*)theView I've modified the line with
theView.frame=self.view.bounds;
instead of using the applicationFrame.
NOW:
On iPhone when I try to load a modalView it cuts about 40px at the bottom
On iPad when I try to load a modalView it lefts 20px at the bottom, because 20px are under the status bar but wantsFullScreen is NO.
UPDATE 2
It seems that the presentModalViewController should be called from the root view controller. I will create a protocol and an abstract UIViewController subclass to implement it an load it correctly.
Any suggestion? work around?
I don't like the approach of creating an entirely custom TabBarController from scratch. I like to put a custom view on top of a real TabBar as a subview, and then pass all the button presses to the real TabBarController. This way you don't have to code a window manager yourself.
- (void)tabButtonPressed:(id)sender
{
UIButton *button = (UIButton *)sender;
if (button.tag == HomeButton)
self.tabBarController.selectedIndex = 0;
// etc.
}
This should also be rejection safe.
This has been addressed here: Application frame leaves blank line at the top
But, you could also specify your frame by subtracting 20 from y:
CGRect rect = [[UIScreen mainScreen] applicationFrame];
theView.frame = CGRectMake(rect.origin.x, rect.origin.y - 20, rect.size.width, rect.size.height);
It has been a while since I'm using my custom TabBarViewController with disappearing tabbar and it seems to work properly both on iPad and iPhone.
The main problem that I had was due to an incorrect assignment to the content view frame and probably to a wrong assumption that modalVC were loaded from the current view controller.
First point: the content view should use the bounds of the main view, here is a part of the loadView method of the Root View Controller:
[super loadView];
containerOfControllersView=[[UIView alloc] initWithFrame:self.view.bounds];
Second:before add as a subview a view of a view controller remark to it that its frame should have the same bounds of its new parent view.
theView.frame =self.view.bounds;
Third: modal view controllers should be loaded from the root view controller or the will never have correct size. That's why I've implemented a base abstract class for each view controllers that inherit a protocol that manage the presetation and dismissing of modal viewcontrollers.
Hope this helps someone else.
Andrea
I am presenting a modal view controller. If it matters, it is scrolling up from the bottom. How can I control what portion of the screen it occupies?
EDIT: I have the following in the modal view controller. It's not helping.
- (void)viewDidLoad {
TestResultView *trv = [[TestResultView alloc]initWithTest: [Model m].currentTest];
self.view = trv;
trv.frame = CGRectMake(0, 320, 320, 160);
[trv release];
[super viewDidLoad];
}
You can modify the frame of the view controller, but if you're using UIViewController's -presentModalViewController:animated: method, the view behind will be unloaded once your modal view is finished animating onto the screen (This assumes you're on an iPhone) and you'll see a white screen where your background view should be. iOS assumes that your modal view controller will be a full-screen view controller, and dumps the other view to save memory.
If you really want to show a view over part of the screen, you should instead add the UIView (no UIViewController) to your current UIViewController's view as a subview, and then animate it onscreen yourself. I think something like this would work in your UIViewController class that will present the view:
// Add the view as a subview and position it offscreen just below the current view
UIView *myHalfView = [[UIView alloc] initWithFrame:someAppropriateFrame];
[self.view addSubview:myHalfView];
CGRect offScreenFrame = myHalfView.bounds;
offScreenFrame.origin = CGPointMake(0.0, CGRectGetMaxY(self.view.frame));
// Now animate the view upwards
[UIView beginAnimations:nil context:nil];
// Move the view upwards the height of your sliding view so it's entirely onscreen
myHalfView.center = CGPointMake(myHalfView.center.x, myHalfView.center.y - myHalfView.bounds.size.height);
[UIView commitAnimations];
[myHalfView release];
For bonus points, you could fade the view in by setting
myHalfView.alpha = 0.0;
before the UIView animation block, and setting
myHalfView.alpha = 1.0;
inside the block after animating the center property.
When you're done, you can do something similar but in reverse to slide the view offscreen. You can add an animationDidStop selector to the UIView animation block to be notified when the view has slid off screen so that you can remove it from the view hierarchy.
From an aesthetic point of view, you should also be careful how you do this since having a view slide up is a standard behavior, and if your view looks like a normal view but stops halfway, users may feel (even briefly) that the app has frozen. They'll figure it out, but it will leave a bad feeling about your app if not handled carefully. Mainly, I would avoid using standard full-screen cues like including a UINavigationController at the top of your view to help users understand what's going on. Half-sheets tend to be UIActionSheets on the iPhone, so think in that direction.
That is nice, the above accepted answer explains a nice hack to present subViews which feel like ModalViews, but what if it is an iPad, and i can indeed give it a modalViewController which doesnt cover the entire screen.
In case of iPads, I dont think the underneath view will be unloaded. ( because there are options where we can present the modalView on iPads, which dont cover the entire screen )
ModalViewController in the end is a controller itself, and like any other controller has a root view, whose properties can be editted, if we can get hold of it.
Here is what will give you a custom frame of the ModalView :
MyViewController *viewController = [[MyViewController alloc] init];
viewConroller.modalPresentationStyle = UIModalPresentationFormSheet;
[self presentModalViewController:viewController animated:YES];
//superView of viewController's view is modalViewController's view, which we were after
viewController.view.superview.frame = CGRectMake(x,y,w,h);
//x y w h - can have desired values.
I would add to #dsaw's answer that the superview of the modal view does not seem to rotate its coordinate system in landscape mode. Here is the code that I used in my own app:
MyViewController* modalVC = [[MyViewController alloc] init];
modalVC.modalPresentationStyle = UIModalPresentationFormSheet;
[self presentModalViewController:modalVC animated:NO];
CGRect r = CGRectMake(self.view.bounds.size.width/2 - 236,
self.view.bounds.size.height/2 - 130,
472, 260);
r = [self.view convertRect:r toView:modalVC.view.superview.superview];
modalVC.view.superview.frame = r;
While the superview may not rotate itself with the iPad, it does seem to do the right thing and keep the modal view centered if I rotate the iPad after showing the modal view.
Here is my setup: one navigation controller, two views
The first view, the rootview, displays the statusbar and navigation bar (portrait view).
When the rootviewController senses rotation, it hides the nav bar and status bar and then it pushes view 2 onto the navigation controller (in landscape now).
This part works as expected.
When View2 viewcontroller senses rotation (back to portrait), it pops itself of the navigation controller, revealing view 1. View 1 then unhides the status bar and nav bar.
From here, it gets weird. Depending on when I unhide the navigation bar, I get strange results. As you can see below (The beaker photo should be just below the navbar at the top of the screen).
I have tried unhiding the navigation bar in:
viewWillRotate/viewDidRotate of view 2
viewWillAppear/viewDidAppear of view 1
poptoRootView in the navigation controller (I subclassed just to try)
Nothing works. Any Idea what is going on? This should be simple, but maybe I am doing things in the wrong places.
Check your autoresizing mask on all views in your nib and make sure its all set properly. If you have anything the way its not supposed to be it will freak out on rotation.
Not sure why it's necessary, but when you're swaping views, you must apply a transformation to your view (with only 1 view the iPhone does this for you), and you must set the bounds of it.
Here is the code that should work for you (on the willAnimateFirstHalfOfRotationToInterfaceOrientation):
#define degreesToRadians(x) (M_PI * (x) / 180.0)
if (UIInterfaceOrientationIsLandscape(toInterfaceOrientation)) {
self.view = landscapeView;
self.view.transform = CGAffineTransformIdentity;
self.view.transform = CGAffineTransformMakeRotation(degreesToRadians(90));
self.view.bounds = CGRectMake(0.0, 0.0, 480.0, 320);
} else {
self.view = portraitView;
self.view.transform = CGAffineTransformIdentity;
self.view.transform = CGAffineTransformMakeRotation(degreesToRadians(0));
self.view.bounds = CGRectMake(0.0, 0.0, 300, 480);
}
In the end, I reworked my app. (to use a modal view controller)
But I came into similar issues, instead white space where the status bar was located.
I think both problems can be attributed to not talking to the Navigation Controller when rotating/resizing views (instead I was talking to the ViewController.
For details of how I solved the problem:
iPhone + CGAffineTransFormRotate(pi/2) + statusBarHidden:YES + presentModalViewController = 20 pixels of white space