Extend touch area on iPhone X in landscape mode - uiview

If a UIViewController is launched in landscape mode, even though the home indicator is not on the right side, still the rightmost 44 points on the screen do not respond to touches. I am wondering what is the purpose of disabling those pixels for touches and is there a way to extend the touch area on the right?

safearealayoutguide
When the view is visible onscreen, this guide reflects the portion of
the view that is not covered by navigation bars, tab bars, toolbars,
and other ancestor views. (In tvOS, the safe area reflects the area
not covered the screen's bezel.) If the view is not currently
installed in a view hierarchy, or is not yet visible onscreen, the
layout guide edges are equal to the edges of the view.
As you try to add objects beyond safe area your content get chance clip by Device Bazels.
Example:
When you add button outside safe area as
let buttonOutsideSafeArea = UIButton(frame: CGRect(x: 0, y: 100, width: 44, height: 40))
buttonOutsideSafeArea.backgroundColor = .red
self.view.addSubview(buttonOutsideSafeArea)
When you rotate right on device.
But it will get clipped(by Top Notch) when you rotate left
Hence try to avoid adding objects beyond safe Area

Related

iOS 12 wrong navigation bar height

I'm working on an iPad app in landscape orientation. There is a navigation bar at the top without a status bar, and I want to line up a UITableView such that the top of the table is at the same y coordinate as the bottom of the navigation bar. Naturally, I do the following in viewDidLoad:
MyTableView* table = [[MyTableView alloc] init];
table.frame = CGRectMake(someX, navBar.frame.size.height, someWidth, someHeight);
[self.view addSubview:table];
At this point, navBar.frame.size.height returns 44. This is strange, because when I run the app, I see that my table overlaps the bottom of the navigation bar by a few points. I do some investigation, and find out that the navigation bar is actually drawing with a height of 50 points.
I try to think what could be wrong, and realize that perhaps I'm doing this too early in the view controller's lifecycle and the views haven't yet been laid out, so I override viewWillAppear, viewDidAppear, and viewDidLayoutSubviews, all of which report that the navigation bar's height is 44, even when it is clearly drawing with a height of 50.
I thought perhaps there's something I simply don't know, so I setup a timer to run a block of code that would repeatedly print out the height of the navigation bar every second after viewDidLoad was called, which allows for any view controller behind-the-scenes setup to finish. Still, every time the timer triggers the print, the height is only 44.
I took a look at interface builder, and even there, in the dimensions property window (second tab from right), it says the navigation bar has a height of 44. Yet on the screen in the view controller in interface builder, it is clearly drawing with a height of 50.
My question is: why does the reported height not match the height the navigation bar draws at? I need the correct height to place my table in the right location.
At this point I'm at a loss for what could possibly be the issue, save for the API simply being completely broken. The only solution I can think of is to just position the navigation bar at -3 and hardcode in a y value for my table view of 44, resulting in 3 points on the top and bottom of the navigation bar being covered up, which is super janky and I want to avoid it if at all possible.
Now, I understand that autolayout is a thing and constraints exist, but I like to manually compute the frames of my views in code and never use constraints, and that is the realm that this question is in. Please don't give answers like "you're supposed to use constraints".
Although I doubt it matters, I'm running iOS 12.0.1 on an iPad Air 2.
My debugging:
Below is an image of the top center portion of my screen. Each of the rectangles is simply a UIView with a background color set, a y coordinate of 0, a width of 50 points, and are spaced 50 points apart from each other (each starts where the previous one ends). The heights of each rectangle from left to right are: 44, 50, and 51. You can tell the navigation bar is drawing at a height of 50 points since the right rectangle is just barely taller than the navigation bar.
EDIT:
I should probably mention that this navigation bar is added to the view controller via being dragged and dropped into a view controller in the storyboard. I still do some frame shenanigans in viewDidLoad, but it's not being dynamically added to the view there.
Another interesting thing of note: In interface builder, you know the dashed blue lines that help you line things up? Apparently, those align with the 44 point height instead of the 50 point height:
After too much time spent with no progress, I decided to finally bite the bullet and transition to constraints. After I spent far too much time trying to get them to do what I wanted (and failing), it turns out that manually laying out my views wasn't the issue. During the conversion process, I deleted my navigation bar and re-added it to the view controller, which caused everything to start working perfectly. The dashed blue alignment guides lined up with the 50 point height and the height field now always returns 50.
My guess is that the logic in Xcode to upgrade storyboards from pre iOS 12 navigation bars (44 points high) to the new iOS 12 navigation bars (50 points high) is broken, which put my storyboard into a bad state where it thought the height was 44 in some places and 50 in others. Deleting and re-adding it looks to have cleared any record of 44-ness so everything is now properly 50 points high.

Exclude navigation bar from iOS "safe area"

In my iOS app, I have views rendered beneath the navigation bar. This is because the navigation bar is hidden until the user taps the screen.
The screenshots below illustrate my problem.
The "X" button is rendered beneath the iPhone X's notch and can hardly be seen. This is when I constrain the button's topAnchor to its superview!.topAnchor.
Note that this works as intended for all devices except the iPhone X.
The "X" button in this screenshot is anchored to its superview!.safeAreaLayoutGuide.topAnchor and renders below the navigation bar. This makes sense given Apple's documentation on safeAreaLayoutGuide:
"this guide reflects the portion of the view that is not covered by navigation bars, tab bars, toolbars, and other ancestor views."
However, I want the "X" button to render below the iPhone X's notch and underneath the navigation bar. Here's what it looks like when the navigation bar is hidden:
The "X" button should render right below the notch.
So my question is:
Is there a way to exclude the navigation bar from a view's safeAreaLayoutGuide? And if not, what are my options, other than manually offsetting the button on iPhone X's.
Note that I'm doing everything programmatically. I no longer use Storyboards.
Thanks!
You can change the view controllers safe area insets by adding additional insets.
Create a UIEdgeInsetsMake() object where you subtract the navigation bar height from the top inset. Then add it to the View Controllers additionalSafeAreaInsets.
Declaration: var additionalSafeAreaInsets: UIEdgeInsets { get set }
This is not an answer, but a workaround:
Keep a reference to the "X" button's top constraint.
In layoutSubviews(), update the constant of the top constraint depending on the "X"'s superview and window safeAreaInsets.
override func layoutSubviews() {
// To support devices with a "safe area". If the receiver has a top safe area, position
// the close button beneath the _window's_ safe area.
// Note that using the receiver's safe area does not work in this case because it includes
// the navigation bar. We want to render the close button beneath the navigation bar.
let windowSafeAreaInsets = UIApplication.shared.keyWindow!.safeAreaInsets
// Only use the safe area if the receiver _and_ window have a top safe area. This handles
// the case of non-safe area devices using a hidden navigation bar.
closeButtonTopConstraint.constant = safeAreaInsets.top > 0 && windowSafeAreaInsets != .zero
? windowSafeAreaInsets.top : 16
// Possibly do something similar for landscape and the "X" button's trailing constraint.
}

positioning UIBarButtonItem vertically (for iPhone X)

I'm trying to compile my app to work correctly with the iPhone X, using all the available screen space and accommodating the new home indicator at the bottom of the screen. My app has a bottom toolbar, and I notice that Apple's apps extend the height of the bottom toolbar to give extra room for the home indicator. I give my toolbar extra height, but the buttons themselves want to position toward the bottom instead of the top. Is it possible to force them to align toward the top of the toolbar instead of toward the bottom?
Thanks.
In the app for which I asked the question, I am positioning views on the screen using coordinates. (In a constraint based app, one would need to pin the bottom constraint of a bottom toolbar to the safe area rather than the superview; if you are using the built-in bottom toolbar belonging to the navigation controller, the position of the toolbar and its content will take care of itself). Surprisingly, the solution turned out to be simply lifting the bottom toolbar up using its y coordinate, no more than 34 pts, and the area below the toolbar and near the home indicator will simply assume the color of the bottom toolbar, making it appear as though that area is part of a taller-than-normal bottom toolbar. The buttons on the toolbar will no longer appear squished.

Instantiating view frame so that it fits with navigation bar / tab bar?

I have an app where sometimes views are in navigation controllers, sometimes not, and sometimes in a tab bar and navigation controller.
I create my views programatically using code such as this:
let contact = ContactFormViewController(contactFormView: ContactFormView.init(frame: UIScreen.main.bounds))
however this is causing frames to always be as large as the screen, meaning when there is a navigation bar or tab bar the view is pushed off the visible screen and doesnt fit.
Is there a method to adapt the view height to always show it all on screen regardless of parent elements like the nav bar or tab bar? or would it be a case of always creating views with manual subtractions like this, which seems poor as it needs to assume hardcoded elements:
let attendees = AttendeesViewController(attendeesView: AttendeesView.init(frame: CGRect(x: 0, y: 0, width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height-55)))
thanks for any guidance as its a major issue in my app
The solution was to set the frame to CGRect.zero and then in the init method of the controller, apply layout constraints to the view class to map it to self.view
I think it's wrong to take UIScreen as the reference to lay out your views. I think you should use the content view (the view at the top of your hierarchy) as the reference.

MKMapView Content Insets with Navigation Bar

I'm experiencing a strange issue with MKMapView placed in a view with a navigation bar. Given a UIScrollView in the same situation, whose width, height, and center are constrained to it's superview (the view controller's view object) the insets will be adjusted if the controller's automaticallyAdjustsScrollViewInsets property is set to YES, and not if set to NO. That's great. I can not find this documented anywhere, but the map view seems to do the same thing, but it's not possible to disable it. Setting automaticallyAdjustsScrollViewInsets has no effect.
In brief(ish):
In the image below, the small back square is centered within the view, regardless of navigation bar. The map view is also constrained to be exactly the size of the view (it extends behind the nav bar). The location of the map view was set to be the current location (also indicated by the blue circle) so I would expect them to match, but they are off by half the combined height of status and navigation bars.
Is there an explanation for this behavior, and a way to get it to work as expected?
This is very strange because an MKMapView is not a subclass of a UIScrollView but it is behaving like one in this case. That's not completely unreasonable, but I would expect the auto adjusting insets to work consistently on a map view and a scroll view.
Workaround
Something awful like this would 'solve the problem':
MKCoordinateRegion region = MKCoordinateRegionMake(self.coordinate, MKCoordinateSpanMake(0.008, 0.008));
MKMapRect mapRect = MKMapRectForCoordinateRegion(region);
[self.mapView setVisibleMapRect:mapRect edgePadding:UIEdgeInsetsMake(0, 0, 64, 0) animated:YES];
Either adjusting the bottom inset as well, or adding a negative inset on the top gets things lined up, but it would break if this behavior of the map view changes, or if the view controller is presented without a navigation bar, or if there is a stiff breeze. It also erodes my confidence in pulling correct coordinates back out of the map view.
MKMapView does indeed sit under the navigation bar, but automatically uses a housing view controller's topLayoutGuide to shift annotations and the compass view, etc. down from under it.
You should make sure that your own views take this into account in their autolayout code.
If you take a look at this, you can see that the mapview is very likely not sitting under the navigation bar, as you think. It is sitting between the bottom of the nav bar and the bottom of the superview.
On another note, the navigation bar is translucent by default, so if the mapview was really sitting under the nav bar you would be able to see through it, assuming you didn't disable the default nav bar behavior.

Resources