iOS Personal Hotspot breaks layout when navigation bar is hidden - ios

I'm writing an application for iOS with Swift and I'm using the auto layout in all my View Controllers but When the personal Hotspot is activated, the view doesn't resize correctly and The bottom of the view goes below the screen. I found It doesn't happen to my all views, except in the views that I have this line of code:
navigationController?.navigationBar.isHidden = true
How can I handle this situation?

I found a solution. I always have this problem when I add a child view controller and the bottom of the child view goes below the screen. I found when the blue bar appears, the height of my parent view controller will be lower than the device screen's height. So I need to change the child view position in the situation.
if let parentHeight = parent?.view.frame.height, parentHeight < UIScreen.main.bounds.height {
view.frame.origin.y = UIScreen.main.bounds.height-childViewHeight-8
}

Related

Static/fixed Navigation Bar in iOS

I have a Navigation Controller and a Collection View under it inside my app. And there is a problem: I use large title inside my Navigation bar, so everything inside is not static. When I scroll the collection view cells, the title (I created it manually using UILabel() to move it as I want inside the navigation bar) and buttons move up and the navigation bar takes form of iOS 10 navigation bar, I mean its height. You can see it here:
The normal state of my Navigation Bar with "Prefer large titles" On:
It happens when I scroll my Collection View, everything goes up:
So the question is simple: how to make the force constant height for the navigation bar? I want it to become fixed even while scrolling. Are there any ideas? Is it possible?
And the second question, if the first is impossible: Another solution for my problem is to make the Navigation Bar with "Prefer large titles" Off bigger. I tried this code:
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
let height: CGFloat = 50 //whatever height you want to add to the existing height
let bounds = self.navigationController!.navigationBar.bounds
self.navigationController?.navigationBar.frame = CGRect(x: 0, y: 0, width: bounds.width, height: bounds.height + height)
}
but it worked only for large titles. So how can I make the navigation bar bigger?
Yes, you can make it fixed. It will not scroll if the very first view in the view hierarchy is not a CollectionView/TableView (ScrollView).
Using Storyboard/Xib:
Consider the following image where a tableView and button are added in scene. Here the navigation bar will collapse on scroll of tableView because tableView is the very first view in viewController's containerView hierarchy attached to the navigation bar.
Now to make the navigation bar fixed, if we just change the order of tableView and button as below, it will disable the collapsing of navigation bar.
To change the order of the view, you have to click, hold and move up/down.
If you have only CollectionView in this scene then you can add a placeholder view at the top and set its height to zero as below,
Programmatically:
If you are setting up view's programmatically then you just need to add a placeholder view at the top or add tableView/collection after adding other views.
e.g,
self.view.addSubview(UIView(frame: .zero))
self.view.addSubview(tableView) // or collectionView

Safe area insets change when hiding status bar iOS 11

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.

Animating UINavigationController height during controller transition

I am designing an iOS app in swift, and I am having some difficulty with animations during a controller transition. Specifically, I've implemented a UINavigationControllerDelegate, to listen for when a certain view is pushed. When this view is pushed, I want to hide a bar at the bottom of the screen. My code is working almost perfectly, however whenever I begin an animation on the height of the navigation controller, the current view (which is being removed) animates its height correctly, but the new controller which is being pushed already has the new height from the animation. To put some code to it, the following function is called from my UINavigationControllerDelegate's willShow viewController function:
func animatePlayerVisibility(_ visible: Bool) {
if visible == showingPlayer {
return
}
showingPlayer = visible
let height: CGFloat = visible ? 56.0 : 0.0
self.view.layoutIfNeeded()
UIView.animate(withDuration: 0.35) {
self.playerHeight.constant = height
self.viewBottom.constant = height
self.view.layoutIfNeeded()
}
}
'playerHeight' is an IBOutlet to a constraint on the height of the player container view. 'viewBottom' is also an IBOutlet constraint between the bottom of the top container view and the bottom of the screen. Essentially, as long as these two constraints are animated together, it should look nice.
To help visualize the graphical bug, I edited this line
self.viewBottom.constant = height
to
self.viewBottom.constant = height * 2.0
I have created an imgur album of the actual wrong behavior in action:
http://imgur.com/a/znAim
As you can see, the old view controller animates properly, when the new controller already has the new animated size.
Here is the layout of my storyboard:
Any help would be really appreciated. I've been trying to fix this for a while with no success.
EDIT: The view of the animation without the *2 applied.
https://imgur.com/a/2a5Sw
Have you thought about not using UINavigationController? Maybe it will be easier to use ChildViewControllers mechanism. Then with it you can use a powerful autolayouts and have more control over animation (in your case height)
More info about this here
I've created a nice little sample project you can find here!
There are a number of things that could be going wrong, and since I haven't looked over your project personally it's likely I organized things very differently in my sample, but hopefully you will understand it. I think the big thing is that I added a constraint in storyboard to the navigationController's container to the bottom of the root viewController. I don't adjust the height of this container at all when I animate.

TableView scroll underlying top bar

Since I updated my app to iOS 7 new GUI I have a problem that I can't solve.
My app consists in a scrollable TableView. Trouble is that TableView scrolls underlying top bar, means that table doesn't consider top bar and extends till the top and it's ugly to see.
I tried removing check on "Extend edges under Top Bars" but it's the same.
How can I solve this?
One solution is: set the table view's contentInset and scrollIndicatorInsets to have a top inset of 20. The table view will still underlap the status bar, but it will be completely visible when scrolled all the way.
If you don't like that solution, and you want a permanent empty area behind the status bar, you will have to change the way you pin/position the top of the table view, to allow for the status bar. How you do this depends on whether you are using auto layout. If you are, just pin to the top layout guide. If you are not, you will have to use the "delta" field provided in the nib editor.
If you are using a UITableViewController, however, you are not in charge of the top of the table view; it is a full-screen view and it is the view controller's main view. This is quite a troublesome situation, actually. I have resorted to two solutions:
Put the whole thing into a UINavigationController in order to get the nav bar to "run interference" for me.
Or, embed the table view controller in a custom parent view controller just so that I can position the top of the table view.
In UINavigationController I created an UIView, which goes under status bar but in front of embed controller (the Table), so table disappear behind this view and status bar is always on top.
var patch: UIView!
override func viewDidLoad() {
super.viewDidLoad()
patch = UIView(frame: CGRectMake(0, 0, view.bounds.width, 20))
patch.backgroundColor = UIColor.redColor()
self.view.addSubview(patch)
}
Then I make it disappear when screen goes in Landscape (in iOS9, status bar automatically disappear in Landscape) and make it reappear when screen goes in Portrait.
override func viewWillTransitionToSize(size: CGSize, withTransitionCoordinator coordinator: UIViewControllerTransitionCoordinator) {
if UIDevice.currentDevice().orientation.isLandscape.boolValue {
patch.hidden = true
} else {
patch.hidden = false
}
}

iOS 7 TableView in a ViewController and NavigationBar blurred effect

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

Resources