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

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.

Related

UISplitViewController - Expand & Collapse Master View in iPad Portrait

My universal app displays both master and detail views in iPad with preferredDisplayMode = .allVisible. I need to expand the master view into full screen and hide the detail view on a button click. I know there is functionality to expand detail into full screen hiding master. But couldn't find how to expand and collapse master view.
I tried in expand function as below.
self.splitViewController.preferredPrimaryColumnWidthFraction = 1.0
self.splitViewController.maximumPrimaryColumnWidth = self.splitViewController.view.bounds.size.width as! CGFloat
And collapse function as below.
self.splitViewController.preferredDisplayMode = .allVisible
self.splitViewController.preferredPrimaryColumnWidthFraction = 0.6
self.splitViewController.maximumPrimaryColumnWidth = self.splitViewController.view.bounds.size.width as! CGFloat
But they don't work. Any ideas on how to achieve this?
My setup uses a navigation bar, but the show/hide functionality would probably work without it. What I wanted to achieve is to have the user decide whether to show the "primary" view and have it shown no matter the orientation - something a UISplitViewController doesn't natively do.
I achieved this through activating/deactivating two arrays of constraints, pinning the secondary" view's leading anchor to the "primary's" trailing anchor. From there, all the constraint changes are with the "primary" view.
(I'm using quotation marks because these view's actually have much more logic that a UIView should have, so they are each UIViewControllers with one being a child of the other, but the constraints are view-related.)
Let's start with the static constraints:
primary.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor).isActive = true
primary.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor).isActive = true
primary.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor).isActive = true
secondary.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor).isActive = true
secondary.leadingAnchor.constraint(equalTo: toolBar.trailingAnchor).isActive = true
secondaary.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor).isActive = true
secondary.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor).isActive = true
Next, define/populate the two arrays that will show/hide the primary:
var isShowingPrimary = false
var showPrimary = [NSLayoutConstraint]()
var hidePrimary = [NSLayoutConstraint]()
showPrimary.append(primary.widthAnchor.constraint(equalToConstant: 300))
hidePrimary.append(primary.widthAnchor.constraint(equalToConstant: 0))
NSLayoutConstraint.activate(hidePrimary)
Notice that isActive is true for the static constraints, and I activated the hidePrimary constraints only.
Now all you need to do is wire up a UIBarButtonItem or UIButton to execute a toggle function that will show/hide the primary view, along with animating it:
func togglePrimary() {
if isShowingPrimary {
// hide primary view
NSLayoutConstraint.deactivate(showPrimary)
NSLayoutConstraint.activate(hidePrimary)
} else {
// show primary view
NSLayoutConstraint.deactivate(hidePrimary)
NSLayoutConstraint.activate(showPrimary)
}
// toggle flag and animate changes
isShowingPrimary.toggle()
UIView.animate(withDuration: 0.3) { self.view.layoutIfNeeded() }
}

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.

iPhone X - space above safe area issue

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:

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.

TopAnchor of viewController changin in iMessage Extension between presentation modes

I'm making a survey iMessage app (yeah I know) and having a problem moving between the presentation modes. The series of screenshots below shows that when the app launches, all is fine in the compact mode. When expanded everything is still correct but then when I go back to compact the content is shifted down by what looks like the same height as the large messages nav bar (86 I believe)
I've tried setting the top constraint to be -86 when switching back to the compact view, however, this either does nothing or sends it back to where is should be and then subtracts 86 so it dissapears too high up. I've based this project on the IceCream example project from app so not sure where this issue is coming from (probably autolayout but everything is pinned to the layout guides)
Here's the code that adds the view controller:
func loadTheViewController(controller: UIViewController) {
// Remove any existing child controllers.
for child in childViewControllers {
child.willMove(toParentViewController: nil)
child.view.removeFromSuperview()
child.removeFromParentViewController()
}
// Embed the new controller.
addChildViewController(controller)
controller.view.frame = view.bounds
controller.view.translatesAutoresizingMaskIntoConstraints = true
view.addSubview(controller.view)
controller.view.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
controller.view.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true
controller.view.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
controller.view.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
controller.didMove(toParentViewController: self)
}
I've been working on this for what feels forever so any suggestions welcome.
You are setting up constraints on view but you have set translatesAutoresizingMaskIntoConstraints to true. The autoresizing mask constraints will likely conflict with the constraints you are adding, cause unexpected results. You should change to:
controller.view.translatesAutoresizingMaskIntoConstraints = false
Also rather than pinning to view.topAnchor, you should pin to the topLayoutGuide, which will take the top navigation bar into account.
controller.view.topAnchor.constraint(equalTo: topLayoutGuide.bottomAnchor).isActive = true
Similarly,
controller.view.bottomAnchor.constraint(equalTo: bottomLayoutGuide.topAnchor).isActive = true

Resources