Adding Vertical Spacing between two views programmatically in swift - ios

I have these views, both are the same, i want to add them programmatically so i want to add constraints programmatically, i've managed to do same using storyboard but i want to use code for this.
i want to add margins to these views so that first one is at the top, next one is below the first one and so,
i've wrote code like this:
self.view.addConstraint(
NSLayoutConstraint(
item: secondView,
attribute: .Top,
relatedBy: .Equal,
toItem: firstView,
attribute: .Top,
multiplier: 1.0,
constant: 0
))
first view has the constraint in which toItem is current view controller and it works, but the second view does not work this way, it just draws it on top of the first view, i want it to be below it, only way i can do this is in constant: 0 enter the height of the view, which i don't like
any suggestions?

The code you supplied is 99% right but
self.view.addConstraint(
NSLayoutConstraint(
item: secondView,
attribute: .Top,
relatedBy: .Equal,
toItem: firstView,
attribute: .Top,
multiplier: 1.0,
constant: 0
))
Your attaching the top of secondView to the top of firstView so they would be on top instead you want the top of secondView to the bottom of firstView.
self.view.addConstraint(
NSLayoutConstraint(
item: secondView,
attribute: .Top,
relatedBy: .Equal,
toItem: firstView,
attribute: .Bottom, <----------
multiplier: 1.0,
constant: 0
))
The constant is the distance.

Related

NSLayoutContraints applied inconsistently (apparently) on UITableViewController

I'm trying to add some kind of overlay menu on a UITableViewController. My first goal is to get a background view to be displayed at the bottom of the screen on a very barren controller. I'm doing this programmatically using the following code in the callback for a bar button, i.e. called after the original layout has occurred:
print("self.tableView.frame=\(self.tableView.frame)")
let settingsView = UIView()
settingsView.translatesAutoresizingMaskIntoConstraints = false
settingsView.backgroundColor = UIColor.cyan.withAlphaComponent(0.5)
self.view.addSubview(settingsView)
self.view.addConstraints([
NSLayoutConstraint(item: settingsView, attribute: .bottom, relatedBy: .equal, toItem: self.tableView, attribute: .bottom, multiplier: 1, constant: 50),
NSLayoutConstraint(item: settingsView, attribute: .leading, relatedBy: .equal, toItem: self.tableView, attribute: .leading, multiplier: 1, constant: 0),
NSLayoutConstraint(item: settingsView, attribute: .width, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: self.view.frame.width),
//NSLayoutConstraint(item: settingsView, attribute: .trailing, relatedBy: .equal, toItem: self.tableView, attribute: .trailing, multiplier: 1, constant: 0),
NSLayoutConstraint(item: settingsView, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: 100)
])
self.view.setNeedsLayout()
But the result doesn't look like I expected: instead of being at the bottom of the screen, the bottom of my view is actually aligned with the top margin (hence the 50 value to see it poke through, you can see the cyan color through the navigation bar on the picture attached). Why is that? If I change the first constraint to align tops instead of bottoms, it works fine (but at the top of course).
Also, I'm surprised that I have to replace my fourth constraint (the one that is commented out, supposedly to align the right edge of the view with the right edge of the screen) with the third one (which sets the width, but somehow seems less clean). If I use the fourth one, then the view disappears completely (I'm guessing it gets a width of 0). What's the reason for that?
The print command at the beginning ensures that the tableView has the right size:
self.tableView.frame=(0.0, 0.0, 375.0, 667.0)
It all seems trivial enough so there's definitely something I'm not seeing here...
So the issue here really seems to be the UITableView(Controller), since these constraints work fine with a normal UIView(Controller). The UINavigationController around it doesn't play a role here. For some reason, it seems like the .bottom and .trailing anchors can't be used on a table view — if someone knows why or can explain to me how to do it, please do!
Anyway, I've gone around the problem by using the actual measurements of the view, which are correct. So the first constraint becomes:
NSLayoutConstraint(item: settingsView,
attribute: .bottom,
relatedBy: .equal,
toItem: self.view,
attribute: .top,
multiplier: 1,
constant: self.view.frame.height + self.view.bounds.minY)
This brings the view nicely to the bottom of the screen. The self.view.bounds.minY in the constant accounts for the presence of a navigation bar at the top of the screen. Still feels a bit like a "hack" to me, especially because I don't understand why the other version doesn't work.

Tab Bar Height and button size - Fixed?

i have a custom tab bar controller, that i want to add 2 buttons to. 1 enlarged centre button and 1 button on the left to create a side out burger menu that is launched from the tab bar instead instead of the top navigation bar.
i was going to try and get the tab bar height programatically so i can set the button heights ect from that. so i read up and tried the following code which does not work.
self.tabBarController?.tabBar.frame.size.height
i read somewhere else that the tabbar is always a fixed 49 pixels regardless of device?
if that is the case is it safe to use something like:
menuButtonFrame.origin.y = self.view.bounds.height - (CGFloat(49) - menuButtonFrame.size.height) / 2
to set the position of my button? (the black box) its not positions right just yet
also wondering what the default value for the tabbar button is?
Create a UIView Like this and set the height of the center item as your wish.
And then in TabbarView Controller. add this view to the tabbar View Like this.
UITabBar.appearance().shadowImage = UIImage()
customNavBar = NSBundle.mainBundle().loadNibNamed("CustomTabBarView", owner: self, options: nil)[0] as! UIView
bdNavBar.translatesAutoresizingMaskIntoConstraints = false
self.tabBar.addSubview(customNavBar)
And then add Constraints to the custom Tabbar.
self.view.addConstraint(NSLayoutConstraint(item: customNavBar, attribute: .Left, relatedBy: .Equal, toItem: self.view, attribute: .Left, multiplier: 1.0, constant: 0))
self.view.addConstraint(NSLayoutConstraint(item: customNavBar, attribute: .Right, relatedBy: .Equal, toItem: self.view, attribute: .Right, multiplier: 1.0, constant: 0))
self.view.addConstraint(NSLayoutConstraint(item: customNavBar, attribute: .Bottom, relatedBy: .Equal, toItem: self.view, attribute: .Bottom, multiplier: 1.0, constant: 0))
bdNavBar.addConstraint(NSLayoutConstraint(item: customNavBar, attribute: NSLayoutAttribute.Height, relatedBy: .Equal, toItem: nil, attribute: NSLayoutAttribute.NotAnAttribute, multiplier: 1.0, constant: 50))
self.tabBar.bringSubviewToFront(customNavBar)

How to add a view between table view and navigation bar?

So what i m trying to achieve is I want to add a view between a tableview and the navigation bar. The hierarchy would be like:
Top-NavigationBar-betweenView-tableView-Bottom.
I have tried something like this, but the betweenView does not go under the navigation bar, and the tableView has a weird blank space above it. Anyone have any idea? Thank you!
let betweenView = searchController.searchBar
self.view.addSubview(betweenView)
let upperConstraint = NSLayoutConstraint(item: betweenView, attribute: .top, relatedBy: .equal, toItem: self.tableView, attribute: .top, multiplier: 1, constant: (self.navigationController?.navigationBar.frame.height)!)
let lowerConstraint = NSLayoutConstraint(item: self.tableView, attribute: .top, relatedBy: .equal, toItem: searchView, attribute: .bottom, multiplier: 1, constant: 0)
self.view.addConstraint(upperConstraint)
self.view.addConstraint(lowerConstraint)
You'll want to do 2 things here:
1) Layout the constraint for the search view to the top layout guide. This will move your search view below the navigation bar on a per need basis:
let upperConstraint = NSLayoutConstraint(item: searchView, attribute: .top, relatedBy: .equal, toItem: topLayoutGuide, attribute: .bottom, multiplier: 1, constant: 0)
2) You need to make sure the table does not automatically adjust the scrolling to the navigation controller:
tableView.automaticallyAdjustsScrollViewInsets = false

How to add a constraint programmatically so that the height of a view just fits the bottom of its last subview?

I have constraint code that lays out a number of UILabels (4+) vertically from top to bottom inside a container view (a regular UIView). I now want my container view to be sized so that its height matches the bottom of that last label that I've added.
I have tried this:
let constraintPanelHeight = NSLayoutConstraint(item: cell.panelOptions,
attribute: .Height, relatedBy: .Equal, toItem: priorLabel!,
attribute: .Bottom, multiplier: 1, constant: 0)
cell.contentView.addConstraint(constraintPanelHeight)
but this generates an Invalid pairing of layout attributes error since I'm matching .Height of one view with .Bottom of a subview (I'm guess that's why).
How can I auto-size my containing view like this?
I don't know what your trying to achieve there exactly. If you want to pin the one cell to the bottom of the other, you'll have to use attribute .Top and another constraint for the height. For example: you have 10 labels in your view, then set your height to one tenth of the superview:
let heightConstraint = NSLayoutConstraint(item: cell.panelOptions, attribute: .Height, relatedBy: .Equal, toItem: superview, attribute: .Height, multiplier: 0.1, constant: 0)
But since iOS9 there's also the UIStackView which makes laying out subviews in a view (vertically or horizontally) very easy. Have a look at that if you want to spread your labels evenly in your superview.
If you are fixed number of views the easiest and most readable way is the use the visual layout format and constraints like this:
NSLayoutConstraint.constraints
WithVisualFormat("V:|[view1][view2][view3]|",
options: NSLayoutFormatOptions.AlignAllCenterX,
metrics: nil,
views: ["view1": view1, "view2": view2,"view3": view3])
If you want to add a variable number the principle is the same but achieved with a loop. You shouldn't set the height of anything. Let the intrinsicContentSize of each label size the container from the inside. You might need to set the priority of contentCompressionResistance of each label to 1000 just to be sure the labels don't get squashed. Keep in mind the you will need horizontal constraints as well but they should be simpler to work out.
And here's the version for a variable number of subviews:
var prevView : UIView?
for view in views{
container.addSubview(view)
//
// Add horizontal constraints for each view to fit the container
// exclude for simplicity
// Add vertical constraints
if prevView == nil{
container.addConstraint(NSLayoutConstraint(item: container,
attribute: .Top,
relatedBy: .Equal,
toItem: view,
attribute: .Top,
multiplier: 1, constant: 0)
)
} else if view == views.last!{
container.addConstraint(NSLayoutConstraint(item: container,
attribute: .Bottom,
relatedBy: .Equal,
toItem: view,
attribute: .Bottom,
multiplier: 1,
constant: 0))
} else if let prev = prevView {
container.addConstraint(NSLayoutConstraint(item: prev,
attribute: .Bottom,
relatedBy: .Equal,
toItem: view,
attribute: .Top,
multiplier: 1,
constant: 0))
}
prevView = view
}

UIScrollView child view from Storyboard. Why extra 20 pixels top and left?

I have a view divided into four sections. A corner view in the top left (unused, invisible); a horizontal view across the top (timelineAreaView in the code) displaying one set of axis labels; a vertical view on the left side displaying another set of axis labels, and the rest of the view is a large collection view. It's not unlike a spreadsheet.
The problem is that I am trying to use a UIScrollView in the top view. If I add a test UIView to the top everything is fine. Here's the code:
let timelineViewController = self.storyboard?.instantiateViewControllerWithIdentifier("TimelineViewController") as? TimelineViewController
addChildViewController(timelineViewController!)
timelineAreaView.addSubview(timelineViewController!.view)
timelineViewController!.didMoveToParentViewController(self)
timelineViewController!.view.translatesAutoresizingMaskIntoConstraints = false
timelineAreaView.addConstraint(NSLayoutConstraint(item: timelineViewController!.view, attribute: NSLayoutAttribute.Left, relatedBy: NSLayoutRelation.Equal,
toItem: timelineAreaView, attribute: NSLayoutAttribute.Left, multiplier: 1.0, constant: 0))
timelineAreaView.addConstraint(NSLayoutConstraint(item: timelineViewController!.view, attribute: NSLayoutAttribute.Top, relatedBy: NSLayoutRelation.Equal,
toItem: timelineAreaView, attribute: NSLayoutAttribute.Top, multiplier: 1.0, constant: 0))
timelineAreaView.addConstraint(NSLayoutConstraint(item: timelineViewController!.view, attribute: NSLayoutAttribute.Right, relatedBy: NSLayoutRelation.Equal,
toItem: timelineAreaView, attribute: NSLayoutAttribute.Right, multiplier: 1.0, constant: 0))
timelineAreaView.addConstraint(NSLayoutConstraint(item: timelineViewController!.view, attribute: NSLayoutAttribute.Bottom, relatedBy: NSLayoutRelation.Equal,
toItem: timelineAreaView, attribute: NSLayoutAttribute.Bottom, multiplier: 1.0, constant: 0))
TimelineViewController is a plain UIViewController with the UIView property backgroundColor set to blue.
Here's the result, displayed in that excellent tool Reveal:
So far so good. But it needs to have a UIScrollView. So If I add a UIScrollView to that UIViewController in the storyboard it looks like this:
Its constraints now look like this:
Here's the problem shown in Reveal - the UIScrollView is highlighted:
Reveal Layout Inspector shows:
So WHY are there 20 pixels added to the y origin and subtracted from the x origin in the frame?
My first thought was to look at the view controllers settings for "Adjust Scroll View Insets" and "Under Top Bars" but these have no effect. I cannot find any setting that has any effect here.
Going back to the storyboard and compensating for the two mysterious 20 pixel errors, i.e.
This makes it look just like I want. But I shouldn't have to do this. What's going on?

Resources