I'm running into problems reconfiguring the UINavigationBar on iOS 7 and 8 when transitioning between views.
My application currently contains the following UIViewController flow:
VC1 --> VC2 --> VC3
In this flow
VC1 is the home screen and has an opaque UINavigationBar
VC2 has a translucent UINavigationBar
VC3 goes back to having an opaque UINavigationBar
The problem I've been running into is that the transitions between these views are all very sloppy looking. To start with I tried the following:
in VC2
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
// configure appearance
[self.navigationController.navigationBar configureTranslucentAppearance];
}
And in VC1 and VC3
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
// configure appearance
[self.navigationController.navigationBar restoreDefaultAppearance];
}
Here are the implementations of the two helper functions listed above:
- (void)restoreDefaultAppearance {
[[UIApplication sharedApplication] setStatusBarStyle:UIStatusBarStyleLightContent];
[self setTitleTextAttributes:#{NSForegroundColorAttributeName: [UIColor JTTextNavBar]}];
[self setTintColor:[UIColor JTTextNavBar]];
[self setBarTintColor:[UIColor JTBackgroundNavBarWithAlpha:1.0]];
[self setBackgroundImage:nil forBarMetrics:UIBarMetricsDefault];
[self setBackgroundColor:[UIColor JTBackgroundNavBarWithAlpha:1.0]];
[self setShadowImage:[UIImage navigationBarShadowImage]];
[self setTranslucent:NO];
}
- (void)configureTranslucentAppearance {
[[UIApplication sharedApplication] setStatusBarStyle:UIStatusBarStyleLightContent];
[self setBackgroundImage:[UIImage new] forBarMetrics:UIBarMetricsDefault];
[self setBackgroundColor:[UIColor clearColor]];
[self setShadowImage:[UIImage new]];
[self setTranslucent:YES];
}
This is the most basic way of handling this transition. It has the following visual artefacts:
When going from VC1 --> VC2 the moment you begin the transition the navigation bar turns black. The animation completes normally
When going from VC2 --> VC1 the nav bar instantly changes to the application default colour before the segue has time to complete.
When going from VC2 --> VC3 the navigation bar instantly goes from translucent to the app nav bar color and then menu items and VC body animate in.
When going from VC3 --> VC2 the nav bar instantly turns black and remains this way until the segue is complete.
None of these transitions look good at all. Ideally I would like the views to transition smoothly along with their new UINavigationBar but the only way I've seen to do this successfully is to manually add a toolbar to each xib.
Any suggestions? Apologies if this description is confusing :(
Edit: Added cropped images of the UINavigationBar and top portion of UIViewController for each of the listed transitions.
I finally found a decent solution!
There doesn't appear to be a proper way to smoothly transition from an opaque to transparent UINavigationBar BUT you can transition smoothly from a view controller with a visible status bar to one that has a hidden status bar.
This opens up a possible workaround which is to add the following in the viewWillAppear of VC2 from above:
[self.navigationController setNavigationBarHidden:YES animated:YES];
Once you have that, manually add a UINavigationBar to your xib and configure it to be transparent (and add all necessary UIBarButtonItem and views).
If everything is hooked up properly transitioning from VC1 to VC2 will hide the UINavigationBar at the same speed as the view transition and VC2 will show up with it's embedded UINavigationBar
Note: To make this work properly you'll have to make sure that in the viewWillAppear of View Controllers that can be accessed from VC2 you reset the UINavigationBar to be visible (if necessary) via:
[self.navigationController setNavigationBarHidden:NO animated:YES];
TL;DR - manually add a UINavigationBar to your transparent nav bar view controller and in its viewWillAppear hide the default one via setNavigationBarHidden:animated:
The black color you're seeing is the background color of the UINavigationController's view. One way to minimize seeing it is to manipulate the background color of that view to the color of the outgoing / incoming view controller's view. This works well if you're working with solid colors. Another approach is to extend your views behind the opaque navigation bar using UIViewController.extendedLayoutIncludesOpaqueBars = YES;
Set the UIWindow background color to your Navigation bar's tintColor.
always use Translucent, plus add an uiview with a color below it and animate it's alpha?
I've struggled with a nearly identical problem. There really aren't any smooth transitions using either a nav bar or toolbar as a blur. The best option that I've found* is to make an image out of the view and then use that image for the transition. Especially if you just need it for transitions, it's just about the cheapest option that still provides a great UI/UX.
*The one caveat is that some of the UI effects in a nav bar and toolbar don't show up when you take a snapshot, screenshot, or rasterize a UIView as an image. This is negligible if used for a transition.
You can create your own navigation bar using just a UIView and that way you have complete control over its appearance, layout, fading etc.
I gave up using UINavigationBar a while ago as it can be a pain to work with, as you are discovering, and now I never ever use it.
I have a root view controller which has a UIView which represents the navigation bar. If I want to do something like add a back button, change the color, show the navigation bar or hide it, change the transparency, etc. that is all controlled by the RVC and other view controllers call methods on the RVC to change the navigation bar depending upon their appearance requirements.
The RVC has a container view which is the full size of the controller view and the other view controllers get loaded into that.
A little bit of configuration to get everything set up, but once done its a structure that can be used in every project that uses a navigation bar very quickly.
So I struggled with this too.
Unfortunately I didn't have any success adding my own navigation bar via the storyboard. Instead, (and I warn you this is hacky) I add a view in the ViewController with the opaque navigation bar that has a negative margin and extends under the navigation bar.
When the ViewController with the transparent navigation bar is pushed the bar then immediately becomes transparent but due to the identically coloured view I have place directly behind it the change isn't noticeable. Et voila.
The code I have is pretty basic and written in C# (Xamarin) but for reasons of completeness....
var backing = new UIView(new CGRect(0, -68, this.View.Frame.Width, 64f));
backing.BackgroundColor = UIColor.Green;
this.View.AddSubview(backing);
Change background color work for me
window?.backgroundColor = Color.red.toUIColor()
I have one more solution..for the ones who are now refactoring the project and cant add navigation bar everywhere.. This is not a perfect solution just like #alexgophermix one. But its not bad either:
let transition = CATransition()
transition.duration = 0.6
transition.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionLinear)
transition.type = kCATransitionFade
navigationController?.navigationBar.shadowImage = UIImage()
navigationController?.navigationBar.setBackgroundImage(UIImage(), for: .default)
navigationController?.navigationBar.isTranslucent = true
navigationController?.navigationBar.layer.add(transition, forKey: nil)
navigationController?.view.backgroundColor = .clear
I am setting my nav bar in viewDidLoad() so this works everywhere and gives a slight different transition. In my case I was setting clear backgrounds somewhere and rest a gradient image. So this small change goes well for everywhere. This transition is not much noticeable but neither looks bad :)
In my case, the black I was seeing was the navbar's background image. Instead of setting it to nil, I set it to match the background color of the view behind the transparent navbar inside viewWillAppear:
let image = UIImage(color: .white)
navigationController?.navigationBar.setBackgroundImage(image, for: .default)
Then, inside willMove and viewWillDisappear I reverted it back to the original color.
YPNavigationBarTransition is a configureable framework animating navigation bars.
YPNavigationBarTransition uses two fake navigationbars (UIToolBar indeed) to simulate bar transitions while making the real navigationbar transparent.
Subclass your own UINavigationController and embed transitioncenter in it like YPNavigationController
Implement YPNavigationBarProtocol for your content controllers respectively.
Done.
A complete demo is include in the repo, checkout it for more details.
Related
I'm pushing a UIViewController (A) with clearColor as background in a UINavigationController (N).
N is placed inside a Container View. This view has a background that I want to show always.
Then, when Push is animated it shows a grey/transparent background in my VC (A) and when it finishes its animation, it shows correctly.
Is there any way to avoid this grey color?
I have 'done' it by putting a white view background in A and making it disappear in viewDidAppear but I think it can be done without that trick...
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
__weak TestsViewController *weakSelf = self;
[UIView animateWithDuration:.3 animations:^{
TestsViewController *ownSelf = weakSelf;
ownSelf.backgroundView.alpha = 0;
}];
}
Set your app's window's background colour to white:
// applicationDidFinishLaunching
self.window.backgroundColor = [UIColor whiteColor];
The push animation often ends up using the background colour of the window behind the transparent areas, and the window background colour is clear by default, so it shows black through the transparent navigation bars and toolbars.
EDIT: Sorry, misunderstood what the issue was. When you push a view controller with a transparent background colour, the background you see behind the view controller is the shadow that is rendered behind your view controller (you can see the edge of it when in a non-transparent view controller).
This question has an answer to your issue. Basically, you have to implement a custom animation controller, to provide the transition animation yourself (as described here). It would probably be easier to continue using your trick than implementing your own custom navigation transition.
I have a UINavigationBar based app. I've created a custom UIView with some titles and added it as a subview to the navigation bar:
[self.navigationController.navigationBar addSubview:_navbarView];
Everything works ok until I hit the back button in the navigation bar and the UIViewController transition occurs.
The problem is that my custom view doesn't fade away like the others elements in the UINavigationBar, it just stays the same and disappears when the transition is complete.
I want it to fade away during the transition like the native elements of the UINavigationBar, is there any way to achieve this?
If you add a subview to the navigation bar, then it will just stay there; the navigation controller doesn't know to do anything special with it. You say your custom view has "some titles" - have you tried doing this instead?
self.navigationItem.titleView = _navbarView;
Then the navigation controller knows that the view should be used in place of your controller's title, and it should animate in and out.
If that doesn't work, you'll need to look at becoming the navigation controller's delegate. Since iOS7, this can get quite complex.
If you need custom navigation bar it could be a good idea to create UINavigationController with custom UINavigationBar
- (instancetype)initWithNavigationBarClass:(Class)navigationBarClass toolbarClass:(Class)toolbarClass
in your navigation bar class you can implement
- (UINavigationItem *)popNavigationItemAnimated:(BOOL)animated
{
if (animated) {
//your_problem_view animation here
}
[super popNavigationItemAnimated];
}
The iOS7 Facebook App has a right side menu that can be shown by swiping right to left or clicking on the upper right button. When this menu is opened the there is a color transition in the entire status bar from blue to black and vice-versa when closed.
This image shows both status bar side-to-side
This looks like a very good solution for iOS Apps with side menus.
Any ideas or ways about how to accomplish this?
I am currently using JASidePanels.
Thanks!
I managed to find a very simple, elegant way to do this, that mimics the Facebook app functionality perfectly.
Here's my approach:
Create view with status bar frame
Set view background color to black, opacity to 0
Add view as subview to any root view (you need a view that will cover both the center view and the menus, so that it won't be confined to any single view - a good option for this is the container view controller used by your menu controller implementation)
Set view's opacity in your menu controller implementation's menu animation method
Here's my specific implementation, using MMDrawerController:
I subclassed MMDrawerController (I actually already had a subclass for using MMDrawerController with storyboards), and added this code to the class's init method:
// Setup view behind status bar for fading during menu drawer animations
if (OSVersionIsAtLeastiOS7()) {
self.statusBarView = [[UIView alloc] initWithFrame:[[UIApplication sharedApplication] statusBarFrame]];
[self.statusBarView setBackgroundColor:[UIColor blackColor]];
[self.statusBarView setAlpha:0.0];
[self.view addSubview:self.statusBarView];
}
// Setup drawer animations
__weak __typeof(&*self) weakSelf = self; // Capture self weakly
[self setDrawerVisualStateBlock:^(MMDrawerController *drawerController, MMDrawerSide drawerSide, CGFloat percentVisible) {
MMDrawerControllerDrawerVisualStateBlock block;
block = (drawerSide == MMDrawerSideLeft) ? [MMDrawerVisualState parallaxVisualStateBlockWithParallaxFactor:15.0] : nil; // Right side animation : Left side animation
if(block){
block(drawerController, drawerSide, percentVisible);
}
[weakSelf.statusBarView setAlpha:percentVisible]; // THIS IS THE RELEVANT CODE
}];
I also added self.statusBarView as a private property.
The first section of code creates a view, configures it, and adds it as a subview of the MMDrawerController subclass's view. The OSVersionIsAtLeastiOS7() method is a custom method that simplifies the check to see if the device is running iOS 7 (if it isn't, your custom view will show up below the status bar, which you don't want).
The second section of code is MMDrawerController's setDrawerVisualStateBlock method, which sets the animations code to be performed when a menu is being opened and closed. The first few lines of code are boilerplate code that sets one of the prebuilt animations blocks to each menu (I wanted parallax on the left, but nothing on the right). The relevant code is the last line of the block: [weakSelf.statusBarView setAlpha:percentVisible];, which sets the status bar view's opacity to match the percentage that the menu is currently open. This allows for the smooth cross animation you see in the Facebook app. You'll also notice I've assigned self to a variable weakSelf, so as to avoid the "retain cycle" compiler warning.
This is my specific approach using MMDrawerController and a subclass, which I did more for convenience because I already had the subclass in place, than because it is necessarily the best approach or the only way to do it. It could probably be implemented in several other ways, using MMDrawerController without a subclass, or using any other side-drawer menu implementation.
The ending result is a smooth fading to black animation behind the status bar, exactly as you see in the new Facebook app.
I've been trying to accomplish the same thing. The method I am using to do this is based on the following concepts:
A background image with a height of 64 points will fill both the
UINavigationBar and the UIStatusBar.
A background image with a height of 44 points will fill the UINavigationBar and leave the
UIStatusBar black.
You can add an subview to the top of the current navigationController's view and it will sit underneath the UIStatusBar.
So, first, you need to create two images with your desired UINavigationBar look:
A 640x128px image to cover navigation bar and status bar (ImageA)
And a 640x88px image to cover the navigation bar but leave the status bar black (ImageB).
In the application:didFinishLaunchingWithOptions: method, set the background of your UINavigationBar with ImageA with [[UINavigationBar appearance] setBackgroundImage:[UIImage imageNamed:#"ImageA.png"] forBarMetrics:UIBarMetricsDefault];
When the side menu starts to open, you are going to want switch the UINavigationBar so it uses ImageB and create a view which you will add underneath the UIStatusBar. Here is some sample code for doing just that:
// Add a property for your "temporary status bar" view
#property (nonatomic, strong) UIView *temporaryStatusBar;
And in the code where the side menu starts to open:
// Create a temporary status bar overlay
self.temporaryStatusBar = [[UIView alloc] initWithFrame:[[UIApplication sharedApplication] statusBarFrame]];
self.temporaryStatusBar.backgroundColor = [UIColor yourColor];
[self.navigationController.view addSubview:self.temporaryStatusBar];
// Update both the current display of the navigationBar and the default appearance values
[[UINavigationBar appearance] setBackgroundImage:[UIImage imageNamed:#"imageB.png"] forBarMetrics:UIBarMetricsDefault];
[self.navigationController.navigationBar setBackgroundImage:[UIImage imageNamed:#"imageB.png"] forBarMetrics:UIBarMetricsDefault];
[self.navigationController.navigationBar setNeedsDisplay];
As the side menu animates open, or as the user pans the menu, all you need to do then is adjust the alpha level of the UIStatusBar overlay. When the side menu is fully open, the UINavigationBar should have ImageB as its background image and the UIStatusBar overlay should have an alpha of 0. When the side menu closes, you'll want to replace the UINavigationBar background with ImageA and remove the UIStatusBar overlay.
Let me know if this works for you!
You can use this awesome slide menu library
https://github.com/arturdev/AMSlideMenu
In this demo project you can see how to do that by writing 4 lines of code.
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
// Setting navigation's bar tint color
self.navigationController.navigationBar.barTintColor = [UIColor colorWithHex:#"#365491" alpha:1];
// Making view with same color that navigation bar
UIView *statusBarView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, self.view.bounds.size.width, 20)];
statusBarView.backgroundColor = [UIColor colorWithHex:#"#365491" alpha:1];
// Replace status bar view with created view and do magic :)
[[self mainSlideMenu] fixStatusBarWithView:statusBarView];
}
In storyboard, in a view controller I tried add a navigation bar under the status bar, running it, it is transparent and shows a label that's supposed to be blurred, like by navigation bar.
But when placing the same view controller embedded in a navigation view controller, the underneath background image could be blurred, which is my intention.
What are these two way different results? What need to do for the firs method to make status bar blur?
Thanks!
In iOS 7 the status bar is transparent by default. The blurring you're seeing when there's also a navigation bar is actually created by the navigation bar. So to create the effect you're looking for without a navigation bar, you need to position a view that produces a blurring effect beneath the status bar.
For reference, add your view with a frame provided by:
CGRect statusBarFrame = [[UIApplication sharedApplication] statusBarFrame];
I know this is old, just for reference, I solved this by setting self.navigationController.navigationBar.clipToBounds = NO
I haven't tested this completely, but go to your plist file and check the following settings:
"View controller-based status bar appearance": If this is set to "Yes", then it should display a status bar that is unique to each View Controller, which might be what you need.
"Status bar style": You may set this to three different styles: Opaque black, Gray, and Transparent black.
Let me know if this worked for you.
UINavigationController will alter the height of its UINavigationBar to either 44 points or 64 points, depending on a rather strange and undocumented set of constraints. If the UINavigationController detects that the top of its view’s frame is visually contiguous with its UIWindow’s top, then it draws its navigation bar with a height of 64 points. If its view’s top is not contiguous with the UIWindow’s top (even if off by only one point), then it draws its navigation bar in the “traditional” way with a height of 44 points. This logic is performed by UINavigationController even if it is several children down inside the view controller hierarchy of your application. There is no way to prevent this behavior.
It looks like you are positioning your view hierarchy in the first example starting at the point (0,20). Also, is that a UIToolbar or a UINavigationBar? If it's the latter, why are you using it by itself and not using it inside of UINavigationController?
If you do not use UINavigationController and are instead using custom view controller containers, you'll need to position your views accordingly.
See this answer for a thorough explanation.
I have similar UI design and based on Matt Hall answer and some article I've googled, I come up with something like this:
- (void)viewDidLoad {
[super viewDidLoad];
if (NSFoundationVersionNumber>NSFoundationVersionNumber_iOS_6_1) {
CGRect statusBarFrame = [self.view convertRect: [UIApplication sharedApplication].statusBarFrame fromView: nil];
UIToolbar *statusBarBackground = [[UIToolbar alloc] initWithFrame: statusBarFrame];
statusBarBackground.barStyle = self.navBar.barStyle;
statusBarBackground.translucent = self.navBar.translucent;
statusBarBackground.autoresizingMask = UIViewAutoresizingFlexibleBottomMargin | UIViewAutoresizingFlexibleWidth;
[self.view addSubview: statusBarBackground];
}
}
Where self.navBar points to navigation bar added in storyboard. This is needed only in case when it runs on iOS7 that is why I've added this condition (my app has to support iOS5).
This works like a charm.
alternative approach (enforce status bar size) is also good:
- (void)viewDidLoad {
[super viewDidLoad];
if (NSFoundationVersionNumber>NSFoundationVersionNumber_iOS_6_1) {
CGRect statusBarFrame = [self.view convertRect: [UIApplication sharedApplication].statusBarFrame fromView: nil];
self.navBar.frame = CGRectUnion(statusBarFrame, self.navBar.frame);
}
}
I've found another solution I think this is best since it involve only storyboard and no code is required.
Switch storyboard view to 6.1 mode (view as: iOS 6.1 and Earlier)
Select problematic UINavigationBar
in size section add 20 delta height in "iOS6/7 Deltas"
Switch back view to 7.0 mode (view as: iOS 7.0 and Later), and be happy with result.
when you embed view controller with navigation view controller that time you will see navigation bar to all the view controller you are pushing to from same view controller. In your first case you are adding the navigation bar object, insted of that you can select view controller from storyboard , go to attributes inspector tab & from their select Top bar as translucent navigation bar.
1) What is the difference between the three lines of code below?
2) Also why does only the third line of code work if I'm returning to a viewController and the previous viewController has set the navigationBar to hidden through the same approach [self.navigationController setNavigationBarHidden:NO] my assumption was that all three lines do the same thing?
self.navigationController.navigationBar.hidden = NO;
[self.navigationController.navigationBar setHidden:NO];
[self.navigationController setNavigationBarHidden:NO];
Follow up:
Why when I need to run this code:
[self.navigationController.navigationBar setBackgroundImage:incorrectAnswerNavigationBarBackgroundImage forBarMetrics:UIBarMetricsDefault];
It only works, working being setting the background image, otherwise the nav bar is just white.
if I have both these lines:
[self.navigationController setNavigationBarHidden:NO];
self.navigationController.navigationBar.hidden = NO;
If I leave out self.navigationController.navigationBar.hidden = NO; the space for the nav bar pops down but it's just white, there is no background image. If I have both lines it works and there is a background image.
The first two are functionally identical; the difference being one uses the dot notation while the other doesn't. These two methods both fire - (void) setHidden:(BOOL)hide on the navigationBar property on the navigation controller.
Now the third one is a completely different method. It's - (void) setNavigationBarHidden:(BOOL)hide and is defined on UINavigationController. The reason why this one works is this method is informing the navigation controller that you wish the navigation bar to be hidden while the first two manually set the navigation bar to be hidden. The first two's changes are undone if UINavigationController calls any methods that modify the hidden property of the navigation bar, hence why the setNavigationBarHidden: method was created so you'd have a way of informing UINavigationController that no matter what it does, it should hide the navigation bar and not change it to be showing.
EDIT: For the second part of this question, you actually need to be calling - (void)setNavigationBarHidden:(BOOL)hidden animated:(BOOL)animated on UINavigationController. That's the proper documented method for UINavigationController.