In our app, we temporarily hide the status bar as part of the animation between transitioning between two screens that both need different status bar styles.
We have a percent driven animation transition which when started, hides the status bar with animation and when finish re shows the status bar.
On iOS 11 the safe area insets include the status bar height which can be variable, and when hidden the top inset of the safe area drops to 0 height.
This re-adjusts all our views and has a horrible jump between view sizes.
We still want to constrain our views to the safe area since we're trying to support iPhone X.
Can we temporarily disable the change to the safe area insets when hiding the status bar?
Constraints that are set to the safe area are affected by the status bar as well as the views actual location on the screen and its transform. If you always want to just apply the top (or bottom) safe area height to your view constraint, you can do this by use of a custom constraint instead.
The following constraint will automatically set its constant value to the height of the device's top safe area height, not affected by the status bar or other parameters. To use it, change the class of any constraint into this, and their constant will always be the safe area height. Note that it will not change its value when the device is rotated.
Objective-C
#interface TopSafeAreaContraint : NSLayoutConstraint
#end
#implementation TopSafeAreaContraint
- (void)awakeFromNib {
[super awakeFromNib];
if (#available(iOS 11.0, *)) {
UIEdgeInsets insets = [UIApplication sharedApplication].keyWindow.safeAreaInsets;
self.constant = MAX(insets.top, 20.0);
} else {
// Pre-iOS 11.0
self.constant = 20.0;
}
}
#end
Swift
class TopSafeAreaConstraint: NSLayoutConstraint {
override func awakeFromNib() {
super.awakeFromNib()
if #available(iOS 11.0, *) {
let insets = UIApplication.shared.keyWindow?.safeAreaInsets ?? .zero
self.constant = max(insets.top, 20)
} else {
// Pre-iOS 11.0
self.constant = 20.0
}
}
}
I've been experiencing a similar issue so I came up with a slightly different approach. This is not a direct answer to the problem. It is a workaround which worked in my case.
I had two different view controllers, both of which must have a navigation bar (but a navigation controller is not required). The 1st view controller is presenting the 2nd one in a modal fashion. The problem is that the 2nd view controller is landscape only, which means that on iPhones with edge-to-edge displays any overrides of prefersStatusBarHidden are ignored and the system always returns true (see here).
What I did was simulate the status bar height through a custom view, and then adjust the height constraint constant in viewDidLoad(_:).
I got no ugly navbar or view controller jumps after that.
Get a reference to the safe area top constraint and try changing the constant of that constraint to adjust for the hide/show of the status bar. This works for me, though in a somewhat different situation where I set the constraint constant within the prefersStatusBarHidden method in reaction to showing/hiding a toolbar.
Try add 2 constraints:
1) view - superview
2) view - safeArea
You generally want your scroll view to go under status bar (safe area) and simply adjust it's content inset, instead of laying out only inside the safe area. The adjustement is automatic for UIScrollView by default. See contentInsetAdjustmentBehavior.
If you have scroll view full view size (under safe area), hiding and showing status bar works perfectly for me, even though the safe area insets are being modified in the scroll view automatically.
Related
I have a UIScrollView that is constraint to view.topAnchor, so it scrolls up to the top edge of an iPhoneX. However, when I add content to the scrollView (such as a UIImage), and constraint it scrollView.topAnchor the content is inset to the safeAreaLayoutGuide. This seems to happen with scrollViews since if I take the content out and place it in the view, it also moves to the screen edge.
Anyone have any ideas how to fix this?
Try setting the view controller's automaticallyAdjustsScrollViewInsets to false in viewDidLoad (self.automaticallyAdjustsScrollViewInsets = false). From the documentation:
The default value of this property is true, which lets container view controllers
know that they should adjust the scroll view insets of this view controller’s
view to account for screen areas consumed by a status bar, search bar, navigation
bar, toolbar, or tab bar. Set this property to false if your view controller
implementation manages its own scroll view inset adjustments.
UPDATE:
As was mentioned, automaticallyAdjustsScrollViewInsets is deprecated in iOS 11+. You can do a version check and to fix it for older versions of iOS as well. Example:
if #available(iOS 11.0, *) {
scrollView.contentInsetAdjustmentBehavior = .never
} else {
self.automaticallyAdjustsScrollViewInsets = false
}
Im trying port my app to iOS7, but the height of my TableView increases in ios 7 while it is correct in ios 6. Due to which last row (cell) is almost half under the tab bar.
Im searching a lot for it, but i dont find any solution. Can anyone help me?
Have a look at iOS 7 UI Transition Guide
if ([self respondsToSelector:#selector(edgesForExtendedLayout)]) {
self.edgesForExtendedLayout = UIRectEdgeNone;
}
Use edgesForExtendedLayout to specify which edges of a view should be extended, regardless of bar translucency. By default, the value of this property is UIRectEdgeAll.
if ([self respondsToSelector:#selector(extendedLayoutIncludesOpaqueBars)]) {
self.extendedLayoutIncludesOpaqueBars = NO;
}
If your design uses opaque bars, refine edgesForExtendedLayout by also setting the extendedLayoutIncludesOpaqueBars property to NO.
if ([self respondsToSelector:#selector(automaticallyAdjustsScrollViewInsets)]) {
self.automaticallyAdjustsScrollViewInsets = NO;
}
If you don’t want a scroll view’s content insets to be automatically adjusted, set automaticallyAdjustsScrollViewInsets to NO.
You can also set topLayoutGuide and bottomLayoutGuide. They indicate the location of the top or bottom bar edges in a view controller’s view. If bars should overlap the top or bottom of a view, you can use Interface Builder to position the view relative to the bar by creating constraints to the bottom of topLayoutGuide or to the top of bottomLayoutGuide.
Moreover, you can also make adjustments in Interface builder.
And if you are not using autolayout, you can set the deltas for iOS6/7.
Open Storyboard, in Utilities of your UIViewController open "Attributes inspector"
"Under Top Bars" is ticked?
I started building a TableView in my app by using a TableViewController in a storyboard. When you do this, you have a very cool effect when you scroll down your list : the cells moving behind the nav bar get blurred.
Some time later, I had to move from this TableViewController to a ViewController with a TableView inside (I had to add other views at the bottom of the table).
In order to avoid having the first cells hidden by the navigation bar (being over it), I added constraints to the Top and Bottom Layout Guides, and to the left and right edges of the view.
This works fine, but I lost the cool blurred scrolling effect : the cells seem to be disappearing before going behind the navigation bar.
I've seen workarounds with people not using constraints and putting magic numbers in interface builder. I cannot do this, first because I dislike it, and second because I have to be iOS 6 compatible.
What did I miss to be able to benefit again from the blurred navigation bar effect ?
You have to manually adjust the contentInset of the table view and make sure the table view frame origin is 0, 0.
In this way the table view will be below the navigation bar, but there will be some margin between the content and the scroll view edges (the content gets shifted down).
I advise you to use the topLayoutGuide property of the view controller to set the right contentInsets, instead of hard coding 64 (status bar + navigation bar).
There's also bottomLayoutGuide, which you should use in case of UITapBars.
Here is some sample code (viewDidLoad should be fine):
// Set edge insets
CGFloat topLayoutGuide = self.topLayoutGuide.length;
tableView.contentInset = UIEdgeInsetsMake(topLayoutGuide, 0, 0, 0);
By the way, this properties of UIViewController might help you (you should not need to change their default values, but I don't know what your view hierarchy is):
automaticallyAdjustsScrollViewInsets
edgesForExtendedLayout
extendedLayoutIncludesOpaqueBars
The tableView needs to be full screen. That is underneath the top and bottom bars. Note don't use the top and bottom layout guides as they are used for positioning relative to the bars not underneath.
Then you need to manually set the content inset of the tableview. This sets the initial scroll position to under the top bar.
Something like:
CGSize statusBarSize = [[UIApplication sharedApplication] statusBarFrame].size;
CGFloat h=MIN(statusBarSize.width, statusBarSize.height);
UIEdgeInsets e = UIEdgeInsetsMake(self.navigationController.navigationBar.bounds.size.height + h,
0.0f,
0.0f,
0.0f);
self.tableView.contentInset = e;
Not you get this functionality for free when using a tableView controller and the "Automatically Adjust content inset" settings
You probably have the coordinates of your tableView not set to (0, 0) to map to those of the viewController.view.frame or viewController.view.bounds. If you have done that, try setting
self.navigationController.navigationBar.translucent = YES;
UIViewController property edgesForExtendedLayout does the trick. If you are using storyboards just make sure Extended Edges Under Top Bars is on (and it is by default).
If you are creating your view controller programmatically try this:
- (void)viewDidLoad
{
[super viewDidLoad];
self.edgesForExtendedLayout = UIRectEdgeAll;
}
And of course, your table view needs to have proper autoresizing mask/layout constraints
edgesForExtendedLayout is not what you want here, as this will limit the table view underneath the navigation bar. In iOS 7, the view controllers uses fullscreen by default, and the property controlling where the tableview content starts is automaticallyAdjustsScrollViewInsets. This should be YES by default, so check if it is somehow set to NO, or try setting it explicitly.
Check this answer for a good explanation on how this works:
https://stackoverflow.com/a/19585104/1485715
I get a weird interface bug with my UIScrollView and I cant figure out how to solve it. I only wrote one line of code (shown below) and it is a blank project's setup easily reproducible!
Setting:
I have a UIScrollView that contains a UISegmentedControl (since the segments of
the control are loaded dynamically, it could exceed the width of the screen and the scrollView is supposed to scroll the segmentedControl horizontally, the height of the scrollview is the same as the UISegmentedControl's).
The ViewController that contains this is embedded in a tabBar (or navigation bar, which also shows the bug). The whole thing is using Auto-Layout.
Bug:
When I scroll the SegmentedControl some degree to the right and then switch the viewController by clicking the other tab on the tabBarController, the content-offset of the segmented control gets weirdly shifted when switching back to the initial viewcontroller. When I try to scroll to the leftmost part of the scrollview it won't let me. When switching the tabs a couple of times, it gets fixed again and I can do this over.
What I did (can you reproduce this?):
Create a blank single-view ios project
Embed the already given viewController in a tabbarcontroller.
Put a scrollView on the upper portion of the view that fits the screen from left to right.
Put a UISegmentedControl on the topleft corner of the scrollview and drag the scrollview to fit the segmented controls height height
Change the Segmented control's width a bit so xcode adds a width-constraint. in the segmented control's width constraint change the width constraint's relation to "greater than or equal"
create an outlet to the segmented control
in viewDidload add this code
[self.segmentedControl insertSegmentWithTitle:#"A really long title so it you have to scroll to see it" atIndex: 0 animated: NO];
Create a blank viewcontroller and add it as a second viewController for the tabbarController.
This is how my storyboard looks like:
Now run the project, scroll the segmented control to it's right end as far as it goes. Switch the tab and switch back and please tell me how your scrollview now behaves - and WHY.
My guess would be it has something to do with Auto Layout maybe? Can't figure out what though.
I tried fixing this by setting the scrollView's contentSize in viewDidAppear or changing the content offset of the scrollView in viewDidAppear or changing frames, combination of those and what not....
Extra question:
Is it no longer neccessary to set the scrollViews contentSize property? Why does it scroll the content automatically?
After googeling I found the answer in another StackOverflow question.
What you need to do is save the scrollview.contentOffset on viewWillDisappear,
set it to CGPointZero on viewDidDisappear and set it back to the saved state on viewDidLayoutSubviews:
-(void) viewWillDisappear: (BOOL) animated {
self.lastContentOffset = self.scrollView.contentOffset;
[super viewWillDisappear: animated];
}
-(void) viewDidDisappear:(BOOL)animated {
[super viewDidDisappear: animated];
self.scrollView.contentOffset = CGPointZero;
}
- (void)viewDidLayoutSubviews {
[super viewDidlayoutSubviews];
self.scrollView.contentOffset = self.lastContentOffset;
}
I have a problem with top 20px behind status bar, specifically I cannot put anything there.
When I created the UI I used the storyboard approach and set status bar style to translucent black. But when do the layout in Xcode my views' height is fixed to 460px (grayed out).
Please help.
Got an answer from a friend, will mark his solution as right answer as soon as he posts it here. For now here is the solution:
In Interface Builder set up the view controller as wanting full screen and of freeform size: http://cl.ly/0x1p1u3q3B1y3b3C3U2n
Then in the view's size settings set its height to 480px: http://cl.ly/1p1b0e060p1Y37393D08
Ensure status bar style is translucent black in the Info.plist: http://cl.ly/153Y391S1b0G3J3z3Y1O
Get satisfactory result: http://cl.ly/0Q1M390i3A3h2F3u2T19
Here's solution without single line of code.
In Interface Builder set up the view controller as wanting full screen and of freeform size.
In the view's size settings set its height to 480px
Ensure status bar style is translucent black in the Info.plist.
Launch and observe
See the links for screenshots of each step in the top post.
Okay, having been told it's possible, I tried placing a view on top of the main view and setting its y coordinate to -20. Then I west to Info.plist and set Status bar style to Transparent black style (alpha of 0.5). The view I placed was visible beneath the status bar.
This only happened at run time; in Interface Buider the simulated translucent status bar is gray but opaque.
Edit: If you want to move your view controller's main view into this space, it is possible during viewWillAppear. (If you try to move it during viewDidLoad, it gets moved back afterwards.)
- (void)viewWillAppear:(BOOL)animated;
{
[super viewWillAppear:animated];
// Move the main view upwards if it isn't already there.
CGRect frame = [[self view] frame];
frame.size.height += frame.origin.y;
frame.origin.y = 0;
[[self view] setFrame:frame];
}
However, it gets moved back after rotation, and my attempts to respond to rotation ended up breaking the cell layout if I did this to a UITableView.
Here is the Swift version. Seems to work. Good solution for taking those App Store screen shots (well in addition to making the info.plist changes.
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
// Move the main view upwards if it isn't already there.
var frame: CGRect = self.view.frame
frame.size.height += frame.origin.y
frame.origin.y = 0
self.view.frame = frame
}
Just make negative Y (-20) coordinate in interface builder.
Try changing the value for "Status Bar" in the "Simulated Metrics" section of the Attributes inspector in Interface Builder. If you set it to "Inferred", Xcode will make no assumptions about the view controller, allowing you to set the size of the view to ANY size you want (even sizes that don't make any sense). Check out this post about the status bar for info on hiding it in your app.