Here's Apple's document of scrollEdgeAppearance for UINavigationBar:
When a navigation controller contains a navigation bar and a scroll
view, part of the scroll view’s content appears underneath the
navigation bar. If the edge of the scrolled content reaches that bar,
UIKit applies the appearance settings in this property.
If the value of this property is nil, UIKit uses the settings found in the
standardAppearance property, modified to use a transparent background.
If no navigation controller manages your navigation bar, UIKit ignores
this property and uses the standard appearance of the navigation bar.
When running on apps that use iOS 14 or earlier, this property applies
to navigation bars with large titles. In iOS 15, this property applies
to all navigation bars.
Here's my demo. I embed ViewController into a UINavigationController, then drag a UITableView and set it as the subview of ViewController's view. I set the following appearance properties to UINavigationBar:
class ViewController: UIViewController {
#IBOutlet weak var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
let redAppearance = UINavigationBarAppearance()
redAppearance.backgroundColor = .red
navigationController?.navigationBar.standardAppearance = redAppearance
let greenAppearance = UINavigationBarAppearance()
greenAppearance.backgroundColor = .green
navigationController?.navigationBar.scrollEdgeAppearance = greenAppearance
}
}
I thought the initial navigation bar should be red, and it should turn green when I scroll the table view. But the truth is it's the other way around.
I did some search and most people just set standardAppearance and scrollEdgeAppearance to the same instance, or set one of them to be nil, so I'm quite confused about these two properties in iOS 15.
You have understood perfectly but backward.
navigationController?.navigationBar.scrollEdgeAppearance = greenAppearance
means the nav bar will be green only when the scrollable view is scrolled all the way down. That is what you see in your first screen shot.
As soon as you scroll up a little bit, the nav bar starts adopting its standard appearance, which is red. That is what you see in your second screen shot.
scrollEdgeAppearance is applied when the edge of the scrollable content reaches the nav bar so what you're seeing is correct. Many people set both scrollEdgeAppearance and standardAppearance to the same since they want the behavior to be no different when the user scrolls but your implementation is certainly a valid use case.
In here, I have hide the default navigation bar and created a custom navigation bar. When run on iOS 13 device using Xcode 11 two navigation bars showing.
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
[self.navigationController setNavigationBarHidden:YES animated:NO];
}
Navigation bar not aligned to the top of the screen properly after updating to iOS 13
Any help will be appreciated.
Try the safeAreaLayoutGuide
Apple: The layout guide representing the portion of your view that is unobscured by bars and other content.
In your viewcontroller, if you want to use the full screen and override the navigationbar entirely with your own subclass, then make sure you do this in the initilizer and the viewwill appear methods
[self.navigationController setNavigationBarHidden:true animated:true];
[self setExtendedLayoutIncludesOpaqueBars:true];
from there, you have to access the values of the top layoutguide which will be 44 points and 20 points depending on the size of the iphone you accesss those this way in loadview or viewdidload or a custom subclass
UIWindow *window = UIApplication.sharedApplication.keyWindow;
CGFloat topPadding = window.safeAreaInsets.top;
then the frame of your custom navigation bar is then calculated like so:
CGRect tempRect = CGRectMake(0, 0, self.view.frame.size.width, topPadding + 44);
44 is there because it's constant across all iphones and ipads for now (this could change in the future and nullify this solution, but this is the standard for now)
that will return correct dimensions for all iphone sizes.
THIS => [self setExtendedLayoutIncludesOpaqueBars:true];
IS ABSOLUTELY necessary to make this work as i explained. this isn't a good idea to travel down this path because very rarely do you ever need to override and replace the navigation bar with some custom UIView, but if you need to do it, this is how you do it. Just know that this will break if you're not updating frame sizes during on call status and other edge case conditions.
In iOS 11 the search bar will now change the navigation bar height to 56dp when adding a search bar to the navigationItem.titleView
I like the height change and don't intend on forcing the height to stay at 44dp or lower.
unfortunately when transitioning from one view controller to another the pushed view will be drawn with the larger navigation bar in mind and then the bar height is changed after the transition is finished.
That looks a little like this:
I need a way of getting the navigation controller to recognise the height change during the transition so that it can animate to the smaller size and draw the view correctly.
I have one current fix which I don't like because it's a little jumpy and it's more work the app has to do and also it has to re-evaulate it's views regardless of which view controller it's being pushed from.
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
if #available(iOS 11, *) {
navigationController?.view.layoutSubviews()
}
}
So far I haven't found any similar questions on stack overflow, any comments from WWDC and nothing in the official apple documentation.
I've seen many apps deal with this however. The apple contacts app will create what looks like two navigation bars and will move between them without animating the height changes and the fb messenger app will perfectly transition between the heights and even allow for the interactive pop transition.
Using this one, it will keep your searchbar's height fixed
if #available(iOS 11.0, *) {
searchBar.heightAnchor.constraint(equalToConstant: 44.0).isActive = true
}
I tried a lot of different functions.
This thing is the only one that really helps
OBJ C:
if (#available(iOS 11, *)) {
self.navigationController.view.layoutSubviews;
}
SWIFT:
if #available(iOS 11, *) {
self.navigationController.view.layoutSubviews()
}
I have a view controller in my app that has a navigation bar dragged on it in the storyboard. It was working fine in the iOS 6 but in iOS 7 it look like this:
The status bar and the navigation bar should no collide with each other. I have seen a lot of such questions on the stack overflow but they didn't of much help to me.
Some questions say that i should use this "self.edgesForExtendedLayout = UIRectEdgeNone;" but it didn't work. Some say i should remove the navigation bar and embed it inside the navigation controller that i cannot do due to the way my program is implemented. Some solutions suggests to use the view bounds and all but it didn't work for me as well.
What is the one thing that can help me resolve this issue. Thanks in advance!
UPDATE: I have embedded the view controller inside a uinavigation controller. Removed the navigation bar that was earlier manually added in it. Now it looks ok in the storyboard but when i run it, it shows the following:
It is showing text from another view controller that is currently behind it that is its parent view controller. Means its transparent now. Can anyone point out what i am doing wrong?
The latest version of the iOS has brought many visual changes and from a developer's point of view, the navigation and status bar are two noticeable changes.
The status bar is now transparent and navigation bar behind it shows through. The navigation bar image can even be extended behind the status bar.
First of all, if you are a beginner and have just started iOS development and are confused the way status bar and navigation bar is working, you can simply go through a blog post HERE that i found very useful. It has all the information related to navigation and status bar in iOS 7.
Now coming to the answer of your question. First of all i can see two different problems. One is that your status bar and navigation bar are both kind of colliding with each other as shown by you in the question with an image.
PROBLEM: Well the problem is that your have earlier dragged a navigation bar in your view controller which was working in iOS 6 correctly but with the arrival of iOS 7 SDK, this approach is resulting in status bar and navigation bar overlapping with each other.
SOLUTION to First Problem: You can either use UIBarPositionTopAttached or you can use view bounds and frames, i can also suggest and link you to Apple's documentation and bla bla bla but that would take some time for you to solve the issue.
The best and the most easiest way to solve this issue is to just embed your view controller inside a navigation controller and thats it. You can do it by just selecting the view controller and going to Editor > Embed In > Navigation Controller. (If there is any content on your old navigation bar, you can first drag it down, embed the view controller in navigation controller and then move the bar buttons on the new navigation bar and then delete the old navigation bar)
SOLUTION to Second Problem: This solution is for your specific question that you have mentioned in the update and is not for the general public reading this. As you can see that navigation and status bar is not visible and a transparent area is showing the parent view controller. I am not really use why you are facing this issue but most probably because of some third party library like ECSlidingView or any other is involved. You can select this view controller in your storyboard and set the background color of the view to be the same as your navigation bar. This will stop showing the parent view controller behind and your navigation bar and status bar will start showing. Now you can cover the rest of your view controller with text view or what ever your are using in it.
Hope this helps!
The navigation bar is too close to the status bar because starting in iOS 7, the status bar is more of an overlay over the whole view controller's view. Since your navigation bar is at (0, 0), the status bar will show on top of the navigation bar. To solve this, simply move the navigation bar down (or, as others have said), create a constraint between the navigation bar and the topLayoutGuide.
When you do that, you will see that there is now a 20 point gap between the navigation bar and the top of the screen. That's because you just moved the navigation bar down 20 points. "But UINavigationController can do it right!" Absolutely, and it does so by implementing UIBarPositioningDelegate on your view controller. This is a one-method protocol that should be implemented like this:
- (UIBarPosition)positionForBar:(id<UIBarPositioning>)bar {
return UIBarPositionTopAttached;
}
After adding your view controller as the delegate for the navigation bar, you'll notice the navigation bar is still shifted down 20 points, but its background will extend up underneath the status bar, just like in UINavigationController.
Another thing you're seeing is that the navigation bar is translucent, meaning anything underneath the navigation bar will be visible to some extent. The translucent property on UINavigationBar is set to YES by default on iOS 7. Before iOS 7, the default was NO.
you can simply do this:
1) add a constrain between the Navigation Bar and Top Layout Guide (select navigationBar, hold ctrl key and go to Bottom Layout Guide, unhold ctrl key)
2) select vertical spacing:
3) set constant to 0:
Result:
UPDATE
In your AppDelegate file you can add this:
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool
{
// Prevent Navigationbar to cover the view
UINavigationBar.appearance().translucent = false
}
I suggest you in your viewDidLoad method you try:
self.navigationController.navigationBar.translucent = NO;
(by default it is yes now)
https://developer.apple.com/library/ios/documentation/uikit/reference/UINavigationBar_Class/Reference/UINavigationBar.html#//apple_ref/occ/instp/UINavigationBar/translucent
This works for me i hope you also have same luck :).
Add below code in your view.
-(void) viewDidLayoutSubviews
{
CGRect tmpFram = self.navigationController.navigationBar.frame;
tmpFram.origin.y += 20;
self.navigationController.navigationBar.frame = tmpFram;
}
It basically change location of navigation bar.
This is new feature with IOS7. Instead of staring at 20 px navigation bar in IOS7 staring at 0 px. As a solution shift the whole view downwards to 20 px or you can use image for navigation bar with height 64px.
In case it still helps someone, this is what worked for me for moving the Navigation Bar little bit down in ios 7 and above:
-(void)viewWillLayoutSubviews
{
float iosVersion = 7.0;
if ([[[UIDevice currentDevice] systemVersion] floatValue] >= iosVersion) {
// iOS 7+
CGRect viewFrame = self.view.frame;
viewFrame.origin.y += 10;
self.view.frame = viewFrame;
}
}
On a device with ios 6.1 and below the Navigation Bar will be unchanged, as it was before.
And this is what I used to make the contents of the Status Bar lighter:
-(UIStatusBarStyle)preferredStatusBarStyle{
return UIStatusBarStyleLightContent;
}
If your UIViewController is NOT in a UINavigationController and you're using UIStoryBoard, you can set the "iOS 6/7 Deltas" to 20 for the delta Y, for every subview that needs to be offset from the UIStatusBar.
Using Swift:
As #Scott Berrevoets said in his answer you need to implement the method positionForBar in the protocol UIBarPositioningDelegate, but as the UINavigationBarDelegate protocol implements this protocol :
public protocol UINavigationBarDelegate : UIBarPositioningDelegate {
...
}
You only need to set the delegate of the UINavigationBar you set using Storyboard and implement the method and it's done, like in this way:
class ViewController: UIViewController, UINavigationBarDelegate {
#IBOutlet weak var navigationBar: UINavigationBar!
override func viewDidLoad() {
super.viewDidLoad()
self.navigationBar.delegate = self
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func positionForBar(bar: UIBarPositioning) -> UIBarPosition {
return UIBarPosition.TopAttached
}
}
NOTE:
It's worth to mention if you set the position of the y-axis of the navigation bar, let's say to 40 from the top, then it will extend underneath to the top from this position, to simulate the behaviour of the UINavigationController you need to set to 20 from the top.
I hope it will help you.
First, set UIViewControllerBasedStatusBarAppearance to NO in Info.plist.
Then, in AppDelegate's application:didFinishLaunchingWithOptions: method add:
if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 7) {
[application setStatusBarStyle:UIStatusBarStyleLightContent];
self.window.clipsToBounds = YES;
self.window.frame = CGRectMake(0, 20, self.window.frame.size.width, self.window.frame.size.height-20);
self.window.bounds = CGRectMake(0, 20, self.window.frame.size.width, self.window.frame.size.height);
}
return YES;
In iOS 7 app occupies 100 % of screen size.This not a problem .
http://www.doubleencore.com/2013/09/developers-guide-to-the-ios-7-status-bar/
in earlier iOS window start after statusbar and in iOS 7 window starts from 0px in earlier version view height is 460 (iPhone 4s and earlier) and 548 (iPhone 5) but in iOS 7 view height is 480 (iPhone 4s and earlier) and 568 (iPhone 5 and later) so you have to start view arrangement after 2o px or you have to start view from 20px.
you can write below code in rootviewcontroller or in all viewcontroller for set view from 20px
#define IOS7_HEIGHT 64
- (void)viewDidLayoutSubviews {
NSString *currSysVer = [[UIDevice currentDevice] systemVersion];
if ([currSysVer compare:#"7.0" options:NSNumericSearch] != NSOrderedAscending)
{
CGRect frame=[self.view frame];
if (frame.origin.y!=IOS7_HEIGHT) {
frame.origin.y = IOS7_HEIGHT;
frame.size.height -= IOS7_HEIGHT;
[self.view setFrame:frame];
[self.view layoutSubviews];
}
}
}
here height is 64 because 20 for statusbar and 44 for navigationbar.
try below code it will help you. and your problem will be solved.
For the ones who are having problems implementing #Masterfego 's solution (which is also the official, but I have had problems with Xcode 6.3 and automatic constraints), this is what I did:
I have a UIViewController with an added Navigation Bar. I selected the NAvigation bar and added a height constraint of 64px. We later see a warning that the navbar will be higher (but this is what we do). Finally, you can see that the Status bar looks nice and has the same color as the navbar. :)
PS: I can't post images yet.
You can probably create constraints that are attached to the top layout guide to specify the navigation bar's position relative to the status bar. See the iOS 7 UI Transition Guide: Appearance and Behavior section for more information about using the layout guides.
it's the best answer.
But I wanted know how to use a Storyboard and dragged UINavigationBar on it.
When I implemented the delegate method, and set the return result to UIBarPositionTopAttached, it did not work.
- (void)viewDidLoad{
self.navigationbar.delegate = self;
}
- (UIBarPosition)positionForBar:(id<UIBarPositioning>)bar{
NSLog(#"Got it");
//
// CGRect frame = self.navigaitonBar.frame;
//
// frame = CGRectMake(0, 20, CGRectGetWidth(frame), CGRectGetHeight(frame));
// self.navigaitonBar.frame = frame;
//
// NSLog(#"frame %f",frame.origin.y);
return UIBarPositionTopAttached;
}
If you use Xcode 6 and Swift, you can make it:
Open to info.plist file of your app.
Add a ViewControllerBasedStatusBarAppearance Boolean key if it is not existing and assign value “NO”.
Add “Status bar style” key if it is not existing and select “Opaque black style” value to it.
I was facing issue when full screen ModalViewController was opening from my MainViewController, NavigationBar position was getting changed when user was coming back to MainViewController from ModalViewController.
Issue which I noticed is status bar height was not getting included when user came back to MainViewController. Please debug and check origin of your NavigationBar before and after coming back to your ViewController.
// This method will adjust navigation bar and view content.
private func adjustNavigationControllerIfNeeded() {
var frame = self.view.frame
let navigationBarHeight = self.navigationController!.navigationBar.frame.size.height
if(frame.origin.y == navigationBarHeight && !UIApplication.shared.isStatusBarHidden) {
// If status bar height is not included but it is showing then we have to adjust
our Navigation controller properly
print("Adjusting navigation controller")
let statusBarHeight = UIApplication.shared.statusBarFrame.height
frame.origin.y += statusBarHeight // Start view below navigation bar
frame.size.height -= statusBarHeight
self.view.frame = frame
self.navigationController!.navigationBar.frame.origin.y = statusBarHeight // Move navigation bar
}
}
And call it from viewWillAppear method -
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.adjustNavigationControllerIfNeeded()
}
This is a weird error that may just be an issue in Xcode for all I know. I have a tab bar controller where the first view is a UITableView with (obviously) a number of cells. When you select a cell, I've set up a segue on the MainStoryboard to go to a detail view controller. I want the tab bar to be hidden when I go to the detail view, so I went into the storyboard, chose my detail view, and clicked "Hides Bottom Bar on Push" in the editor screen that starts with "Simulated Metrics."
Everything works just fine, except that when I tap on a cell, a black bar flashes at the top of the UITableView screen, dropping the tableview cells down (as if the cells are falling down below the tab bar at the bottom), just before the screen pushes over to the detail view. The effect isn't harmful at all, but it's very disconcerting, and I'd like to smooth that out.
The only fix I've found is to uncheck the "Hides Bottom Bar when Pushed" option on the storyboard. That indeed does get rid of that black bar flash, but of course the tab bar stays on the screen when I go to the detail view, which is what I don't want.
Any ideas?
Just for completeness' sake, I went ahead and ran
[self.navigationController setToolbarHidden:YES animated: YES];
on the detail view controller's viewWillAppear method (and even tried it with the storyboard option both on and off), but there was no difference. The toolbar did indeed hide just fine, but I still got that black line at the top. So weird.
I know it is too late !!! I ran into same issue. It seems like the Auto resizing mask for the view was incorrect to be exact the UIViewAutoresizingFlexibleTopMargin. I checked this on in the xib file. If you are trying to do it in code make sure this flag -UIViewAutoresizingFlexibleTopMargin - is not included in the autoresizing mask.
Hope this will help some one in the future
I know it is a bit late, but I have same problem and I can't solve it with any of the previous answers. (I suppose this is the reason non was accepted).
The problem is that view size of the SecondViewController is same as view size of a previous ViewController, so too small to fit in a ViewController with Toolbar hidden. Thats why black background of a UITabBarController is visible at the top when transition is happening, and on a viewDidAppear view will stretch on right size.
For me it help to subclass root UITabBarController and set background color to same background color as SecondViewController has.
class RootViewController: UITabBarController {
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = Style.backgroundColor
}
}
Then you can leave checkbox checked inside storyboard and it will look ok.
P.S.
If you have some views, that is position on the bottom part of the view, you need to set bottom constraints so they are smaller by 49 (because this is the height of the toolbar), and then on viewDidAppear set the right constraint.
For example:
I have view that need to be position 44 px from bottom edge. Before, I have constraint set to 44 and I have some strange behaviour of that view. It was placed to height and then jump on the right place.
I fix this with setting constraint to -5 (44-49), and then in viewDidAppear set the constraint back to 44. Now I have normal behaviour of that view.
Wow I just had the same issue now, very painful, and no info on the net about it.
Anyway, a simple workaround for me was to change the current view's Frame moving the y coordinates up and making the height bigger by the height of the tab bar. This fixed the problem if done straight after pushing the new view onto the navigation controller. Also, there was no need to fix the Frame afterwards (it must be updated when the view is shown again).
MonoTouch code:
UIViewController viewControllerToPush = new MyViewController();
viewControllerToPush.HidesBottomBarWhenPushed = true; // I had this in the MyViewController's constructor, doesn't make any difference
this.NavigationController.PushViewController(viewControllerToPush, true);
float offset = this.TabBarController.TabBar.Frame.Height;
this.View.Frame = new System.Drawing.RectangleF(0, -offset, this.View.Frame.Width, this.View.Frame.Height + offset);
Objective C code (untested, just a translation of the monotouch code):
UIViewController *viewControllerToPush = [MyViewController new];
viewControllerToPush.hidesBottomBarWhenPushed = YES; viewControllerToPush.hidesBottomBarWhenPushed = YES;
float offset = self.tabBarController.tabBar.frame.size.height; float offset = self.tabBarController.tabBar.frame.size.height;
self.view.frame = CGRectMake(0, -offset, self.view.frame.width, self.view.frame.height + offset); self.view.frame = CGRectMake(0, -offset, self.view.frame.size.width, self.view.frame.size.height + offset);
Do this in viewWillAppear of detailViewController, it should work fine
subclass your navigation controller, or just find the navigation bar
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
let backdropEffectView = navigationBar.subviews[0].subviews[0].subviews[0] //_UIBackdropEffectView
let visualEffectView: UIVisualEffectView = UIVisualEffectView(effect: UIBlurEffect(style: .Light))
visualEffectView.frame = backdropEffectView.frame
backdropEffectView.superview?.insertSubview(visualEffectView, aboveSubview: backdropEffectView)
backdropEffectView.removeFromSuperview()
}