I'd like to have an underline that indicates which item was selected. It slides to any other items whenever the item was tapped. Therefore, I added a subview to the custom UITabBarController and set the animation. Then I use hidesBottomBarWhenPushed to hide the tab bar when pushed. However, the underline seems not combined with the custom UITabBarController.
How to handle the subview so it is always on top even when using the back gesture? This Flipboard app capture is what I want to do.
Edit:
CustomTabBarController.m
- (void)viewDidLoad
{
[super viewDidLoad];
// create underline view
CGRect tabBarFrame = self.tabBar.frame;
CGFloat itemWidth = (CGFloat)CGRectGetWidth(tabBarFrame) / MIN(5, self.tabBar.items.count);
CGFloat originX = (CGFloat)itemWidth * self.selectedIndex;
CGRect underlineFrame = CGRectMake(originX, CGRectGetMaxY(tabBarFrame) - 3.0f, itemWidth, 3.0f);
self.underlineView = [[UIView alloc] initWithFrame:underlineFrame];
self.underlineView.backgroundColor = [UIColor redColor];
[self.view addSubview:self.underlineView];
}
#pragma mark - UITabBarDelegate
- (void)tabBar:(UITabBar *)tabBar didSelectItem:(UITabBarItem *)item
{
NSUInteger itemIndex = [tabBar.items indexOfObject:item];
CGRect underlineFrame = self.underlineView.frame;
CGFloat originX = (CGFloat)CGRectGetWidth(self.underlineView.frame) * itemIndex;
// underline shifting animation
[UIView animateWithDuration:0.25
animations:^{
self.underlineView.frame = CGRectMake(originX, underlineFrame.origin.y, CGRectGetWidth(underlineFrame), CGRectGetHeight(underlineFrame));
}];
}
CustomTableViewController.m
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
UIViewController *detailViewController = segue.destinationViewController;
detailViewController.hidesBottomBarWhenPushed = YES;
}
hidesBottomBarWhenPushed hides the tab bar but its subview (the underline view).
If I hide it by myself and show it in viewWillAppear, the underline view does not look like on top of the tab bar.
I finally found a workaround. To override the method hidesBottomBarWhenPushed, then you can add an alternative view for tab bar's subviews.
sourceViewController.m
- (BOOL)hidesBottomBarWhenPushed
{
[super hidesBottomBarWhenPushed];
CustomTabBarController *tabBarController = (CustomTabBarController *)self.tabBarController;
if (tabBarController.underlineView.isHidden) {
CGRect tabBarBounds = tabBarController.tabBar.bounds;
CGFloat underlineHeight = CGRectGetHeight(tabBarController.underlineView.frame);
CGFloat itemWidth = (CGFloat)CGRectGetWidth(tabBarBounds) / MIN(5, tabBarController.tabBar.items.count);
CGFloat originX = (CGFloat)itemWidth * tabBarController.selectedIndex;
UIView *alternativeView = [[UIView alloc] initWithFrame:CGRectMake(originX,
CGRectGetMaxY(tabBarBounds) - underlineHeight,
itemWidth,
underlineHeight)];
alternativeView.tag = tabBarController.underlineViewTag;
alternativeView.backgroundColor = tabBarController.underlineView.backgroundColor;
[tabBarController.tabBar addSubview:alternativeView];
}
return NO;
}
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
CustomTabBarController *tabBarController = (CustomTabBarController *)self.tabBarController;
if (tabBarController.underlineView.isHidden) {
tabBarController.underlineView.hidden = NO;
NSInteger underlineViewTag = tabBarController.underlineViewTag;
UIView *alternativeView = [tabBarController.tabBar viewWithTag:underlineViewTag];
[alternativeView removeFromSuperview];
}
}
Don't forget the case that interactivePopGesture failure to popover view controller, the alternative view still be added to tab bar. So remove it at destination view controller if needed.
destinationViewController.m
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
CustomTabBarController *tabBarController = (CustomTabBarController *)self.tabBarController;
NSInteger underlineViewTag = tabBarController.underlineViewTag;
UIView *alternativeView = [tabBarController.tabBar viewWithTag:underlineViewTag];
if (alternativeView) [alternativeView removeFromSuperview];
}
Related
i have a UIViewController with several specific subviews (like PassBook Views).
When i exit a view i want to reload the ViewController and all his subviews. i call the viewDidLoad and viewWillLayoutSubviews method of the rootviewController like this:
- (IBAction)disconnect:(UIStoryboardSegue *)segue {
self.sourceVC = segue.sourceViewController;
self.disconnected = YES;
ViewController *rootController=(ViewController *)((AppDelegate *)[[UIApplication sharedApplication] delegate]).window.rootViewController;
[rootController viewDidLoad];
[rootController viewWillLayoutSubviews];}
in my ViewController class:
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view from its nib.
[self setupPBDataSource];}
- (void)viewWillLayoutSubviews {
[super viewWillLayoutSubviews];
CGRect frame = self.view.bounds;
CGFloat maxY = (([[[UIDevice currentDevice] systemVersion] floatValue] >= 7.0)\
? 20 : 0);
frame.origin.y += maxY;
frame.size.height -= maxY;
if (!_passbookView) {
self.passbookView =
[[PBPassGroupStackView alloc] initWithFrame:frame datasource:self];
[self.view addSubview:self.passbookView];
} else {
[self.passbookView removeFromSuperview];
self.passbookView =
[[PBPassGroupStackView alloc] initWithFrame:frame datasource:self];
[self.view addSubview:_passbookView];
}}
i just want to know how to reload my new passbookView (with two new subviews created on a login segue) ??? thanks.
I have one main viewController A with an UITabBar. My issue are when I scroll to the last cell and after I go to the viewController B with a cell click in UITableView and when I come back to my viewController A, my last cell is cut off at the bottom and I can scroll for appear all content of this cell. But by default at the bottom the UITableView doesn't keep the same place that last time.
When I launch viewController B I hide my UITabBar with this code in VCB "viewWillAppear" :
- (void)hideTabBar {
UITabBar *tabBar = self.tabBarController.tabBar;
UIView *parent = tabBar.superview;
UIView *content = [parent.subviews objectAtIndex:0];
UIView *window = parent.superview;
[UIView animateWithDuration:0.3
animations:^{
CGRect tabFrame = tabBar.frame;
tabFrame.origin.y = CGRectGetMaxY(window.bounds);
tabBar.frame = tabFrame;
content.frame = window.bounds;
}];
}
And when I come back to my viewController A I show my UITabBar with this code in VCB "viewWillDisappear":
- (void)showTabBar {
UITabBar *tabBar = self._tab;
UIView *parent = tabBar.superview;
UIView *content = [parent.subviews objectAtIndex:0];
UIView *window = parent.superview;
[UIView animateWithDuration:0.3
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;
}];
}
I have the same problem in iOS 6 but the scroll doesn't allow to go at the bottom and the last cell are cut off always.
In my viewController A in "viewWillAppear" I do:
if ([[UIDevice currentDevice].systemVersion hasPrefix:#"7"]) {
[self._tabBarControllerArticle.tabBar setTranslucent:NO];
}
Before when I clicked (image 1)
When I come back (image 2)
Thanks for advance for all the answer!!!
Another way of doing it is to set translucent property of the tabBar to NO like this
// In init or viewDidLoad of tab bar controller
self.tabBar.translucent = NO;
Assuming you're using iOS7 this should adjust your UITableView just above self.tabBar
Click your tableViewController where the problem is happening, go to the Attributes Inspector, scroll down and uncheck the "Under Bottom Bars" box.
The selected answer to this question helped me.
You missed to assign frame back to your view. See the updated code.
- (void)showTabBar {
UITabBar *tabBar = self._tab;
UIView *parent = tabBar.superview;
UIView *content = [parent.subviews objectAtIndex:0];
UIView *window = parent.superview;
[UIView animateWithDuration:0.3
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;
//YOU MISSED TO ADD BELOW LINE....
content.frame = contentFrame;
}];
}
I have a UINavigationController to which I need to add a second UINavigationBar. Neither of those bars is translucent. Problem is, view controllers that I put inside this navigation controller are partially covered by my second navigation bar. Where do I adjust the frames of those view controllers' views so that I don't get a "blinking" effect of them changing frames while being visible?
EDIT:
This is in viewDidLoad:
UINavigationBar *secondaryNavBar = [[UINavigationBar alloc] initWithFrame:CGRectMake(0, 64, self.view.frame.size.width, 50)];
secondaryNavBar.translucent = NO;
if ([secondaryNavBar respondsToSelector:#selector(setBarTintColor:)]) { //it has to work on iOS 6 as well
secondaryNavBar.barTintColor = [UIColor darkGrayColor];
secondaryNavBar.tintColor = [UIColor whiteColor];
}
else {
secondaryNavBar.tintColor = [UIColor darkGrayColor];
}
[self.view addSubview:secondaryNavBar];
self.secondaryNavBar = secondaryNavBar;
Here's a working solution. Certainly not the best, and I did not make it to support iOS 6, you'll have to work on it and test it.
CustomNavigationController.m :
#implementation CustomNavigationController {
UINavigationBar *bottomNavBar;
}
- (void)viewDidLoad {
[super viewDidLoad];
[self showNavBar];
}
- (void)showNavBar {
UINavigationBar *secondaryNavBar = [[UINavigationBar alloc] initWithFrame:CGRectMake(0, 64, self.view.frame.size.width, 50)];
secondaryNavBar.translucent = NO;
if ([secondaryNavBar respondsToSelector:#selector(setBarTintColor:)]) { //it has to work on iOS 6 as well
secondaryNavBar.barTintColor = [UIColor darkGrayColor];
secondaryNavBar.tintColor = [UIColor whiteColor];
}
else {
secondaryNavBar.tintColor = [UIColor darkGrayColor];
}
[self.view addSubview:secondaryNavBar];
bottomNavBar = secondaryNavBar;
[self layoutNavBar];
}
- (void)layoutNavBar {
// Get the currently displayed view
UIView *contentView = self.topViewController.view;
// Get its frame and height
CGRect contentFrame = contentView.frame;
float height = contentFrame.size.height;
// Adapt height and y origin with the new nav bar
contentFrame.size.height = height - bottomNavBar.frame.size.height;
contentFrame.origin.y = bottomNavBar.frame.origin.y + bottomNavBar.frame.size.height;
// Set the view's frame
contentView.frame = contentFrame;
}
#end
ViewController.m :
#implementation ViewController
-(void)viewDidAppear:(BOOL)animated {
CustomNavigationController *navigation = (CustomNavigationController*)self.navigationController;
[navigation layoutNavBar];
}
#end
Note that you have to call layoutNavBar on viewDidAppear, or the view's frame will be reset by your app. This is not a perfectly clean solution, but a pretty good fix.
Currently I am wanting to create a slide animation when the user selects on a segmented button of a UISegmentedControl instantiated on top of a navigationbar. Currently I have a UISegmentedControl with 6 buttons the user is allowed to press and select to go to different views.
Everything works accordingly but I am having an issue with implementing the slide transition, if it is even possible.
I am able to implement a slide transition between UITabBar views using this method:
- (BOOL)tabBarController:(UITabBarController *)tabBarController shouldSelectViewController:(UIViewController *)viewController
{
int controllerIndex = [[tabBarController viewControllers] indexOfObject:viewController];
if(controllerIndex == self.selectedIndex || self.isAnimating){
return NO;
}
// Get the views.
UIView * fromView = tabBarController.selectedViewController.view;
UIView * toView = [viewController view];
// Get the size of the view area.
CGRect viewSize = fromView.frame;
BOOL scrollRight = controllerIndex > tabBarController.selectedIndex;
// Add the to view to the tab bar view.
[fromView.superview addSubview:toView];
// Position it off screen.
toView.frame = CGRectMake((scrollRight ? 320 : -320), viewSize.origin.y, 320, viewSize.size.height);
[UIView animateWithDuration:0.2 animations: ^{
// Animate the views on and off the screen. This will appear to slide.
fromView.frame =CGRectMake((scrollRight ? -320 : 320), viewSize.origin.y, 320, viewSize.size.height);
toView.frame =CGRectMake(0, viewSize.origin.y, 320, viewSize.size.height);
} completion:^(BOOL finished) {
if (finished) {
// Remove the old view from the tabbar view.
[fromView removeFromSuperview];
tabBarController.selectedIndex = controllerIndex;
}
}
];
return NO;
}
Not so sure if the same rules apply for a UISegmentedControl of several viewcontrollers. Is this possible to do? I figure it should be but anyone have any ideas on how to get started?
EDIT
Heres the code I use within my segmentedcontroller...
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
/* This bunch of code creates the segmentedControllerButtons in the nav bar */
self.segmentedViewControllers = [self segmentedViewControllerContent];
NSArray * segmentTitles = #[#"Plant", #"Net", #"Wiz", #"Date", #"Clone", #"GF/E"];
self.segmentedControl = [[UISegmentedControl alloc]initWithItems:segmentTitles];
self.segmentedControl.selectedSegmentIndex = 0;
self.segmentedControl.segmentedControlStyle = UISegmentedControlStyleBar;
[self.segmentedControl addTarget:self action:#selector(didChangeSegmentControl:) forControlEvents:UIControlEventValueChanged];
self.navigationItem.titleView = self.segmentedControl;
self.navigationController.navigationBar.tintColor = [UIColor blackColor];
self.segmentedControl.tintColor = [UIColor redColor];
[self didChangeSegmentControl:self.segmentedControl]; // kick everything off
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (NSArray *)segmentedViewControllerContent {
CDDConfigPlantViewController *plant = [[CDDConfigPlantViewController alloc] initWithNibName:#"CDDConfigPlantViewController" bundle:nil];
plant->ipAddress = ipAddress;
plant->encode = encodedInfo;
CDDConfigNetworkViewController *network = [[CDDConfigNetworkViewController alloc] initWithNibName:#"CDDConfigNetworkViewController" bundle:nil];
network->ipAddress = ipAddress;
network->encode = encodedInfo;
CDDConfigAcquisitionWizardViewController *acquisition_wizard = [[CDDConfigAcquisitionWizardViewController alloc] initWithNibName:#"CDDConfigAcquisitionWizardViewController" bundle:nil];
acquisition_wizard->ipAddress = ipAddress;
acquisition_wizard->encode = encodedInfo;
CDDConfigDateTimeViewController *date_time = [[CDDConfigDateTimeViewController alloc] initWithNibName:#"CDDConfigDateTimeViewController" bundle:nil];
date_time->ipAddress = ipAddress;
date_time->encode = encodedInfo;
CDDConfigCDDCloneViewController *cdd_clone = [[CDDConfigCDDCloneViewController alloc] initWithNibName:#"CDDConfigCDDCloneViewController" bundle:nil];
cdd_clone->ipAddress = ipAddress;
cdd_clone->encode = encodedInfo;
CDDConfigGroundfaultEnergyViewController *groundfault_energy = [[CDDConfigGroundfaultEnergyViewController alloc] initWithNibName:#"CDDConfigGroundfaultEnergyViewController" bundle:nil];
groundfault_energy->ipAddress = ipAddress;
groundfault_energy->encode = encodedInfo;
NSArray * controllers = [NSArray arrayWithObjects:plant, network, acquisition_wizard, date_time, cdd_clone, groundfault_energy, nil];
return controllers;
}
#pragma mark -
#pragma mark Segment control
- (void)didChangeSegmentControl:(UISegmentedControl *)control {
if (self.activeViewController) {
[self.activeViewController viewWillDisappear:NO];
[self.activeViewController.view removeFromSuperview];
[self.activeViewController viewDidDisappear:NO];
}
self.activeViewController = [self.segmentedViewControllers objectAtIndex:control.selectedSegmentIndex];
[self.activeViewController viewWillAppear:YES];
[self.view addSubview:self.activeViewController.view];
[self.activeViewController viewDidAppear:YES];
}
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
[self.activeViewController viewWillAppear:animated];
}
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
[self.activeViewController viewDidAppear:animated];
}
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
[self.activeViewController viewWillDisappear:animated];
}
- (void)viewDidDisappear:(BOOL)animated {
[super viewDidDisappear:animated];
[self.activeViewController viewDidDisappear:animated];
}
Is it UIView you want to animate or UIViewControllers.
If UIView the animateWithDuration: animations: completion: works
if UIViewControllers presentViewController: animated: completion: is the way to go
I have a requirement to show a status bar at certain times at the bottom of my application. I can easily put this at the bottom of my application's main view, but whenever I push a view controller on top of this (either modally or not) it hides this status bar.
Is there any way I can add a status bar like this, and have it be outside the bounds of my application itself? Ideally I'd like this to work like the call-in-progress status bar on the iPhone - when this bar appears, the app is pushed down, and a call to [[UIScreen mainScreen] applicationFrame] returns the correct size (i.e. it accounts for the presence of this status bar when calculating the height available for the app).
I wanted to do this, too, so I tried View Controller Containment. I'm still trying it out, so I'm not willing to give this a ringing endorsement, but it might be something you'd want to try playing around with yourself if you're in iOS5. But it appears to give you a status bar that will appear or disappear from the bottom of the screen.
This is a view controller that will open another view controller, but if there is status text to show, it pops up from the bottom of the screen and stays there until you get rid of it. I've only done a little testing so far, but it looks like this handles pushViewController/popViewController, but maybe not modal views.
My header looks like:
// StatusBarViewController.h
//
// Created by Robert Ryan on 7/8/12.
#import <UIKit/UIKit.h>
#interface StatusBarViewController : UIViewController
#property (strong, nonatomic) UIViewController *appController;
- (void)setStatus:(NSString *)text;
#end
My implementation file (this is ARC) looks like:
// StatusBarViewController.m
//
// Created by Robert Ryan on 7/8/12.
#import "StatusBarViewController.h"
#interface StatusBarViewController ()
{
BOOL _statusHidden;
UIView *_appView;
UILabel *_statusLabel;
}
#end
#implementation StatusBarViewController
#synthesize appController = _appController;
- (void)dealloc
{
_appView = nil;
_statusLabel = nil;
[self setAppController:nil]; // usually I don't like setters in dealloc, but this does some special stuff
}
- (void)createControlsWithStatusHidden
{
// create default app view that takes up whole screen
CGRect frame = self.view.frame;
frame.origin = CGPointMake(0.0, 0.0);
_appView = [[UIView alloc] initWithFrame:frame];
_appView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
_appView.clipsToBounds = YES;
[self.view addSubview:_appView];
// create status label that is just off screen below the app view
_statusLabel = [[UILabel alloc] init];
_statusLabel.font = [UIFont fontWithName:#"Helvetica-Bold" size:12.0];
_statusLabel.backgroundColor = [UIColor darkGrayColor];
_statusLabel.textColor = [UIColor whiteColor];
CGSize size = [#"Hey!" sizeWithFont:_statusLabel.font]; // test size of box with random text
_statusLabel.frame = CGRectMake(0.0, frame.size.height, frame.size.width, size.height);
_statusLabel.textAlignment = UITextAlignmentCenter;
_statusLabel.autoresizingMask = UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleWidth;
[self.view addSubview:_statusLabel];
}
- (void)viewDidLoad
{
[super viewDidLoad];
[self createControlsWithStatusHidden];
_statusHidden = YES;
// I'm instantiating from storyboard. If you're using NIBs, just create your controller controller using initWithNib and then set our appController accordingly.
self.appController = [self.storyboard instantiateViewControllerWithIdentifier:#"MainNavigator"];
}
- (void)setAppController:(UIViewController *)controller
{
if (controller)
{
controller.view.frame = CGRectMake(0.0, 0.0, _appView.frame.size.width, _appView.frame.size.height);
[self addChildViewController:controller];
[controller didMoveToParentViewController:self];
if (self.appController)
{
// if we have both a new controller and and old one, then let's transition, cleaning up the old one upon completion
[self transitionFromViewController:self.appController
toViewController:controller
duration:0.5
options:UIViewAnimationOptionTransitionCrossDissolve | UIViewAnimationOptionCurveEaseInOut
animations:nil
completion:^(BOOL finished){
if (self.appController)
{
[self.appController willMoveToParentViewController:nil];
[self.appController removeFromParentViewController];
}
}];
}
else
{
// if we have no previous controller (i.e. this is our first rodeo), then just add it to the view
[_appView addSubview:controller.view];
}
}
else
{
// no new controller, so we're just removing any old on if it was there
if (self.appController)
{
// if there was an old controller, remove it's view, and remove it from the view controller hierarchy
[self.appController.view removeFromSuperview];
[self.appController willMoveToParentViewController:nil];
[self.appController removeFromParentViewController];
}
}
_appController = controller;
}
- (void)hideStatusWithCompletion:(void (^)(BOOL finished))completion
{
[UIView animateWithDuration:0.25
animations:^{
CGRect labelFrame = _statusLabel.frame;
labelFrame.origin.y += labelFrame.size.height;
_statusLabel.frame = labelFrame;
CGRect appFrame = _appView.frame;
appFrame.size.height += labelFrame.size.height;
_appView.frame = appFrame;
}
completion:completion];
}
- (void)unhideStatusWithCompletion:(void (^)(BOOL finished))completion
{
[UIView animateWithDuration:0.25
animations:^{
CGRect labelFrame = _statusLabel.frame;
labelFrame.origin.y -= labelFrame.size.height;
_statusLabel.frame = labelFrame;
CGRect appFrame = _appView.frame;
appFrame.size.height -= labelFrame.size.height;
_appView.frame = appFrame;
}
completion:completion];
}
- (void)setStatus:(NSString *)text
{
BOOL hasText = (text && [text length] > 0);
if (hasText)
{
if (!_statusHidden)
{
// if we have text, but status is already shown, then hide it and unhide it with new value
[self hideStatusWithCompletion:^(BOOL finished){
_statusLabel.text = text;
[self unhideStatusWithCompletion:nil];
}];
}
else
{
// if we have text, but no status is currently shown, then just unhide it
_statusLabel.text = text;
[self unhideStatusWithCompletion:nil];
}
_statusHidden = NO;
}
else
{
if (!_statusHidden)
{
// if we don't have text, but status bar is shown, then just hide it
[self hideStatusWithCompletion:^(BOOL finished){
_statusLabel.text = text;
}];
_statusHidden = YES;
}
}
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
return (interfaceOrientation != UIInterfaceOrientationPortraitUpsideDown);
}
#end
And then, any view controller that wants to update the status message would use a method kind of like:
- (void)setStatus:(NSString *)text
{
UIViewController *controller = [UIApplication sharedApplication].delegate.window.rootViewController;
if ([controller isKindOfClass:[StatusBarViewController class]])
{
[(StatusBarViewController *)controller setStatus:text];
}
}