I have a subclass of tableview cell which needs to have a UICollectionView inside it. I am now facing issues while trying to add a UICollectionView. It is letting me add a new UICollectionView but I am unable to set its constrains. Below is my code for doing the same:
// Add Collection
let layout = UICollectionViewFlowLayout()
layout.sectionInset = UIEdgeInsets(top: 20, left: 10, bottom: 10, right: 10)
collectionProducts = UICollectionView(frame: CGRect.zero, collectionViewLayout: layout)
contentView.addSubview(collectionProducts)
let leadingColConstraint = NSLayoutConstraint(item: collectionProducts, attribute: NSLayoutAttribute.leadingMargin, relatedBy: NSLayoutRelation.equal, toItem: contentView, attribute: NSLayoutAttribute.leadingMargin, multiplier: 1, constant: 0)
let trailingColConstraint = NSLayoutConstraint(item: collectionProducts, attribute: NSLayoutAttribute.trailingMargin, relatedBy: NSLayoutRelation.equal, toItem: contentView, attribute: NSLayoutAttribute.trailingMargin, multiplier: 1, constant: 0)
let topColConstraint = NSLayoutConstraint(item: collectionProducts, attribute: NSLayoutAttribute.top, relatedBy: NSLayoutRelation.equal, toItem: lblHeader, attribute: NSLayoutAttribute.bottom, multiplier: 1, constant: 62)
let bottomColConstraint = NSLayoutConstraint(item: collectionProducts, attribute: NSLayoutAttribute.top, relatedBy: NSLayoutRelation.equal, toItem: contentView, attribute: NSLayoutAttribute.bottom, multiplier: 1, constant: 55)
contentView.addConstraints([leadingColConstraint,trailingColConstraint,topColConstraint,bottomColConstraint])
collectionProducts.translatesAutoresizingMaskIntoConstraints = false
Am I missing anything? I am a relatively late mover to Swift and also relatively new to creating the cells programmatically. I can easily do the same using nib or storyboard, but I am stuck here. Kindly help be out.
Maybe you can avoid all those constraints, just add Visual Format Language constraints, like this
contentView.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|[v0]|", options: NSLayoutFormatOptions(), metrics: nil, views: ["v0": collectionProducts]))
contentView.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|[label][v0]|", options: NSLayoutFormatOptions(), metrics: nil, views: ["label": labelHeader,"v0":collectionProducts]))
And keep the collectionProducts.translatesAutoresizingMaskIntoConstraints to false
Related
"V:|[v(>=height)]-0.0#highPriority-|"
What will be the constraint (NSLayoutConstraint style) for above VFL.
Perhaps its considering view height with greaterThanEqual & bottom constraint with UILayoutPriority.defaultHigh.
Something i used -
let heightConstraint = NSLayoutConstraint(item: self.view!, attribute: NSLayoutAttribute.height, relatedBy: NSLayoutRelation.greaterThanOrEqual, toItem: nil, attribute: NSLayoutAttribute.notAnAttribute, multiplier: 1, constant: 0)
let bottomConstraint = NSLayoutConstraint(item: self.view!, attribute: NSLayoutAttribute.bottom, relatedBy: NSLayoutRelation.equal, toItem: nil, attribute: NSLayoutAttribute.notAnAttribute, multiplier: 1, constant: 0)
bottomConstraint.priority = .defaultHigh
NSLayoutConstraint.activate([heightConstraint,bottomConstraint])
Initial Set up for Answer:
let parentView = self.view!
let childView = UIView()
childView.backgroundColor = UIColor.lightGray
childView.translatesAutoresizingMaskIntoConstraints = false
parentView.addSubview(childView)
For given VFL:
"V:|[v(>=height)]-0.0#highPriority-|"
1. VFL Implementation:
let height: CGFloat = 100
let priority: Int = 1000
//VFL (for vertical positioning and height of childView
parentView.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|[v(>=\(height))]-0.0#\(priority)-|",
options: [],
metrics: nil,
views: ["v" : childView]))
2. NSLayoutConstraint Implementation:
The above VFL's NSLayoutConstraint equivalent is:
let height: CGFloat = 100
//VFL Equivalent: "V:|[v]"
let topConstraint = NSLayoutConstraint(item: childView,
attribute: .top,
relatedBy: .equal,
toItem: parentView,
attribute: .top,
multiplier: 1,
constant: 0)
//VFL Equivalent: "[v(>=height)]"
let heightConstraint = NSLayoutConstraint(item: childView,
attribute: .height,
relatedBy: .greaterThanOrEqual,
toItem: nil,
attribute: .notAnAttribute,
multiplier: 1,
constant: height)
//VFL Equivalent: "[v]|" or "[v]-0.0-|"
let bottomConstraint = NSLayoutConstraint(item: childView,
attribute: .bottom,
relatedBy: .equal,
toItem: parentView,
attribute: .bottom,
multiplier: 1,
constant: 0)
//Adding VFL Equivalent: #priority
bottomConstraint.priority = .defaultHigh
childView.addConstraint(heightConstraint)
parentView.addConstraint(topConstraint)
parentView.addConstraint(bottomConstraint)
NOTE: The given VFL in your question only provides the childViews y position and height.
For width, add the constraints accordingly.
VFL Example for width would be:
parentView.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|[v(100)]",
options: [],
metrics: nil,
views: ["v" : childView]))
Ref:
Apple's Autolayout Guide on VFL
I have an image view that I set up like this:
let buttonImageView: UIImageView = {
let iv = UIImageView()
iv.backgroundColor = .red
iv.translatesAutoresizingMaskIntoConstraints = false
iv.frame.size = CGSize(width: CGFloat(375 * (11 / 59)), height: CGFloat(375 * (11 / 59)))
return iv
}()
Then, I add constraints:
/* center-top button */
// buttonImageView.frame = CGRect(x: 0, y: 0, width: buttonDiameter, height: buttonDiameter)
addConstraint(NSLayoutConstraint(item: buttonImageView, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: buttonDiameter))
addConstraint(NSLayoutConstraint(item: buttonImageView, attribute: .width, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: buttonDiameter))
addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:[v0]", options: NSLayoutFormatOptions.alignAllCenterX, metrics: nil, views: ["v0": buttonImageView]))
// addConstraintsWithFormat("H:[v0]", views: buttonImageView)
addConstraintsWithFormat("V:|-\(buttonSpacing)-[v0]", views: buttonImageView)
//addConstraint(NSLayoutConstraint(item: buttonImageView, attribute: .centerX, relatedBy: .equal, toItem: self, attribute: .centerX, multiplier: 1, constant: 0))
Please ignore the "Hi!" square. The red square below it, buttonImageView, should be at that vertical constraint, but centered horizontally.
If you use constraints programmatically, you first need to activate them
blabla.constraint1.isActive = true;
Replace:
addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:[v0]", options: NSLayoutFormatOptions.alignAllCenterX, metrics: nil, views: ["v0": buttonImageView]))
with:
NSLayoutConstraint(item: buttonImageView, attribute: .centerX, relatedBy: .equal, toItem: self, attribute: .centerX, multiplier: 1.0, constant: 0.0).isActive = true
Should do the trick.
addConstraint(NSLayoutConstraint(item: buttonImageView, attribute: .centerX, relatedBy: .equal, toItem: self, attribute: .centerX, multiplier: 1, constant: 0))
I'm triying to instert programatically a view inside an UIScrollView, but it doesn't appear here is my code:
mainScrollView.translatesAutoresizingMaskIntoConstraints = false
self.view.addSubview(mainScrollView)
//Add Trailing
let trailingConstraint = NSLayoutConstraint(item: mainScrollView, attribute: .trailing, relatedBy: .equal, toItem: self.view, attribute: .trailing, multiplier: 1, constant: 0)
self.view.addConstraint(trailingConstraint)
//Add Leading
let leadingConstraint = NSLayoutConstraint(item: mainScrollView, attribute: .leading, relatedBy: .equal, toItem: self.view, attribute: .leading, multiplier: 1, constant: 0)
self.view.addConstraint(leadingConstraint)
//Add Top
let topConstraint = NSLayoutConstraint(item: mainScrollView, attribute: .top, relatedBy: .equal, toItem: self.view, attribute: .top, multiplier: 1, constant: 0)
self.view.addConstraint(topConstraint)
//Add Bottom
let bottomConstraint = NSLayoutConstraint(item: mainScrollView, attribute: .bottom, relatedBy: .equal, toItem: self.view, attribute: .bottom, multiplier: 1, constant: 0)
self.view.addConstraint(bottomConstraint)
let contentView = UIView()
contentView.translatesAutoresizingMaskIntoConstraints = false
contentView.backgroundColor = .blue
mainScrollView.addSubview(contentView)
//Add Trailing
let trailingConstraintContent = NSLayoutConstraint(item: contentView, attribute: .trailing, relatedBy: .equal, toItem: mainScrollView, attribute: .trailing, multiplier: 1, constant: 0)
mainScrollView.addConstraint(trailingConstraintContent)
//Add Leading
let leadingConstraintContent = NSLayoutConstraint(item: contentView, attribute: .leading, relatedBy: .equal, toItem: mainScrollView, attribute: .leading, multiplier: 1, constant: 0)
mainScrollView.addConstraint(leadingConstraintContent)
//Add Top
let topConstraintContent = NSLayoutConstraint(item: contentView, attribute: .top, relatedBy: .equal, toItem: mainScrollView, attribute: .top, multiplier: 1, constant: 0)
mainScrollView.addConstraint(topConstraintContent)
//Add Bottom
let bottomConstraintContent = NSLayoutConstraint(item: contentView, attribute: .bottom, relatedBy: .equal, toItem: mainScrollView, attribute: .bottom, multiplier: 1, constant: 0)
mainScrollView.addConstraint(bottomConstraintContent)
The first ScrollView is inserted because I added a background color and I can see it, but I cant see the contentview added as background color blue.
Any help?
UPDATE
I have tried the following to and no success:
mainScrollView.translatesAutoresizingMaskIntoConstraints = false
let contentView = UIView()
contentView.translatesAutoresizingMaskIntoConstraints = false
mainScrollView.backgroundColor = .red
contentView.backgroundColor = .blue
self.view.addSubview(mainScrollView)
mainScrollView.addSubview(contentView)
let viewsDictionary = ["mainScrollView": mainScrollView, "contentView": contentView]
let mainScrollViewVerticalConstraint = NSLayoutConstraint.constraints(withVisualFormat: "V:|[mainScrollView]|", options: [], metrics: nil, views: viewsDictionary)
let mainScrollViewHorizontalConstraint = NSLayoutConstraint.constraints(withVisualFormat: "H:|[mainScrollView]|", options: [], metrics: nil, views: viewsDictionary)
self.view.addConstraints(mainScrollViewVerticalConstraint)
self.view.addConstraints(mainScrollViewHorizontalConstraint)
let contentViewVerticalConstraint = NSLayoutConstraint.constraints(withVisualFormat: "V:|[contentView]|", options: [], metrics: nil, views: viewsDictionary)
let contentViewHorizontalConstraint = NSLayoutConstraint.constraints(withVisualFormat: "H:|[contentView]|", options: [], metrics: nil, views: viewsDictionary)
mainScrollView.addConstraints(contentViewVerticalConstraint)
mainScrollView.addConstraints(contentViewHorizontalConstraint)
When adding sub views the important thing to remember is to pin them in a way to make it possible for Auto Layout to evaluate the height, this is because the scroll view determines the content size by determining the sum of heights sub views (in case of vertical).
So you should specify the height in VFL . eg.
let contentViewVerticalConstraint=NSLayoutConstraint.constraints(withVisualFormat: "V:|[contentView(300)]", options: [], metrics: nil, views: viewsDictionary)
Create a custom view using .xib file
Apply the constraints for the custom view child components in .xib (auto layout)
Create a custom view instant
MyView *myview = [[[NSBundle mainBundle] loadNibNamed:#"nibName" owner:nil options:nil] objectAtIndex:0];
Set its frame for screen width as
CGRect screenSize = [UIScreen mainScreen].bounds.size;
myView = CGRectMake(0.0, 0.0, screenSize.width, 100.0);
Add to parent view
[parentView addSubView:myView];
I have a UITableView with those constraints. I'm trying to create the UITableView programmatically, but still want these constraints in code.
You can do this relatively easy by using visual format language:
Add your view, topLayoutGuide and bottomLayoutGuide to a dictionary. I used views. You also have to set translatesAutoresizingMaskIntoConstraints to false for the constraints to work.
Example:
view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:|[yourView]-(-15)-|", options: [], metrics: nil, views: views))
view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:[topLayout][yourView][botLayout]", options: [], metrics: nil, views: views))
one way to do is :
let newView = UIView()
newView.backgroundColor = UIColor.redColor()
newView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(newView)
let horizontalConstraint = NSLayoutConstraint(item: newView, attribute: NSLayoutAttribute.Leading, relatedBy: NSLayoutRelation.Equal, toItem: view, attribute: NSLayoutAttribute.Leading, multiplier: 1, constant: 0)
view.addConstraint(horizontalConstraint)
let verticalConstraint = NSLayoutConstraint(item: newView, attribute: NSLayoutAttribute.Trailing, relatedBy: NSLayoutRelation.Equal, toItem: view, attribute: NSLayoutAttribute.Trailing, multiplier: 1, constant: -15)
view.addConstraint(verticalConstraint)
let widthConstraint = NSLayoutConstraint(item: newView, attribute: NSLayoutAttribute.Top, relatedBy: NSLayoutRelation.Equal, toItem: nil, attribute: NSLayoutAttribute.Top, multiplier: 1, constant: 0)
view.addConstraint(widthConstraint)
let heightConstraint = NSLayoutConstraint(item: newView, attribute: NSLayoutAttribute.Bottom, relatedBy: NSLayoutRelation.Equal, toItem: nil, attribute: NSLayoutAttribute.Bottom, multiplier: 1, constant: 0)
view.addConstraint(heightConstraint)
for more info you can follow this Swift | Adding constraints programmatically
I'm attempting to layout all my constraints programmatically because I have to add and remove boxes based on user input. When I run a function which applies all my constraints in ViewDidLoad, it works great. But if I run it again, after removing them all, 2 of my labels disappear behind my navigation bar. I can't figure out why! Keep in mind if I never run the function pasted below, my screen is blank because I have no constraints on start up from the storyboard. My goal is to figure out why my label and button disappear when the same piece of code is run a second time.
Below is a picture when the code, which I'll post below, is run initially in ViewDidLoad:
Below is a picture when the same piece of code is run again afterwards when the next button is pressed. This is just a test, in the future I'll need to reset all my constraints after changing them:
And Below is a picture of the debug view hierarchy afterwards:
The code that creates the constraints is below. Keep in mind I have only 5 elements, my main view, scroll view, aglAltitudeLabel which is the label, nextButton which is the button, and aglAltitude which is the text input:
//Remove all pre-existing contraints
var constraints:NSArray = aglAltitudeLabel.constraints()
aglAltitudeLabel.removeConstraints(constraints)
constraints = aglAltitude.constraints()
aglAltitude.removeConstraints(constraints)
constraints = nextButton.constraints()
nextButton.removeConstraints(constraints)
constraints = scrollView.constraints()
scrollView.removeConstraints(constraints)
constraints = view.constraints()
self.view.removeConstraints(constraints)
constraints = calculateButton.constraints()
calculateButton.removeConstraints(constraints)
//Make scrollview fit normal View, accounting for Navigation bar
self.view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("|[first(\(self.view.frame.width))]|", options: nil, metrics: nil, views: ["first": scrollView]))
self.view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|-64-[first(\(self.view.frame.height-64))]|", options: nil, metrics: nil, views: ["first": scrollView]))
//Set the heights for the label and button
let labelHeight = NSLayoutConstraint(item: aglAltitudeLabel, attribute: NSLayoutAttribute.Height, relatedBy: NSLayoutRelation.Equal, toItem: nil, attribute: NSLayoutAttribute.NotAnAttribute, multiplier: 1.0, constant: 20)
scrollView.addConstraint(labelHeight)
let buttonHeight = NSLayoutConstraint(item: nextButton, attribute: NSLayoutAttribute.Height, relatedBy: NSLayoutRelation.Equal, toItem: nil, attribute: NSLayoutAttribute.NotAnAttribute, multiplier: 1.0, constant: 30)
scrollView.addConstraint(buttonHeight)
//Setup the vertical layout
scrollView.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|-8-[first(30)]-8-|", options: nil, metrics: nil, views: ["first": aglAltitude]))
//Setup the horizontal layout
scrollView.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("|-8-[first(97)]-8-[second(>=100)]-8-[third(46)]-8-|", options: nil, metrics: nil, views: ["first": aglAltitudeLabel, "second": aglAltitude, "third": nextButton]))
//Align baselines - this also occurs with the option above of AlignAllBaselines and leaving this code out
let aglLabelBaseline = NSLayoutConstraint(item: aglAltitudeLabel, attribute: NSLayoutAttribute.Baseline, relatedBy: NSLayoutRelation.Equal, toItem: aglAltitude, attribute: NSLayoutAttribute.Baseline, multiplier: 1.0, constant: 0)
scrollView.addConstraint(aglLabelBaseline)
let nextButtonBaseline = NSLayoutConstraint(item: nextButton, attribute: NSLayoutAttribute.Baseline, relatedBy: NSLayoutRelation.Equal, toItem: aglAltitude, attribute: NSLayoutAttribute.Baseline, multiplier: 1.0, constant: 0)
scrollView.addConstraint(nextButtonBaseline)
self.view.layoutSubviews()
Thank you for any help. I'm sure I'm doing 55 things incorrectly.
Edit: If I change the last section of code from this:
//Align baselines - this also occurs with the option above of AlignAllBaselines and leaving this code out
let aglLabelBaseline = NSLayoutConstraint(item: aglAltitudeLabel, attribute: NSLayoutAttribute.Baseline, relatedBy: NSLayoutRelation.Equal, toItem: aglAltitude, attribute: NSLayoutAttribute.Baseline, multiplier: 1.0, constant: 0)
scrollView.addConstraint(aglLabelBaseline)
let nextButtonBaseline = NSLayoutConstraint(item: nextButton, attribute: NSLayoutAttribute.Baseline, relatedBy: NSLayoutRelation.Equal, toItem: aglAltitude, attribute: NSLayoutAttribute.Baseline, multiplier: 1.0, constant: 0)
scrollView.addConstraint(nextButtonBaseline)
To this:
//Align baselines - this also occurs with the option above of AlignAllBaselines and leaving this code out
let aglLabelBaseline = NSLayoutConstraint(item: aglAltitudeLabel, attribute: NSLayoutAttribute.Top, relatedBy: NSLayoutRelation.Equal, toItem: scrollView, attribute: NSLayoutAttribute.Top, multiplier: 1.0, constant: 14)
self.view.addConstraint(aglLabelBaseline)
let nextButtonBaseline = NSLayoutConstraint(item: nextButton, attribute: NSLayoutAttribute.Top, relatedBy: NSLayoutRelation.Equal, toItem: scrollView, attribute: NSLayoutAttribute.Top, multiplier: 1.0, constant: 8)
self.view.addConstraint(nextButtonBaseline)
it works exactly as I'd expect. Why the difference?
Without knowing what your requirements are, it's hard to give you good advice. Here is an example of how I might do it. I added all the views in code, and added background colors to the views so I could better see their layout. I'm assuming that the scroll view is only added once, and never removed, so it's constraints to self.view should only be added once in viewDidLoad. Same goes for the height of the the other views if you don't need to change them (notice that I add the height constraints to the view itself, not its superview). The only thing that needs to be removed and re-added are the constraints between the scroll view and its subviews.
class ViewController: UIViewController {
var aglAltitudeLabel = UILabel()
var aglAltitude = UITextField()
var nextButton = UIButton.buttonWithType(.System) as UIButton
var scrollView = UIScrollView()
var viewsDict: NSDictionary!
override func viewDidLoad() {
super.viewDidLoad()
aglAltitudeLabel.text = "AGL Altitude"
aglAltitudeLabel.backgroundColor = UIColor.lightGrayColor()
aglAltitudeLabel.setTranslatesAutoresizingMaskIntoConstraints(false)
aglAltitude.setTranslatesAutoresizingMaskIntoConstraints(false)
aglAltitude.backgroundColor = UIColor.yellowColor()
aglAltitudeLabel.addConstraint(NSLayoutConstraint(item: aglAltitudeLabel, attribute: .Height, relatedBy: .Equal, toItem: nil, attribute: .NotAnAttribute, multiplier: 1, constant: 30))
scrollView.setTranslatesAutoresizingMaskIntoConstraints(false)
nextButton.setTranslatesAutoresizingMaskIntoConstraints(false)
nextButton.setTitle("Next", forState: .Normal)
nextButton.backgroundColor = UIColor.lightGrayColor()
nextButton.addConstraint(NSLayoutConstraint(item: nextButton, attribute: .Height, relatedBy: .Equal, toItem: nil, attribute: .NotAnAttribute, multiplier: 1, constant: 30))
self.view.addSubview(scrollView)
scrollView.addSubview(aglAltitude)
scrollView.addSubview(aglAltitudeLabel)
scrollView.addSubview(nextButton)
scrollView.backgroundColor = UIColor(red: 1, green: 1, blue: 0.8, alpha: 1)
viewsDict = ["scrollView": scrollView, "aglAltitudeLabel": aglAltitudeLabel, "aglAltitude":aglAltitude, "nextButton":nextButton] as NSDictionary
self.view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("|[scrollView]|", options: nil, metrics: nil, views: viewsDict))
self.view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|-64-[scrollView]|", options: nil, metrics: nil, views: viewsDict))
self.redoConstraints()
}
#IBAction func redoConstraints() {
scrollView.removeConstraints(scrollView.constraints())
scrollView.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|-8-[aglAltitude(30)]-8-|", options: nil, metrics: nil, views: viewsDict))
scrollView.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("|-8-[aglAltitudeLabel(97)]-8-[aglAltitude(>=100)]-8-[nextButton(46)]-8-|", options: .AlignAllBottom , metrics: nil, views: viewsDict))
}
}
Notice that I took the fixed width and height out of the constraints for the scroll view. There's no need for that, and it would cause it to not work correctly on rotation.