iPhone X - space above safe area issue - ios

This question does already poses the exact same issue that I'm having:
iPhone X - How are we supposed to handle the space above table section headers? It's showing through the content
I've got a UICollectionView with a header at the top which holds a few buttons.
When the UICollectionView scrolls up, so it goes under the header and then shows through in the space above the header.
Unfortunately I'm not quite following the answer given by Matt, how does one:
"It's just a matter of giving the table view controller a parent view controller with a black background. You can do that in code, or you can configure it entirely without code in the storyboard."
I would of contacted Matt directly but there isn't an option for this.
EDIT:
Screenshot of my storyboard, would greatly appreciate advice on what I need to do (please talk as if you would to a dog or small child'(!)

Basically, Apple really doesn't support a table view controller or collection view controller that isn't a child view controller of some surrounding view controller. It could be a custom parent view controller (as in #AamirR's answer), but the most common approach is to put it inside a navigation interface (a UINavigationController). Now the navigation bar occupies the top of the screen. It automatically grows up behind the "notch" in the iPhone X. It covers the cells as they go above the header.
If you look at Apple's apps, they all work like that. For example, look at the Moments view in the Photos app, or the Month view in the Calendar app. They all have a navigation bar at the top.
So, embed your Subject View Controller in a Navigation Controller.
iPhone 5:
iPhone X:

You have to create a parent view controller for UICollectionViewController and add SubjectViewController as a child of that parent, like so:
class SubjectParentViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = .gray
// Add collection view as a child view controller
let collectionViewController = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "SubjectVC")
collectionViewController.willMove(toParentViewController: self)
self.addChildViewController(collectionViewController)
let collectionView: UIView = collectionViewController.view
collectionView.translatesAutoresizingMaskIntoConstraints = false
self.view.addSubview(collectionView)
collectionViewController.didMove(toParentViewController: self)
// add auto-layout constraints to layout guides
var guide: UILayoutGuide!
guide = self.view.layoutMarginsGuide
collectionView.leadingAnchor.constraint(equalTo: guide.leadingAnchor).isActive = true
collectionView.trailingAnchor.constraint(equalTo: guide.trailingAnchor).isActive = true
guide = self.view.safeAreaLayoutGuide
collectionView.topAnchor.constraintEqualToSystemSpacingBelow(guide.topAnchor, multiplier: 1.0).isActive = true
collectionView.bottomAnchor.constraintEqualToSystemSpacingBelow(guide.bottomAnchor, multiplier: 1.0).isActive = true
}
}
Edit your storyboard with these 2 steps:
Create a view controller, and set its class to be above custom class SubjectParentViewController
Move all segues from SubjectViewController to SubjectParentViewController
Finally, create a File SubjectParentViewController.swift and paste the class
This sets UITableViewController/UICollectionViewController's view aligned to layout margins and safe-area guides:

Related

Gap between status bar and toplayout guide bottom anchor even though `edgesForExtendedLayout` set to `top`

I have a simple view controller which is being rendered modally simply by using
viewController.present(myVC, animated: true, completion: nil)
myVC view controller does not have any embedded navigation controller and in viewDidLoad of myVC I am setting view which is supposed to behave like navigation bar view (I can't use navigation bar / navigation controller unfortunately)
Here is how I add the view programmatically
self.view.addSubview(topView)
topView.translatesAutoresizingMaskIntoConstraints = false
topView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor).isActive = true
topView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor).isActive = true
topView.topAnchor.constraint(equalTo: self.topLayoutGuide.bottomAnchor).isActive = true
topView.heightAnchor.constraint(equalToConstant: 70).isActive = true
I have specified extended edges as top in my viewDidLoad of myVC
self.edgesForExtendedLayout = .top
And the UI looks like
There is a gap between status bar and view added highlighted by yellow border which I am not sure how to fix :(
Please help
You get the gap because you are adding your topView's top constraint to the view's topLayoutGuide.bottom (which sits a bit below the notch). So that is intended behavior.
The cleanest way to get rid of that gap is to embed you view controller in a UINavigationController and use a real navigation bar.
But if you cannot do that you have to get rid of the gap yourself.
I cannot think of an elegant way to get rid of this gap but you could add a negative constant to the constraint that is as high as the gap:
topView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: -14).isActive = true
But you would have to make sure that this is only done on devices that have a safeAreaLayoutGuide.topAnchor > 0. (iPhoneX etc.). On all other devices the constant has to be 0.
As I said this is not a very stable or elegant solution but it would work.
BTW If possible you should change self.topLayoutGuide.bottomAnchor (deprecated) to view.safeAreaLayoutGuide.topAnchor.

Replacing edgesForExtendedLayout with safeAreaInsets or LayoutGuide **WITHOUT** interface builder

Preface: I'm doing everything programmatically. 0 interface builder.
Previously, when pushing a VC onto a navVC stack, the pushed VC would have the upper edge occluded by the navbar. I want it so the top edge of the pushed VC is under the nav bar. I found that "edgesForExtendedLayout" did what I wanted to. Except Apple says:
"Instead of this property, use the safe area of your view to determine which parts of your interface are occluded by other content. For more information, see the safeAreaLayoutGuide and safeAreaInsets properties of UIView."
I have no idea how to translate the following code:
if let nextVC = getNextVC() {
nextVC.edgesForExtendedLayout = .init(rawValue: 0)
pushViewController(nextVC, animated: true)
}
To use safeAreaLayoutGuide or safeAreaInsets. I can't use constraints because the pushed view is not in the view hierarchy yet?
I've attached some images to give an idea of what I'm dealing with.
Green = border of UINavigation controller
Yellow = border of 'nextVC'
Red = border of table inside 'nextVC'
With edgesForExtendedLayout set to "none" - right, but changing value Apple says not to change:
With edgesForExtendedLayout set to "all" - wrong, but default value Apple says to keep:

pageviewcontroller inside navigation controller causing layout problems

I have a UIViewController (using Swift)
It is just a simple list of Items. It is implemented as a UIViewController to which I have added a UITableView. This 'Items' view is invoked via this code in my slide menu implementation, and the initial view of the slide menu is embeded in a NavigationController - so all the views in the slide menu take on the navigation controller. Items is one of those slide menu views invoked like this
self.openViewControllerBasedOnIdentifier("Items")
This works fine and I get....
I then have an add button in the top right that you can push to add new Items. The add button brings up a PageViewController. I use page view because there are multiple pages of details associated with the Item being added. This also works so far in that I get the pageviewcontroller launched and I can flip between pages.
The problem is that the placement of elements on the page shifts under some circumstances (it is not consistent).
When I initially show the 1st page of the PageViewController I see the layout that I designed (on the left below). If I simply tab out of the Text field OR if I swipe to the 2nd page and then swipe back to the 1st page then I see the right image. The whole view jumps up!
I tried flipping all the different settings in the storyboard regarding . I even moved the majority of the drawing in to the code and out of storyboard in order to manually set constraints. These are the settings I played with. tried lots of combinations but they are mostly all off now.
Here is Before and After. How do I stop the view from jumping up and down. Also, interesting is that if i swipe from page-3 to page-2 then page-2 is shifted up. If I keep going back to page-1 and then swipe back to page-2 then page-2 is back down to correct position. So deepening on which way I enter page-2 it is different layout. From 1 to 2, page 2 is correct. From 3 to 2, page 2 is shifted up.
I can not get the layout to stay in one spot.
The other option, is to just shut off NavigationController when in the page view controller and deal without having it.
So...
1) how can I stop the jumping - best case
2) how can I shut off navigation controller when pageviewcontroller is up - second option
The pages of my pageviewcontroller are invoked via this code
instantiateViewController(withIdentifier: "itemDetail\(num)ViewController")
Where 'num' is 1,2, or 3. Using an indexed array and viewControllerBefore along with viewControllerAfter methods. The paging itself works fine.
Thanks
This does not answer the question of how to make storyboard work. That remains a mystery. HOWEVER, I was able to fix the strange behavior of shifting layout by doing the following.
I removed ALL of the drawing from Storyboard and put everything in the code. For example, I have defined a container view ...
// The container
let containerView: UIView = {
let view = UIView()
view.backgroundColor = UIColor.white
view.translatesAutoresizingMaskIntoConstraints = false
view.layer.masksToBounds = true
return view
}()
Then later I place the container relative to the UIViewController's view
view.addSubview(containerView)
containerView.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 0).isActive = true
containerView.topAnchor.constraint(equalTo: view.topAnchor, constant: 155).isActive = true
containerView.widthAnchor.constraint(equalTo: view.widthAnchor, constant: 0).isActive = true
containerView.heightAnchor.constraint(equalTo: view.heightAnchor, constant: -155).isActive = true
Similarly, I have defined all of the other controls in the code and then added them to the containerView subView and made then all relative to the containerView.
This keeps everything in place as I swipe between pages of the PageViewController
I had the same problem, and found the solution. You just need to disable automaticallyAdjustsScrollViewInsets in your viewDidLoad. Here's my code:
override func viewDidLoad()
{
super.viewDidLoad()
self.dataSource = self
self.automaticallyAdjustsScrollViewInsets = false
if let detailsVC = viewControllersList.first as? DetailsViewController
{
detailsVC.delegate = self
self.setViewControllers([detailsVC], direction: .forward, animated: true, completion: nil)
}
}
Credits to this answer.

How to keep navigation bar from disappearing with MSMessagesViewController -> UIContainerView -> UINavigationController -> UITableViewController?

I am trying to put a navigation controller with a table view controller within an iMessage app (iOS 10). This seems to work when I put the UINavigationController within an UIContainerView within the MSMessagesViewController.
However, this breaks when in expanded view. The UINavigationBar that the controller has disappears.
How can I remedy this, or am I taking the wrong approach?
Let me start with the assumption that you used view.addSubview to add your UITableViewController to the MSMessagesAppViewController
In order to show the navigation bar correctly. Make sure you have set all your constraints correctly. Here's the example I have, and hopefully this would work for you:
// Embed the new controller. Recommended way of presenting VC shown in WWDC (icecream example). Ugly but does the work
addChildViewController(controller)
view.addSubview(controller.view)
let viewRect = view.bounds
controller.view.frame = viewRect
controller.view.translatesAutoresizingMaskIntoConstraints = false
controller.view.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
controller.view.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true
controller.view.topAnchor.constraint(equalTo: topLayoutGuide.bottomAnchor).isActive = true
if presentationStyle == .compact {
controller.view.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
} else {
controller.view.bottomAnchor.constraint(equalTo: bottomLayoutGuide.topAnchor).isActive = true
}
controller.didMove(toParentViewController: self)
Here's the link to the post on the Apple Developer Forum which solved my problem: https://forums.developer.apple.com/thread/52049
In the screens you show there isn't any problem with the navigation bar!!
So in my case, I was using Storyboards, so I will add in the Storyboard solution to this.
Similar to DLee's answer, the top constraint needs to be set to the "Top Layout Guide" not "Top." Here's what it looks like in a Storyboard:
In my case, I used a Container View to hold everything, so with this having the top layout guide set as the top constraint, it made everything go in the right place.
In my post, I originally used the "Top" which was what caused parts of the iMessage app (specifically the navigation bar) to disappear.

UISplitViewController Master Content Width

I have a UISplitViewController in my application (MvvmCross / Xamarin iOS) and for some reason I cannot get the content to respect the dimensions of the available view areas.
In the situation shown in the screenshot the master view is hosting a UIViewController with a TableView inside. All the layouts are done with constraints and work fine on their own when running in an iPhone emulator.
As soon as I switching to running on an iPad some custom code I have in my presenter shows this same view in the master panel of a UISplitViewController but in this situation the constraints seem to be ignored and I end up with a view that looks like this:
As you can see the right hand side of the table cell is now way off the viewable area of the master panel of the UISplitViewController.
Both the UITableView and the UITableCell both use View.Frame as their initial size (I've tried View.Bounds as well).
How can I get the cells and / or table to respect the bounds of the UISplitViewController available space?
Thanks to Cheesebarons question I found my solution (cause).
I have a set of methods in a helper class that I use to generate my "default" UIViews.
One of these methods creates my default UITableView:
public static UITableView CreateDefaultTableView(CGRect rect, UITableViewStyle style)
{
var tv = new UITableView(rect, style)
{
AutoresizingMask = UIViewAutoresizing.FlexibleHeight,
SeparatorStyle = UITableViewCellSeparatorStyle.SingleLine,
SeparatorColor = IosConstants.DefaultTableSeparatorColor,
BackgroundColor = IosConstants.DefaultViewBackgroundColor
};
return tv;
}
Changing:
AutoresizingMask = UIViewAutoresizing.FlexibleHeight,
To:
AutoresizingMask = UIViewAutoresizing.All,
Schoolboy error!

Resources