UISplitViewController - Expand & Collapse Master View in iPad Portrait - ios

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() }
}

Related

Centering between two anchors

I want to set a centerYAnchor between two anchors. Similar to this:
centeredLabel.centerYAnchor.constraint(equalTo: view.centerYAnchor),
However, I don't want it to be centered relative to the screen. I want it to be right in between two other anchors on the screen. Like if I have a toolbar at the top like this:
toolbar.topAnchor.constraint(equalTo: view.topAnchor),
Then I have a button at the bottom like this:
button.bottomAnchor.constraint(equalTo: guide.bottomAnchor, constant: -20)
is there a way I can center centeredLabel's y constraint to be right between the bottomanchor of toolbar and the top anchor of button?
is there a way I can center centeredLabel's y constraint to be right between the bottomanchor of toolbar and the top anchor of button?
Yes, there is. The simple way is to use a transparent spacer view whose top is anchored to the upper anchor and whose bottom is anchored to the lower anchor. Now you center-anchor your label to the center of the spacer view.
However, although that is simple, it is not the best way. The best way is to create, instead of a transparent spacer view, a custom UILayoutGuide. Unfortunately this can be done only in code, not in the storyboard (whereas the spacer view and label can be configured entirely in the storyboard). But it has the advantage that it doesn't burden the rendering tree with an additional view.
Here's your situation, more or less, using a button as the upper view and a button as the lower view. The label is centered vertically between them:
Here's the code that generated that situation. b1 and b2 are the buttons (and it doesn't matter how they are created and positioned):
let g = UILayoutGuide()
self.view.addLayoutGuide(g)
g.topAnchor.constraint(equalTo: b1.bottomAnchor).isActive = true
g.bottomAnchor.constraint(equalTo: b2.topAnchor).isActive = true
g.leadingAnchor.constraint(equalTo:b1.leadingAnchor).isActive = true
g.trailingAnchor.constraint(equalTo:b1.trailingAnchor).isActive = true
let lab = UILabel()
lab.text = "Label"
lab.translatesAutoresizingMaskIntoConstraints = false
self.view.addSubview(lab)
lab.leadingAnchor.constraint(equalTo:g.leadingAnchor).isActive = true
lab.centerYAnchor.constraint(equalTo:g.centerYAnchor).isActive = true
Although #matt's solution works, here is a simpler one that uses autolayout without the need to create a UILayoutGuide.
iOS 10 introduced a simple way of doing this through NSLayoutXAxisAnchor.anchorWithOffset(to:) and NSLayoutYAxisAnchor.anchorWithOffset(to:).
Here are convenience methods which wrap up this logic.
For X axis centering
extension NSLayoutXAxisAnchor {
func constraint(between anchor1: NSLayoutXAxisAnchor, and anchor2: NSLayoutXAxisAnchor) -> NSLayoutConstraint {
let anchor1Constraint = anchor1.anchorWithOffset(to: self)
let anchor2Constraint = anchorWithOffset(to: anchor2)
return anchor1Constraint.constraint(equalTo: anchor2Constraint)
}
}
For Y axis centering
extension NSLayoutYAxisAnchor {
func constraint(between anchor1: NSLayoutYAxisAnchor, and anchor2: NSLayoutYAxisAnchor) -> NSLayoutConstraint {
let anchor1Constraint = anchor1.anchorWithOffset(to: self)
let anchor2Constraint = anchorWithOffset(to: anchor2)
return anchor1Constraint.constraint(equalTo: anchor2Constraint)
}
}
To do what you need you can call:
centeredLabel.centerYAnchor.constraint(between: toolbar.bottomAnchor, and: button.topAnchor)

Menu bar and images/buttons don´t align properly

I´m having trouble with NSLayoutConstraints. I wanted to create a custom menu bar right underneath the header with some images(or buttons) for the navigation but I have no idea why the images do not properly align in the middle and I guess the menu bar does not align underneath the header.. I tried to fix it with changing some values but I´m clueless at this point.
private func setUpMenuBar() {
view.addSubview(menuBar)
let viewWidth = view.viewWidth
menuBar.translatesAutoresizingMaskIntoConstraints = false
menuBar.centerXAnchor.constraint(equalTo:
view.centerXAnchor).isActive = true
menuBar.centerYAnchor.constraint(equalTo:
view.topAnchor).isActive = true
menuBar.heightAnchor.constraint(equalToConstant: 100).isActive
= true
menuBar.widthAnchor.constraint(equalToConstant:
viewWidth).isActive = true
}
Your menuBar is currently the same width as your view so this is why your buttons are evenly spaced across the view. Try making your menuBar half of the view width so that your buttons are closer together (you can experiment here):
menuBar.widthAnchor.constraint(equalToConstant: viewWidth / 2).isActive = true
If you also want a bit of space between the bar, try adding a topConstraint (you can experiment with changing the 30):
menuBar.topAnchor.constraint(equalToConstant: 30).isActive = true

Adding and removing UIStackViews messing up my UIScrollView

Whenever I use the pickerview to switch views from Auto Rent to Schedule Rent it works perfectly. It is when I switch from Schedule Rent to Auto Rent that this black bar appears. I have attached the hierarchy of my content view. I thought it had to do with previous constraints added, so I remove a StackView whenever one view is chosen. For example, if Auto Rent is chosen, then I remove the StackView where the Schedule View is in:
//Holds Temp Stackviews
var stackViewHolder1: UIView?
var stackViewHolder2: UIView?
override func viewDidLoad() {
super.viewDidLoad()
stackViewHolder1 = stackViewMain.arrangedSubviews[0]
stackViewHolder2 = stackViewMain.arrangedSubviews[1]
}
if txtRentType.text == "Auto Rent" {
let tempView = stackViewHolder1
let tempView1 = stackViewHolder2
tempView!.isHidden = true
stackViewMain.removeArrangedSubview(tempView!)
if(tempView1!.isHidden == true){
tempView1!.isHidden = false
stackViewMain.addArrangedSubview(tempView1!)
}
else{
let tempView = stackViewHolder1
let tempView1 = stackViewHolder2
tempView1!.isHidden = true
stackViewMain.removeArrangedSubview(tempView1!)
if(tempView!.isHidden == true){
tempView!.isHidden = false
stackViewMain.addArrangedSubview(tempView!)
}
}
I have tried deleting one view and toggling only one view has being hidden and that removes the black bar issue. There is no constraint with the stackViews and Content View.
EDIT:
The screen highlighted is the scrollView. The one after is the contentView. UIWindow goes black in the back.
My Title Bar at the top ends up in the middle somehow.
You can try to modify your stack distribution property
stack.distribution = .equalCentering
After you won't need to use this:
.removeArrangedSubview()
.addArrangedSubview()
When you hide some view, the other view take all space of your stack, you don't need to update your constraints. You can try it on interface builder to see how it works.
are you pinning your scrollview and the content view to the bottom with constraints?
If the content view is a stack view you can pin it to the bottom as well with layout constraints and play with the content distribution.
You don't need to use remove/Add arranged subviews.
when hiding a view in a stackView its automatically removed.
so i think you can just hide or show the stackViewMain.subviews[0] o stackViewMain.subviews[1]
i'm with objc maybe i do a mistake but it would be something like this :
if txtRentType.text == "Auto Rent" {
stackViewMain.arrangedSubviews[0].isHidden = true;
stackViewMain.arrangedSubviews[1].isHidden = false;
}else{
stackViewMain.arrangedSubviews[1].isHidden = true;
stackViewMain.arrangedSubviews[0].isHidden = false;
}

activating constraints programmatically at runtime

I want to change the positions of my view programmatically at runtime using constraints
I have following arrangement of views
|
viewA
|
viewB
|
viewC
say i have viewA with topAnchor constraints set to top of margin of parent view and viewB with topAnchorset to viewA and so on for viewC
And i want to change the position of these views on certain action at runtime
|
viewA
|
viewC
|
viewB
i have store two different constraints for viewB one with top of viewA and another with top of viewC
viewBTopConstraints = viewB.topAnchor.constraint(equalTo: viewA.bottomAnchor, constant: TOP_SPACE)
newViewBTopConstraints = viewB.topAnchor.constraint(equalTo: viewC.bottomAnchor, constant: TOP_SPACE)
In my toggle action method i have done something like this
viewBTopConstraints?.isActive = false
newViewBTopConstraints?.isActive = true
This works on first run of action however it fails on second time, on further debugging in view debugger i found out that it creates duplicate constraints rather that changing the original one.
Your requirements sounds similar to an app where I have some constraints that change based on landscape or portrait. There I set up three groups of constraints:
Those that always are isActive = true
Those that are isActive = true in portrait
Those that are isActive = true in landscape
The trick is to put the latter two into arrays, and activate/deactive the proper array at the right time. Here's a snippet of what I mean:
var p = [NSLayoutConstraint]()
var l = [NSLayoutConstraint]()
////// portrait layout....
// pin info button above imageLayout, right justified
p.append(info.topAnchor.constraint(equalTo: margins.topAnchor, constant: 20.0))
p.append(info.trailingAnchor.constraint(equalTo: margins.trailingAnchor))
////// landscape layout....
// pin info button above buttons, right justified
l.append(info.topAnchor.constraint(equalTo: margins.topAnchor, constant: 20.0))
l.append(info.trailingAnchor.constraint(equalTo: margins.trailingAnchor))
Note that I'm not setting any isActive = true. I'm just appending things to the two arrays as needed. Now, in viewWillLayoutSubviews() - depending on your needs, viewDidLayoutSubviews() may be the better override - you *activate/deactivate the correct array:
NSLayoutConstraint.deactivate(l)
NSLayoutConstraint.deactivate(p)
if self.bounds.width > self.bounds.height {
NSLayoutConstraint.activate(l)
} else {
NSLayoutConstraint.activate(p)
}
I've found that working with individual constraints, setting isActive, greatly increases the risks of conflicts.

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