Iphone: Is it possible to hide the TabBar? (Pre-iOS 8) - iphone

I have an application that uses a UITabBarController to switch between modes. When in a certain mode, I'd like to hide the tab bar until the steps of that mode have been completed. Note that I'm not using a navigation controller so I can't use the setHidesBottomBarWhenPushed method on the navigation controller to hide the tab bar.
Prior to iOS 8, When I attempt to hide the tarbar using:
self.tabBarController.tabBar.hidden = YES
the tab bar goes away, but it leaves a 50 pixel blank area at the bottom of the screen where the tab bar used to be. I can't seem to figure out how to fill that area. Anything in the UI that is in that area is clipped and cannot be seen.
Any ideas if this is even possible? I'd really like to stay away from the navigation controller.

Here's my code for that:
This is, of course, mucking with the goings on in the controller's view hierarchy. It could change/break. This uses defined APIs, so Apple won't care, but they won't care about breaking your code, either.
- (void)hideTabBar {
UITabBar *tabBar = self.tabBarController.tabBar;
UIView *parent = tabBar.superview; // UILayoutContainerView
UIView *content = [parent.subviews objectAtIndex:0]; // UITransitionView
UIView *window = parent.superview;
[UIView animateWithDuration:0.5
animations:^{
CGRect tabFrame = tabBar.frame;
tabFrame.origin.y = CGRectGetMaxY(window.bounds);
tabBar.frame = tabFrame;
content.frame = window.bounds;
}];
// 1
}
- (void)showTabBar {
UITabBar *tabBar = self.tabBarController.tabBar;
UIView *parent = tabBar.superview; // UILayoutContainerView
UIView *content = [parent.subviews objectAtIndex:0]; // UITransitionView
UIView *window = parent.superview;
[UIView animateWithDuration:0.5
animations:^{
CGRect tabFrame = tabBar.frame;
tabFrame.origin.y = CGRectGetMaxY(window.bounds) - CGRectGetHeight(tabBar.frame);
tabBar.frame = tabFrame;
CGRect contentFrame = content.frame;
contentFrame.size.height -= tabFrame.size.height;
}];
// 2
}
Edit:
An anonymous user has suggested the following addition for 7.0 (i have not tested this, and could not say whether it is a workaround or an ideal implementation):
// 1. To Hide the black line in IOS7 only, this extra bit is required
if (SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(#"7.0")) {
[self.tabBarController.tabBar setTranslucent:YES];
}
// 2. For IOS 7 only
if (SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(#"7.0")) {
[self.tabBarController.tabBar setTranslucent:NO];
}
Edit: Entirely untested in 8.x and likely lacking in some layouts.

Like Steve, I haven't found a clean way to do this (even though Apple Photopicker does something similar). Here is what I have done:
if (systemAction)
{
// Reveal tab bar back
CGRect bounds = [[UIScreen mainScreen] bounds];
CGRect tabBarFrame = self.tabBarController.tabBar.frame;
self.tabBarController.view.frame = CGRectMake(0,0,bounds.size.width,bounds.size.height);
self.toolBar.hidden = YES;
systemAction = NO;
}
else
{
//hide tab bar
CGRect bounds = [[UIScreen mainScreen] bounds];
CGRect tabBarFrame = self.tabBarController.tabBar.frame;
CGRect navigationBarFrame = self.navigationController.navigationBar.frame;
self.tabBarController.view.frame = CGRectMake(0,0,bounds.size.width,bounds.size.height+tabBarFrame.size.height);
self.toolBar.hidden = NO;
CGRect frame = self.toolBar.frame;
frame.origin.y = bounds.size.height - frame.size.height - navigationBarFrame.size.height;
self.toolBar.frame = frame;
systemAction = YES;
}
What it is doing is pushing the view down so I can display a toolbar (and not hiding it). Obviously this is for only the 'root view' of a tabbar + navigation controller. For any subsequent views you can set the 'hidesBottomBarWhenPushed' on the viewcontroller you are pushing.

I tried a number of the solutions above, but no joy in iOS 8. I find that setting in viewWillAppear the following works for me. Should work in iOS 7 as the extendedLayoutIncludesOpaqueBars was introduced then.
self.extendedLayoutIncludesOpaqueBars = true
self.tabBarController?.tabBar.isHidden = true
self.tabBarController?.tabBar.isOpaque = true
and if you need to turn tabBars on again when you leave to use the following in viewWillDisappear.
self.tabBarController?.tabBar.isHidden = false
self.tabBarController?.tabBar.isOpaque = false
I use this to allow a return from a transition to keep the TabBar hidden. Not used it in a button action but if like me you find nothing above now works, this could be the basis of a programmable solution.

It's a bit late in the day, but of all the answers to the question that I've trawled through this afternoon, this is the one that worked best for me.
How to hide uitabbarcontroller
// Method call
[self hideTabBar:self.tabBarController];
// Method implementations
- (void)hideTabBar:(UITabBarController *) tabbarcontroller
{
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:0.5];
for(UIView *view in tabbarcontroller.view.subviews)
{
if([view isKindOfClass:[UITabBar class]])
{
[view setFrame:CGRectMake(view.frame.origin.x, 480, view.frame.size.width, view.frame.size.height)];
}
else
{
[view setFrame:CGRectMake(view.frame.origin.x, view.frame.origin.y, view.frame.size.width, 480)];
}
}
[UIView commitAnimations];
}
- (void)showTabBar:(UITabBarController *) tabbarcontroller
{
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:0.5];
for(UIView *view in tabbarcontroller.view.subviews)
{
NSLog(#"%#", view);
if([view isKindOfClass:[UITabBar class]])
{
[view setFrame:CGRectMake(view.frame.origin.x, 431, view.frame.size.width, view.frame.size.height)];
}
else
{
[view setFrame:CGRectMake(view.frame.origin.x, view.frame.origin.y, view.frame.size.width, 431)];
}
}
[UIView commitAnimations];
}

I use only this single line to achieve this. I use prepareForSegue method before showing the view controller having the tab bar.
-(void) prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender{
if([segue.identifier isEqualToString:#"showLogin"]){
[segue.destinationViewController setHidesBottomBarWhenPushed:YES];
}
}

I had worked on almost the same case, actually used the code from http://www.developers-life.com/hide-uitabbarcontrolleruitabbar-with-animation.html and made it better according to my needs, this might help others too.
I am using a UISplitViewController as the root view controller and its detail portion is a UITabBarController, I had to hide the tabbar in portrait mode:
// In UITabBarController's custom implementation add following method,
// this method is all that will do the trick, just call this method
// whenever tabbar needs to be hidden/shown
- (void) hidetabbar:(NSNumber*)isHidden {
UITabBarController *tabBarController=self;
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:0.5];
CGRect tabbarFrame=CGRectZero;
for(UIView *theView in tabBarController.view.subviews) {
//NSLog(#"%#", view);
if([theView isKindOfClass:[UITabBar class]]) {
tabbarFrame=theView.frame;
if ([isHidden boolValue]) {
tabbarFrame=CGRectMake(tabbarFrame.origin.x,
tabBarController.view.frame.size.height,
tabbarFrame.size.width,
tabbarFrame.size.height);
} else {
tabbarFrame=CGRectMake(tabbarFrame.origin.x,
tabBarController.view.frame.size.height - tabbarFrame.size.height,
tabbarFrame.size.width,
tabbarFrame.size.height);
}
theView.frame=tabbarFrame;
break;
}
}
for(UIView *theView in tabBarController.view.subviews) {
if(![theView isKindOfClass:[UITabBar class]]) {
CGRect theViewFrame=theView.frame;
if ([isHidden boolValue]) {
theViewFrame=CGRectMake(theViewFrame.origin.x,
theViewFrame.origin.y,
theViewFrame.size.width,
theViewFrame.size.height + tabbarFrame.size.height);
} else {
theViewFrame=CGRectMake(theViewFrame.origin.x,
theViewFrame.origin.y,
theViewFrame.size.width,
theViewFrame.size.height - tabbarFrame.size.height);
}
theView.frame=theViewFrame;
}
}
[UIView commitAnimations];
}
I used following code to call the hidetabbar: method
//In my UISplitViewController's custom implementation
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
#synchronized(self){
//change the self.splitDetailController to your UITabBarController's object
[self.splitDetailController
performSelector:#selector(hidetabbar:)
withObject:[NSNumber numberWithBool:UIInterfaceOrientationIsLandscape(interfaceOrientation)]
afterDelay:0.5];
}
return YES;
}
I tested this code to work in simulator only, let me know if it works on device too ;-)

Do you have the autoResizingMask set on the sub view?
view.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
Something like that should do the trick and allow the view sitting atop the stack to re-size.

The obvious solution, keeping your original architecture, would have been to present that view modally:
- (void)tabBarController:(UITabBarController *)tb
didSelectViewController:(UIViewController *)vc {
if (tb.selectedIndex == MODALONE) {
UIViewController* mod =
[[UIViewController alloc] initWithNibName: #"ModalView"
bundle: nil];
[tb presentModalViewController:mod animated:NO];
[mod release];
}
}
The view now covers the entire screen (except for the status bar is there is one) including the tab bar, so it looks as if the tab bar has gone away in response to the user pressing that tab bar item.

autoresizing mask has an enumeration. Try to set all the options and check if autoresize subviews option is checked in parent view

You can create Tabbar Category and show/Hide easily. and you can access full view.
create category #import "UITabBarController+HideTabBar.h"
#implementation UITabBarController (HideTabBar)
- (void)hideTabBarAnimated:(BOOL)animated
{
CGRect statusbarFrame = [UIApplication sharedApplication].statusBarFrame;
CGRect tabBarControllerFrame = self.view.frame;
if (statusbarFrame.size.height>20)
{
tabBarControllerFrame.size.height = screenSize.size.height + self.tabBar.frame.size.height - 20.0;
}
else
{
tabBarControllerFrame.size.height = screenSize.size.height + self.tabBar.frame.size.height ;
}
if (animated) {
[UIView animateWithDuration:0.2 animations:^{
[self.view setFrame:tabBarControllerFrame];
} completion:^(BOOL finished) {
}];
}
else
[self.view setFrame:tabBarControllerFrame];
}
- (void)showTabBarAnimated:(BOOL)animated {
CGRect statusbarFrame = [UIApplication sharedApplication].statusBarFrame;
CGRect tabBarControllerFrame = self.view.frame;
if (statusbarFrame.size.height>20)
{
tabBarControllerFrame.size.height = screenSize.size.height - 20.0;
}
else
{
tabBarControllerFrame.size.height = screenSize.size.height ;
}
if (animated) {
[UIView animateWithDuration:0.2 animations:^{
[self.view setFrame:tabBarControllerFrame];
} completion:^(BOOL finished) {
}];
}
else
[self.view setFrame:tabBarControllerFrame];
}
#end
Note : use statusbarFrame is used when hotspot or call is ON so tabbar would not cut down.
Now Import category in which you class you want to use methods and just call below methods to hide or show tabbar.
[self.tabBarController hideTabBarAnimated:YES];
[self.tabBarController showTabBarAnimated:YES];
Hope this Helps.

Hope this works.
#interface UITabBarController (Additions)
-(void)setTabBarHidden:(BOOL)hidden animated:(BOOL)animated;
#end
#implementation UITabBarController (Additions)
-(void)setTabBarHidden:(BOOL)hidden animated:(BOOL)animated
{
if (animated)
{
[UIView beginAnimations:nil context:nil];
}
if (hidden)
{
self.tabBar.frame = CGRectMake(self.tabBar.frame.origin.x, self.tabBar.superview.frame.size.height, self.tabBar.bounds.size.width, self.tabBar.bounds.size.height);
}
else
{
self.tabBar.frame = CGRectMake(self.tabBar.frame.origin.x, self.tabBar.superview.frame.size.height - self.tabBar.frame.size.height + 10, self.tabBar.bounds.size.width, self.tabBar.bounds.size.height);
}
if (animated)
{
[UIView commitAnimations];
}
}

Here is my solution (my tab view controller is inside navigation controller for good measure)... So I have subclassed UITabBarController and did this... exposing -setTabBarHidden: method
- (void)setTabBarHidden:(BOOL)hidden {
_tabBarHidden = hidden;
[UIView performWithoutAnimation:^{
[self adjustViews];
}];
}
- (void)adjustViews {
if ( _tabBarHidden ) {
CGRect f = self.tabBar.frame;
// move tab bar offscreen
f.origin.y = CGRectGetMaxY(self.view.frame);
self.tabBar.frame = f;
// adjust current view frame
self.selectedViewController.view.frame = self.view.frame;
} else {
CGRect f = self.tabBar.frame;
// move tab bar on screen
f.origin.y = CGRectGetMaxY(self.view.frame) - (CGRectGetMaxY(self.tabBar.bounds) + CGRectGetMaxY(self.navigationController.navigationBar.frame));
self.tabBar.frame = f;
// adjust current view frame
f = self.view.bounds;
f.size.height -= CGRectGetMaxY(self.tabBar.bounds);
self.selectedViewController.view.frame = f;
}
}
- (void)viewWillLayoutSubviews {
[super viewWillLayoutSubviews];
[UIView performWithoutAnimation:^{
[self adjustViews];
}];
}
- (void)viewDidLayoutSubviews {
[super viewDidLayoutSubviews];
[UIView performWithoutAnimation:^{
[self adjustViews];
}];
}

put the statement in the init method of the UIViewController
override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
self.hidesBottomBarWhenPushed = true
setupDependencyConfigurator()
}

See this thread:
Show/Hide TabBarController in iphone
In summary, you can see an example of this behavior in this sample code:
http://developer.apple.com/iphone/library/samplecode/TheElements/index.html

Why are you not using a navigation controller. It's a lot easier to hide the nav bar than the tab bar...

Just made the following code in Monotouch inside a subclass of UITabBarController:
public void ShowTabBar()
{
UIView.BeginAnimations("Anim");
UIView.SetAnimationDuration(0.25f);
this.View.Subviews[0].Frame = new RectangleF(0f, 0f, 320f, 431f);
this.TabBar.Frame = new RectangleF(0f, 431f, 320f, 49f);
this.TabBar.Hidden = false;
UIView.CommitAnimations();
}
public void HideTabBar()
{
UIView.BeginAnimations("Anim");
UIView.SetAnimationDuration(0.25f);
this.View.Subviews[0].Frame = new RectangleF(0f, 0f, 320f, 480f);
this.TabBar.Frame = new RectangleF(0f, 481f, 320f, 510f);
this.TabBar.Hidden = true;
UIView.CommitAnimations();
}

Related

Prevent some view controller in a navigation controller from rotating

I have a navigation controller which displays two view controllers at once. They are layered and the front most controller can be dragged down to reveal the controller below.
My app delegate is configured so the app will only allow interface orientations when the view in the back is being revealed, as seen below.
- (NSUInteger)application:(UIApplication *)application supportedInterfaceOrientationsForWindow:(UIWindow *)window
{
if (self.revealNavigationController.isViewRevealed)
{
return UIInterfaceOrientationMaskAllButUpsideDown;
}
return UIInterfaceOrientationMaskPortrait;
}
Naturally this causes both the front and the back view controller to rotate when the device is rotated but I am only interested in rotating the back view controller.
Is there any way to completely disable rotation in the front most view controller?
You need to use the view controller containment APIs added in iOS5. Basically, what you need to do is remove your one view controller when you no longer want it to participate in the rotation events, and add it back when its ready again. A sample would be...
#implementation AXRotationDemoViewController
- (id)init
{
if ((self = [super init]))
{
self.oneViewController = [[AXLoggingViewController alloc] init];
self.oneViewController.interfaceOrientations = UIInterfaceOrientationMaskPortrait;
self.twoViewController = [[AXLoggingViewController alloc] init];
self.twoViewController.interfaceOrientations = UIInterfaceOrientationMaskAllButUpsideDown;
}
return self;
}
- (void)viewDidLoad
{
[self.oneViewController willMoveToParentViewController:self];
[self addChildViewController:self.oneViewController];
[self.view addSubview:self.oneViewController.view];
[self.oneViewController didMoveToParentViewController:self];
[self.twoViewController willMoveToParentViewController:self];
[self addChildViewController:self.twoViewController];
[self.view addSubview:self.twoViewController.view];
[self.twoViewController didMoveToParentViewController:self];
self.oneViewController.view.backgroundColor = [UIColor redColor];
self.twoViewController.view.backgroundColor = [UIColor greenColor];
UIButton * showBackButton = [UIButton buttonWithType:UIButtonTypeRoundedRect];
[showBackButton setTitle:#"Toggle Back/Front" forState:UIControlStateNormal];
[showBackButton addTarget:self action:#selector(_toggle:) forControlEvents:UIControlEventTouchUpInside];
[showBackButton sizeToFit];
showBackButton.center = self.view.center;
[self.view addSubview:showBackButton];
}
- (void)_toggle:(id)sender
{
if ([self.childViewControllers containsObject:self.oneViewController])
{
[self.oneViewController.view removeFromSuperview];
[self.oneViewController removeFromParentViewController];
}
else
{
[self.oneViewController willMoveToParentViewController:self];
[self addChildViewController:self.oneViewController];
[self.view addSubview:self.oneViewController.view];
[self.oneViewController didMoveToParentViewController:self];
UIWindow * window = self.view.window;
UIViewController * hack = window.rootViewController;
window.rootViewController = nil;
window.rootViewController = hack;
}
}
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
CGRect halfScreenFrame = self.view.bounds;
halfScreenFrame.size.height /= 2;
self.oneViewController.view.frame = halfScreenFrame;
self.twoViewController.view.frame = CGRectOffset(halfScreenFrame, 0, halfScreenFrame.size.height);
}
- (NSUInteger)supportedInterfaceOrientations
{
if ([self.childViewControllers containsObject:self.oneViewController])
return self.oneViewController.supportedInterfaceOrientations;
return self.twoViewController.supportedInterfaceOrientations;
}
#end
However you may notice, there is no official way to really tell iOS you've changed the value of supportedInterfaceOrientations, so your best bet is either a) use a hack like in my example above to force iOS to redo everything or b) don't let the user close the back view until the device is already in portrait again.
Here is the sample I put together if that doesn't explain things well enough.
Yes, take a look at the -supportedInterfaceOrientations method in UIViewController. You return a UIInterfaceOrientationMask bitmask that specifies the orientations you want that UIViewController subclass to support.
The solution suggested by axiixc is generally better so depending on your problem, you should look at his answer. However, this did not solve my problem. I had a UINavigationController in which I inserted the view of a view controller as subview at index 0. This way, the view would be in the back. I had then made the UINavigationBar draggable so that when it dragged down, it would reveal the view in the back. The front most view would then move with the navigation bar.
I did not manage to get this to work with the view controller containment API introduced iOS 5, as suggested by axiixc.
Instead, I removed the view from the navigation controller and added it directly to the UIWindow when the app was launched and I handled the rotation of this view myself and disabled rotation on all other views (that is, I disabled it on the navigation controller).
This is how I added the view in -application:didFinishLaunchingWithOptions:
self.revealViewController = [[RevealViewController alloc] init];
[self.window insertSubview:self.revealViewController.view atIndex:0];
Then, right after that I registered for UIDeviceOrientationDidChangeNotification.
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(didRotate:) name:UIDeviceOrientationDidChangeNotification object:nil];
My didRotate: looks like this: (this is inspired by another answer on StackOverflow which I am now unable to find again, sorry)
- (void)didRotate:(NSNotification *)notification
{
// Only rotate if the view is revealed
if (self.revealNavigationController.isViewRevealed)
{
UIDeviceOrientation orientation = [UIDevice currentDevice].orientation;
[self updateForDeviceOrientation:orientation animated:YES];
}
}
I call -updateForDeviceOrientation:animated:
- (void)updateForDeviceOrientation:(UIDeviceOrientation)orientation animated:(BOOL)animated
{
CGFloat degrees = 0.0f;
switch (orientation)
{
case UIDeviceOrientationLandscapeLeft:
degrees = 90.0f;
break;
case UIDeviceOrientationLandscapeRight:
degrees = -90.0f;
break;
case UIDeviceOrientationPortrait:
degrees = 0.0f;
break;
default:
break;
}
CGFloat duration = (animated) ? [UIApplication sharedApplication].statusBarOrientationAnimationDuration : 0.0f;
__weak typeof(self) weakSelf = self;
[UIView animateWithDuration:duration animations:^{
// Rotate view
weakSelf.revealViewController.view.transform = CGAffineTransformIdentity;
weakSelf.revealViewController.view.transform = CGAffineTransformMakeRotation(DEGREES_TO_RADIANS(degrees));
// Resize view for rotation
CGFloat width, height;
if (UIDeviceOrientationIsLandscape(orientation))
{
width = MAX(CGRectGetWidth(weakSelf.revealViewController.view.bounds), CGRectGetHeight(weakSelf.revealViewController.view.bounds));
height = MIN(CGRectGetWidth(weakSelf.revealViewController.view.bounds), CGRectGetHeight(weakSelf.revealViewController.view.bounds));
}
else
{
width = MIN(CGRectGetWidth(weakSelf.revealViewController.view.bounds), CGRectGetHeight(weakSelf.revealViewController.view.bounds));
height = MAX(CGRectGetWidth(weakSelf.revealViewController.view.bounds), CGRectGetHeight(weakSelf.revealViewController.view.bounds));
}
CGSize newSize = CGSizeMake(width, height);
CGRect viewBounds = weakSelf.revealViewController.view.bounds;
viewBounds.size = newSize;
weakSelf.revealViewController.view.bounds = viewBounds;
CGRect viewFrame = weakSelf.revealViewController.view.frame;
viewFrame.size = newSize;
weakSelf.revealViewController.view.bounds = viewFrame;
}];
}

Present Modal View with Animation Effect

In my iPhone App I need to display a modal view with transparent background and it should appear with animation like it is appearing from center of view and its size is increasing.
similar like "drawing something" iPhone App when we click on settings button.
How do I do this?
You can do one of 4 following transition styles:
viewController.modalTransitionStyle = UIModalTransitionStyleCoverVertical;
viewController.modalTransitionStyle = UIModalTransitionStyleCrossDissolve;
viewController.modalTransitionStyle = UIModalTransitionStyleFlipHorizontal;
viewController.modalTransitionStyle = UIModalTransitionStylePartialCurl;
[self presentModalViewController:viewController animated:YES];
If you want something that is not included in these defaults you are going to have to build your own custom animation for presenting the modal view. Like the following but obviously for the style you want.
UIModalTransitionStyle horizontal movement
Let's say you have a viewController thats called aScoreSheet that you want to present. Try to define this method in the view controller that's going to do the presenting.
-(void) presentTransparentModalViewController: (ScoreSheet *) aViewController
{
scoreSheet = aViewController;
UIView *view = aViewController.view;
view.opaque = NO;
[view.subviews enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
UIView *each = obj;
each.opaque = NO;
}];
[self.view addSubview:view];
view.center = CGPointMake(160, 800); //for iPhone
[UIView animateWithDuration:0.9 delay:0 options:UIViewAnimationCurveEaseInOut animations:^{
view.center = CGPointMake(160, 240);
} completion:^(BOOL finished) {
self.view.userInteractionEnabled=YES;
}];
}
and then to dismiss the controller:
-(void) dismissTransparentModalViewControllerAnimated:(BOOL) animated{
if (animated) {
[UIView animateWithDuration:0.4
animations:^{
scoreSheet.view.center = CGPointMake(scoreSheet.view.center.x, scoreSheet.view.center.y + 480);
} completion:^(BOOL finished) {
[scoreSheet.view removeFromSuperview];
scoreSheet = nil;
}];
}
}
Not a full answer, but maybe you can take a look at this open source library:
https://github.com/Split82/HMGLTransitions
It has some custom modal transitions, maybe not exactly the one you are looking for, but you can easily add your transition by subclassing HMGLTransition.
Hope this helps

Unclear on how to properly remove a UIViewController from another UIViewController

thank you for your time, (As a side note, this question pertains mostly to iOS 4.2.3, I am aware some of these issues could be resolved with moving the code base to iOS 5, however we would like to release this app for phones running iOS 4 as well.)
I have a "MasterViewController" that is in charge of calling and dismising other UIVIewControllers.
First we trigger a new one:
In MasterViewController.m
-(IBAction)triggerPrime:(id)sender {
[self clearHomeScreen];
NSUInteger randomNumber = arc4random() % 2;
if (randomNumber == 0) {
self.flashTextViewIsDisplayed = NO;
ThinkOfViewController *thinkVC = [[ThinkOfViewController alloc] initWithNibName:#"ThinkOfViewController" bundle:nil];
self.thinkOfViewController = thinkVC;
[thinkVC release];
self.picturePrimeViewIsDisplayed = YES;
[self.view addSubview:self.thinkOfViewController.view];
}
else if (randomNumber == 1) {
self.picturePrimeViewIsDisplayed = NO;
FlashTextPrimeViewController *flashVC = [[FlashTextPrimeViewController alloc] initWithNibName:#"FlashTextPrimeViewController" bundle:nil];
self.flashTextPrimeViewController = flashVC;
[flashVC release];
self.flashTextViewIsDisplayed = YES;
[self.view addSubview:self.flashTextPrimeViewController.view];
}
Let's say that our randomNumber is 0, and it adds the ThinkOfViewController to the subview, (This is a very basic screen, it essentially displays some text with some assets animating:
In ThinkOfViewController.m
- (void)viewDidLoad {
[super viewDidLoad];
self.thinkOf.alpha = 0.0;
self.dot1.alpha = 0.0;
self.dot2.alpha = 0.0;
self.dot3.alpha = 0.0;
self.background.alpha = 0.0;
[self animateViews];
}
-(void)animateViews {
[UIView animateWithDuration:0.25 animations:^ {
self.background.alpha = 1.0;
}completion:^(BOOL finished) {
[UIView animateWithDuration:0.75 delay:0.00 options:UIViewAnimationCurveEaseIn animations:^ {
self.thinkOf.alpha = 1.0;
}completion:^(BOOL finished) {
[UIView animateWithDuration:0.20 delay:0.60 options:UIViewAnimationCurveEaseIn animations:^ {
self.dot1.alpha = 1.0;
}completion:^(BOOL finsihed) {
[UIView animateWithDuration:0.20 delay:0.60 options:UIViewAnimationCurveEaseIn animations:^ {
self.dot2.alpha = 1.0;
}completion:^(BOOL finished) {
[UIView animateWithDuration:0.20 delay:0.60 options:UIViewAnimationCurveEaseIn animations:^ {
self.dot3.alpha = 1.0;
}completion:^(BOOL finished) {
[UIView animateWithDuration:0.50 delay:0.60 options:UIViewAnimationCurveEaseInOut animations:^{
self.view.alpha = 0.0;
}completion:^(BOOL finished) {
NSLog(#"all animations done");
[[NSNotificationCenter defaultCenter] postNotificationName:#"removeThinkOfView" object:nil];
}];
}];
}];
}];
}];
}];
}
As you can see, once the animation sequence is finished, I post a notification to NSNotificationCenter (which resides in the MasterViewController) to remove this viewController.
In MasterViewController.m
-(void)removeThinkOfView {
[self.thinkOfViewController.view removeFromSuperview];
[self showPicturePrime];
}
-(void)showPicturePrime {
if (self.picturePrimeViewController == nil) {
PicturePrimeViewController *pVC = [[PicturePrimeViewController alloc] initWithNibName:#"PicturePrimeViewController" bundle:nil];
self.picturePrimeViewController = pVC;
[pVC release];
[self.view addSubview:self.picturePrimeViewController.view];
}
else {
PicturePrimeViewController *pVC = [[PicturePrimeViewController alloc] initWithNibName:#"PicturePrimeViewController" bundle:nil];
self.picturePrimeViewController = pVC;
[pVC release];
[self.view addSubview:self.picturePrimeViewController.view];
}
}
Now a picturePrimeViewController is loaded and added to the subview, everything loads and displays fine. Now, to get a new prime, you simple swipe for a new one.
In picturePrimeViewController.m
-(void)handleSwipeFromRight:(UISwipeGestureRecognizer *)gestureRecognizer {
if (!transitioning) {
[self performTransition];
}
}
-(void)performTransition {
CATransition *transition = [CATransition animation];
transition.duration = 1.0;
transition.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn];
transition.type = kCATransitionPush;
transition.subtype = kCATransitionFromLeft;
transitioning = YES;
transition.delegate = self;
[self.view.layer addAnimation:transition forKey:nil];
[UIView animateWithDuration:0.5 animations:^ {
self.view.alpha = 0.0;
}completion:^(BOOL finished) {
NSLog(#"Transition Animation Complete");
}];
}
-(void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag {
transitioning = NO;
[[NSNotificationCenter defaultCenter] postNotificationName:#"nextPrime" object:nil];
}
Now in the animationDidStop: Method, i again post another notification to the NSNotificationCenter back to the MasterViewController to signal another prime.
In MasterViewController.m
-(void)nextPrime {
if (self.picturePrimeViewIsDisplayed) {
[self.picturePrimeViewController.view removeFromSuperview];
self.picturePrimeViewController = nil;
[self showAPrime];
}
if (self.flashTextViewIsDisplayed) {
[self.flashTextPrimeViewController.view removeFromSuperview];
self.flashTextPrimeViewController = nil;
[self showAPrime];
}
}
However! Upon swiping the right, the view animates properly but then I get a bad access crash when the ThinkOfViewController is attempting to dealloc it's UIViews. So for some reason, it is taking that the ThinkOfViewController a long time to dealloc, when I assumed when I called [self.thinkOfViewController removeFromSuperview], it should of been removed immeditately.
(Side note, the textFlashViewController has no problems, the only problems are comming with this ThinkOfViewController).
Is this paradigm I set up a bad implmentation of dealing with UiViewController's comming in and out? I have read that delegation can help in this instance, I'm just not sure how that works.
If any of you have any ideas, I would be so grateful, as I have mined through forums and documentations and cannot see a solution to my rather custom implmentation of dealing with these views.
So, the short answer is yes, this "paradigm" is a bad implementation of dealing with UIViewControllers. I would suggest going and reading Apple's View Controller Programming Guide, which outlines the correct implementation, but here's a quick synopsis:
Your MasterViewController, from what I gather, manages these other two UIViewControllers, ThinkOfViewController and PicturePrimeViewController. When you are adding or removing the views of a view controller from the screen, you don't add or remove the views to the MasterViewController. The whole point of a view controller is to manage the "showing" or "hiding" of its view. I put showing and hiding in quotes because you're not actually showing or hiding them. You are pushing and popping them off of the view hierarchy, and this is done with a UINavigationController. Each UIViewController knows the UINavigationController that it is being controlled by (thus, UINavigationControllers are known as "controllers of controllers"), and you can thus push a view controller onto the stack by saying
[self.navigationController pushViewController:vcToBePushed animated:YES];
When the view controller that is on top of the stack is to be removed, you simply have to say
[self.navigationController popViewControllerAnimated:YES];
Pushing or popping a UIViewController onto or off of the stack means the view controller takes its view with it, and displays it or removes it from on screen. So that covers, in the tightest nutshell imaginable, UIViewController view management.
The other issue, regarding the EXC_BAD_ACESS crash, means that you are trying to access memory that has already been allocated for another object. I suspect the culprit is here:
if (randomNumber == 0) {
self.flashTextViewIsDisplayed = NO;
ThinkOfViewController *thinkVC = [[ThinkOfViewController alloc] initWithNibName:#"ThinkOfViewController" bundle:nil];
self.thinkOfViewController = thinkVC;
[thinkVC release];
self.picturePrimeViewIsDisplayed = YES;
[self.view addSubview:self.thinkOfViewController.view];
}
The problem is that you release thinkVC before it has had a chance to be retained by the MasterViewController's view (which happens in self.view addSubview:). Thus, self.view is adding a pointer to an object whose memory just got added back to the heap. Scrapping your add/removeSubview methodology for the push/pop methodology I just outlined will keep that sort of memory issue from happening.
I hope this helps, but let us (us being SO) know if you still have issues.

Why does the keyboard not show in my UITextView?

I have a container class which is a view controller.
It holds the UITextView for notes and has another view controller as a subview, called it PDFViewController. That one has an array of view controllers, called them PageViewController's, in it and a UIScrollView so i can swipe through the different view controllers from the array.
Each of the PageViewController's has also an UIScrollView, so i can zoom the different pages.
But when I show my UITextView i cannot edit or write anything.
It shows a blinking cursor, but no keyboard and can't write text.
When i highlight a word from the default text, it shows me some dictionary options but the words won't be replaced.
I just don't know where the problem might be.
container.m (View Controller)
- (void) initNotes {
notesVisible = FALSE;
notes = [[UITextView alloc]initWithFrame:CGRectMake(10, 10, note_width, note_height)];
notes.backgroundColor = [UIColor yellowColor];
notes.font = [UIFont fontWithName:#"Arial" size:24];
notes.text = #"Hier ist Platz für Ihre Notizen";
container = [[UIView alloc] initWithFrame:CGRectMake(start_x, start_y, container_width, container_height)];
container.backgroundColor = [UIColor yellowColor];
[container.layer setShadowColor:[[UIColor blackColor] CGColor]];
[container.layer setShadowOffset:CGSizeMake(2.0, 3.0)];
[container.layer setShadowOpacity:0.6];
[container.layer setShadowRadius:5];
container.layer.shouldRasterize = YES;
[container addSubview:notes];
[self.view addSubview:container];
}
- (void) showTextView {
[UIView beginAnimations:#"MoveAndStrech" context:nil];
[UIView setAnimationDuration:0.4];
[UIView setAnimationBeginsFromCurrentState:YES];
[UIView setAnimationCurve:UIViewAnimationCurveEaseOut];
container.frame = CGRectMake(start_x, self.view.center.y-container_height, container_width, container_height);
[UIView commitAnimations];
notesVisible = !notesVisible;
}
- (void) hideTextView {
[UIView beginAnimations:#"MoveAndStrech" context:nil];
[UIView setAnimationDuration:0.4];
[UIView setAnimationBeginsFromCurrentState:YES];
[UIView setAnimationCurve:UIViewAnimationCurveEaseIn];
container.frame = CGRectMake(start_x, start_y, container_width, container_height);
// [notes resignFirstResponder];
[UIView commitAnimations];
notesVisible = !notesVisible;
}
#implementation PDFViewController
- (void)awakeFromNib
{
painting = false;
dataInstance = [PDFDataInstance sharedInstance];
chapter = [dataInstance chapter];
[self getNumberOfPages];
kNumberOfPages = dataInstance.pages;
// Register observer to be called when a cell from BookmarkPDFController is pressed
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(didSelectBookmark:)
name:#"bookmarkPressedinPDFView" object:nil];
// Register observer to be called when a cell from BookmarkPDFController is pressed
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(togglePainting:)
name:#"togglePainting" object:nil];
// view controllers are created lazily
// in the meantime, load the array with placeholders which will be replaced on demand
NSMutableArray *controllers = [[NSMutableArray alloc] init];
for (unsigned i = 0; i < kNumberOfPages; i++)
{
[controllers addObject:[NSNull null]];
}
self.viewControllers = controllers;
[controllers release];
// a page is the width of the scroll view
scrollView.pagingEnabled = YES;
scrollView.contentSize = CGSizeMake(scrollView.frame.size.width * kNumberOfPages, scrollView.frame.size.height);
scrollView.showsHorizontalScrollIndicator = NO;
scrollView.showsVerticalScrollIndicator = NO;
scrollView.delegate = self;
scrollView.directionalLockEnabled = YES;
currentPage = [[chapter currentPage] integerValue];
// pages are created on demand
// load the visible page
// load the page on either side to avoid flashes when the user starts scrolling
//
// Load pages based on currentPage cause of when coming from bookmarks
[self loadScrollViewWithPage:currentPage];
[self loadScrollViewWithPage:currentPage+1];
// update the scroll view to the appropriate page
CGRect frame = scrollView.frame;
frame.origin.x = frame.size.width * currentPage;
frame.origin.y = 0;
[scrollView scrollRectToVisible:frame animated:YES];
}
- (void)loadScrollViewWithPage:(int)page
{
if (page < 0)
return;
if (page >= kNumberOfPages)
return;
// replace the placeholder if necessary
PageViewController *controller = [viewControllers objectAtIndex:page];
if ((NSNull *)controller == [NSNull null])
{
//page+1 cause CGPDF indexing starts with 1
controller = [[PageViewController alloc] initWithPageNumberAndUrl:page+1: [chapter urlOnFilesystem]];
[viewControllers replaceObjectAtIndex:page withObject:controller];
[controller release];
}
// add the controller's view to the scroll view
if (controller.view.superview == nil)
{
CGRect frame = scrollView.frame;
frame.origin.x = frame.size.width * page;
frame.origin.y = 0;
controller.view.frame = frame;
[scrollView addSubview:controller.view];
}
}
TextView delegates
- (void)textViewDidBeginEditing:(UITextView *)textView {
NSLog(#"begin editing");
}
- (BOOL)textView:(UITextView *)aTextView shouldChangeTextInRange:(NSRange)aRange replacementText:(NSString*)aText
{
NSLog(#"something changed");
return YES;
}
Just want to add another possibility here. In the iOS simulator, make sure the "Hardware/Keyboard/Connect Hardware Keyboard" is not checked. It took me a couple of hours to figure out this. I guess I toggled that option by accidently pressing the shortcut. Not good experience :(
As pictured, Connect Hardware Keyboard should be UN-checked.
Is the UITextView receiving touches? Implement the UITextViewDelegate methods and see if they get called (esp textViewDidBeginEditing: and textView: shouldChangeTextInRange: replacementText:). This way you'll know for sure whether the text view is indeed handling the touches or not.
If it does, then I don't see anything wrong with your code (except you might have accidentally added a view overlapping the textview)
No one asked , but have you set :
notes.editable = YES ;
?
Make sure that in the delegate of your textview. Following method is returning YES.
- (BOOL)textViewShouldBeginEditing:(UITextView *)textView
{
return YES;
}
After you have set the notes delegates, do the following.
- (void)textViewDidBeginEditing:(UITextView *)textView {
if ([textView isEqual:notes]) {
NSLog(#"notes begin editing");
}
}
- (BOOL)textView:(UITextView *)aTextView shouldChangeTextInRange:(NSRange)aRange replacementText:(NSString*)aText {
if ([aTextView isEqual:notes]) {
NSLog(#"something changed in notes");
}
return YES;
}
This should confirm delegates of notes are indeed getting called.
you need to implement your container class with UITextViewDelegate
and add notes.delegate=self; just after initialize the notes object in initNotes method.
Check for 2 things:
Is notes.editable = YES; if so please change it to NO.
2.Check whether your UITextView delegates are called are not. If not Please make sure you include in your implementation file and set notes.delegate = self.
Try without container, just use the UITextView (notes) as the container itself.
Every time you used container use notes instead.
And then dont:
[container addSubview:notes];
[self.view addSubview:container];
Just:
[self.view addSubview:notes];
Tell me how it goes
I had similar behavior, the UITextView not responding to touches and the keyboard not showing.
My UITextView was layered on top of a UICollectionView which was receiving all the touches even though the UITextView was visibly on top.
I fixed it by adjusting the frame of the UICollectionView so that the UITextView has nothing behind it. After that, all the delegate methods were called like a charm.
Hope this helps.
please Refer this one. where ContantView is your view/textfield and _bubbletable is your table.
- (void)keyboardWasShown:(NSNotification*)aNotification
{
NSDictionary* info = [aNotification userInfo];
CGSize kbSize = [[info objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue].size;
[UIView animateWithDuration:0.2f animations:^{
CGRect frame = _ContantView.frame;
frame.origin.y -= kbSize.height;
_ContantView.frame = frame;
frame = _bubbleTable.frame;
frame.size.height -= kbSize.height;
_bubbleTable.frame = frame;
}];
}

How to hide tab bar programmatically and then expand view to fit

I got the code from this question: How to hide UITabBarController programmatically? which is brilliant, however the view doesn't expand to fit the space left by the tab bar now.
I have set the appropriate UIViewAutoresizingMasks to the view, but I'm assuming that just because its hidden doesn't mean its not still taking up the space?
Anyway, if I do [self.navigationController setNavigationBarHidden:YES animated:YES]; then the navigation bar moves up and off the screen expanding the view with it.
How can I replicate this behavior for the Tab Bar?
Turns out its not quite possible. Best way is to present a modal view (navigation) controller instead of pushing a view controller.
This worked great for me! (combines solutions from other posts mentioned -580 is randomly large number)
for(UIView *view in self.tabBarController.view.subviews)
{
if([view isKindOfClass:[UITabBar class]])
{
[view setFrame:CGRectMake(view.frame.origin.x, 580, view.frame.size.width,
view.frame.size.height)];
}
else
{
[view setFrame:CGRectMake(view.frame.origin.x, view.frame.origin.y,
view.frame.size.width, view.frame.size.height +40)];
}
}
-(void)hideTabBar
{ UITabBarController * tabbarcontroller= appDelegate.tabBarVC;
if (tabbarcontroller.tabBar.isHidden)
{
return;
}
tabbarcontroller.tabBar.hidden=YES;
CGRect frm=tabbarcontroller.view.frame;
frm.size.height += tabbarcontroller.tabBar.frame.size.height;
tabbarcontroller.view.frame=frm;
}
-(void)showTabBar
{ UITabBarController * tabbarcontroller=appDelegate.tabBarVC;
if (!tabbarcontroller.tabBar.isHidden)
{
return;
}
CGRect frm=tabbarcontroller.view.frame;
frm.size.height -= tabbarcontroller.tabBar.frame.size.height;
tabbarcontroller.view.frame=frm;
tabbarcontroller.tabBar.hidden=NO;
}
here appDelegate is = (AppDelegate *) [[UIApplication sharedApplication] delegate]
tabBarVc is UITabBarController *tabBarVC defined as property in app delegate
in NSContraints era, do NOT try to modify frame by code, bad things may happen.
Use:
pushedViewController.hidesBottomBarWhenPushed = YES;
typically set hidesBottomBarWhenPushed to yes in prepareforSegue, ANYWAY before iOS actually pushes the new controller.
The easiest way is probably to set a new frame for the view:
CGRect viewFrame = view.frame;
viewFrame.size.height += 40; // Change this to the height of the tab bar
[UIView beginAnimations:nil context:nil];
[UIView setAnimationDuration:0.75];
view.frame = viewFrame;
[UIView commitAnimations];