How to add equal width and equal height constraints programmatically in swift? - swift

Could anyone help me I have this simple view that is very simple to do if I'm using story board but all of my UILabel is based on somewhere(API) that is why I need to create my view programmatically.
this is the view I would like to achieved
what I accomplished at the moment are pinning the top label in its position and adding leading space on label 1, 2, & 3
basically what i need are:
pin the label 1 to the top label
pin the label 3 to bottom
add vertical spaces to label 1,2,3
have equal width and height constraint in label 1,2 ,3
this is my code so far.
private func addTopConstraint(from:AnyObject,to:AnyObject,cons:CGFloat){
let topConstraint = NSLayoutConstraint(item: from, attribute: .Top, relatedBy: .Equal, toItem: to, attribute: .Bottom, multiplier: 1.0, constant: cons)
self.view.addConstraint(topConstraint)
}
private func addVerticalSpace(from:AnyObject,to:AnyObject,cons:CGFloat){
let verticalSpace = NSLayoutConstraint(item: from, attribute: .Bottom, relatedBy: .Equal, toItem: to, attribute: .Bottom, multiplier: 1.0, constant: cons)
self.view.addConstraint(verticalSpace)
}
private func addLeadingSpace(from:AnyObject,to:AnyObject,cons:CGFloat){
let leadingSpace = NSLayoutConstraint(item: from, attribute: .Leading, relatedBy: .Equal, toItem: to, attribute: .Leading, multiplier: 1.0, constant: cons)
self.view.addConstraint(leadingSpace)
}
private func sameWidthAndHeight (from:AnyObject,to:AnyObject){
let width = NSLayoutConstraint(item: from, attribute: .Width, relatedBy: .Equal, toItem: to, attribute: .Width, multiplier: 1.0, constant: 0)
let height = NSLayoutConstraint(item: from, attribute: .Height, relatedBy: .Equal, toItem: to, attribute: .Height, multiplier: 1.0, constant: 0)
self.view.addConstraint(width)
self.view.addConstraint(height)
}
but the sameWidthAndHeight and addVerticalSpace function fails
maybe it something to do with content hugging and priorities too, I'm not that sure since we're used to do this in storyboard.
could anyone share a thought? Thanks in advance

Related

A layout issue (NSLayoutConstraint) in an iOS app

In an iOS app I have an autolayout issue.
The 2 following screenshots show the problem.
The switch (UISwitch object) on the right is displaced horizontally, when it should be fixed. Can anyone see what is happening?
It is true that the string on the left is changing length, but I think (according to the way I have set the constraints up) the font should be resized or the string split in 2 lines; but not the switch displaced.
Here is the relevant swift code:
import UIKit
class My_ViewController: UIViewController {
let xPanel = UILabel(), yPanel = UILabel(),
khToggle = UISwitch(), khLabel = UILabel()
....
override func viewDidLoad() {
super.viewDidLoad()
layOutUI()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
.....
toggleKeepHide(khToggle)
}
func layOutUI() {
for component in [xPanel,yPanel,khLabel,khToggle] {
component.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(component)
}
...........
khLabel.numberOfLines = 0
khLabel.adjustsFontSizeToFitWidth = true
khToggle.addTarget(self,
action: #selector(toggleKeepHide(_:)),
for: .valueChanged)
view.addConstraints([
.........
NSLayoutConstraint(item: khToggle, attribute: .right, relatedBy: .equal,
toItem: view, attribute: .right, multiplier: 1.0, constant: -30.0),
NSLayoutConstraint(item: khToggle, attribute: .top, relatedBy: .equal,
toItem: yPanel, attribute: .bottom, multiplier: 1.0,
constant: 50.0),
NSLayoutConstraint(item: khLabel, attribute: .right, relatedBy: .equal,
toItem: khToggle, attribute: .left, multiplier: 1.0, constant: -23.0),
NSLayoutConstraint(item: khLabel, attribute: .centerY, relatedBy: .equal,
toItem: khToggle, attribute: .centerY, multiplier: 1.0, constant: 0.0),
NSLayoutConstraint(item: khLabel, attribute: .left, relatedBy: .equal,
toItem: view, attribute: .left, multiplier: 1.0, constant: 30.0)])
}
#objc func toggleKeepHide(_ sender: UISwitch) {
if sender.isOn {khLabel.text = "Hide this object from the wyxoug list."}
else {khLabel.text = "Keep this object in the wyxoug list."}
}
}
You haven't provided sufficient information to reproduce the problem. Here's a reduction of your code, in my view controller's viewDidLoad (I eliminated everything but the label and the switch, fixed your left and right (you should never use those), and changed the alignment between the two views to top instead of center):
khToggle = UISwitch()
khToggle.translatesAutoresizingMaskIntoConstraints = false
self.view.addSubview(khToggle)
khLabel = UILabel()
khLabel.text = String(repeating: "word ", count: 40)
khLabel.translatesAutoresizingMaskIntoConstraints = false
self.view.addSubview(khLabel)
// Do any additional setup after loading the view.
khLabel.numberOfLines = 0
view.addConstraints([
NSLayoutConstraint(item: khToggle!, attribute: .trailing, relatedBy: .equal,
toItem: view, attribute: .trailing, multiplier: 1.0, constant: -30.0),
NSLayoutConstraint(item: khToggle!, attribute: .top, relatedBy: .equal,
toItem: view, attribute: .top, multiplier: 1.0,
constant: 50.0),
NSLayoutConstraint(item: khLabel!, attribute: .trailing, relatedBy: .equal,
toItem: khToggle, attribute: .leading, multiplier: 1.0, constant: -23.0),
NSLayoutConstraint(item: khLabel!, attribute: .top, relatedBy: .equal,
toItem: khToggle, attribute: .top, multiplier: 1.0, constant: 0.0),
NSLayoutConstraint(item: khLabel!, attribute: .leading, relatedBy: .equal,
toItem: view, attribute: .leading, multiplier: 1.0, constant: 30.0)])
The result displays fine; there are no constraint conflicts or ambiguities, and it looks as one would expect:
The horizontal constraints for the label and the toggle are competing against each other.
(If you step in to Xcode's visual debugger, you'll find that there is a warning: the UISwitch instance has an ambiguous width and horizontal position.)
Solution
You have provided absolute values to AutoLayout, and it cannot resolve the requirements. To fix this, introduce some flexibility in to the requirements by setting the compression resistance of the label to a lower value:
khLabel.setCompressionResistance(.defaultLow, for: .horizontal)

Formatting UICollectionViewCell inner items with constraints

I have a UICollectionViewCell and I want to be able to format the items inside of it more freely. That means - I want to be able to set constraints relative to the cell itself.
This is my cell:
And this is my code:
//image View Constraints
let productImageTopConstraint = NSLayoutConstraint(item: ProductImageView, attribute: .top, relatedBy: .equal, toItem: self, attribute: .top, multiplier: 1, constant: 1) // constant was 10
let productImageBottomConstraint = NSLayoutConstraint(item: ProductImageView, attribute: .bottom, relatedBy: .equal, toItem: self, attribute: .bottom, multiplier: 1, constant: -30)
let productImageLeadingConstraint = NSLayoutConstraint(item: ProductImageView, attribute: .leading, relatedBy: .equal, toItem: self, attribute: .leading, multiplier: 1, constant: 10)
let productImageTrailingConstraint = NSLayoutConstraint(item: ProductImageView, attribute: .trailing, relatedBy: .equal, toItem: self, attribute: .trailing, multiplier: 1, constant: -10)
//product name field constraints
let productNameTopConstraint = NSLayoutConstraint(item: ProductName, attribute: .top, relatedBy: .equal, toItem: ProductImageView, attribute: .bottom, multiplier: 1, constant: 10)
let productNameBottomConstraint = NSLayoutConstraint(item: ProductName, attribute: .bottom, relatedBy: .equal, toItem: self, attribute: .bottom, multiplier: 1, constant: 0)
let productNameLeadingConstraint = NSLayoutConstraint(item: ProductName, attribute: .leading, relatedBy: .equal, toItem: self, attribute: .leading, multiplier: 1, constant: 10)
let productNameTrailingConstraint = NSLayoutConstraint(item: ProductName, attribute: .trailing, relatedBy: .equal, toItem: self, attribute: .trailing, multiplier: 1, constant: 0)
What I want:
The ImageView to be closer to the cell's top edge
The product name label to be in the center
To be able to add another label between the product name label and the cell's bottom edge
How do I do that ? How do I take into account the cell's edges ?
You can use Layout Anchors to achieve this.
first get the margins of your UICollectionViewCell contentView like below
let margins = self.contentView.layoutMarginsGuide
1. The ImageView to be closer to the cell's top edge
Add following constraints relative to cell's content view margins like below
//Add top, left, right constraint relative to cell content view like below
yourImageView.topAnchor.constraint(equalTo: margins.topAnchor,constant:5).isActive = true
yourImageView.leftAnchor.constraint(equalTo: margins.leftAnchor,constant:5).isActive = true
yourImageView.rightAnchor.constraint(equalTo: margins.rightAnchor,constant:5).isActive = true
2. The product name label to be in the center
//To center align
yourLabel.centerXAnchor.constraint(equalTo: margins.centerXAnchor).isActive = true
//Now set your label top anchor to display it below your image view
yourLabel.topAnchor.constraint(equalTo: yourImageView.bottomAnchor,constant:5).isActive = true
3. To be able to add another label between the product name label and the cell's bottom edge
anotherLabel.topAnchor.constraint(equalTo: yourLabel.bottomAnchor,constant:5).isActive = true
anotherLabel.bottomAnchor.constraint(equalTo: margins.bottomAnchor).isActive = true
//To center align
anotherLabel.centerXAnchor.constraint(equalTo: margins.centerXAnchor).isActive = true
UPDATE
Make sure you have added your control as subview and set translatesAutoresizingMaskIntoConstraints = false
The order in which you should add constraint programmatically is as follows
Initialise your controls like let yourLabel = UILabel()
Set yourLabel.translatesAutoresizingMaskIntoConstraints = false
Add your label as subview self.addSubView(yourLabel)
Add constraints to your label
1
let productImageTopConstraint = NSLayoutConstraint(item: ProductImageView,
attribute: .top,
relatedBy: .equal,
toItem: self,
attribute: .top,
multiplier: 1,
constant: 1) <- make it 0 so it will be pinned to top edge
2 Set ProductName.textAlignment = .center
3 a) Remove productNameBottomConstraint so ProductName's height will be calculated from text and font automatically
b) Add another label with layout
let productName2TopConstraint = NSLayoutConstraint(item: ProductName2, attribute: .top, relatedBy: .equal, toItem: ProductName, attribute: .bottom, multiplier: 1, constant: 10)
let productName2BottomConstraint = NSLayoutConstraint(item: ProductName2, attribute: .bottom, relatedBy: .equal, toItem: self, attribute: .bottom, multiplier: 1, constant: 0)
let productName2LeadingConstraint = NSLayoutConstraint(item: ProductName2, attribute: .leading, relatedBy: .equal, toItem: self, attribute: .leading, multiplier: 1, constant: 10)
let productName2TrailingConstraint = NSLayoutConstraint(item: ProductName2, attribute: .trailing, relatedBy: .equal, toItem: self, attribute: .trailing, multiplier: 1, constant: 0)

Adding constraints to in image within scrollView breaks panning

Using swift 3.0 iOS 10.x
Need an image within a scrollView to stick to the sides when I go from landscape to portrait and put this NSLayout rules into place.
let imageViewRight = (NSLayoutConstraint(item: self.image2P, attribute: .right, relatedBy: .equal, toItem: self.view, attribute: .right, multiplier: 1.0, constant: 0))
let imageViewLeft = (NSLayoutConstraint(item: self.image2P, attribute: .left, relatedBy: .equal, toItem: self.view, attribute: .left, multiplier: 1.0, constant: 0))
let imageViewTop = NSLayoutConstraint(item: self.image2P, attribute: .top, relatedBy: .equal, toItem: self.view, attribute: .top, multiplier: 1, constant: 0)
let imageViewBot = NSLayoutConstraint(item: self.image2P, attribute: .bottom, relatedBy: .equal, toItem: self.view, attribute: .bottom, multiplier: 1, constant:0)
self.view.addConstraints([imageViewBot,imageViewTop,imageViewRight,imageViewLeft])
NSLayoutConstraint.activate([imageViewBot,imageViewTop,imageViewRight,imageViewLeft])
They work, but of course if I zoom into the image I find they break panning.
I thought I might be able to get around it by activating and deactiving them, but even if I simply add them it breaks... is this correct behaviour?

Adding constraints programmatically in UIView with UITextView

I added constraints programmatically in my UITextView, but trailing and bottom constraints are not working correctly. others work fine.
I think UITextView frame size is not correct.
I just want to add margin in my UITextView programmatically.
My code is
let textView = UITextView()
self.view.addSubview(textView)
var constraints = [NSLayoutConstraint]()
constraintTop = NSLayoutConstraint(item: textView,
attribute: NSLayoutAttribute.Top,
relatedBy: NSLayoutRelation.Equal,
toItem: self.view,
attribute: NSLayoutAttribute.Top,
multiplier: 1.0,
constant: 10)
constraints.append(constraintTop)
constraintBottom = NSLayoutConstraint(item: textView,
attribute: NSLayoutAttribute.Bottom,
relatedBy: NSLayoutRelation.Equal,
toItem: self.view,
attribute: NSLayoutAttribute.Bottom,
multiplier: 1.0,
constant: 10)
constraints.append(constraintBottom)
constraintLeft = NSLayoutConstraint(item: textView,
attribute: NSLayoutAttribute.Leading,
relatedBy: NSLayoutRelation.Equal,
toItem: self.view,
attribute: NSLayoutAttribute.Leading,
multiplier: 1.0,
constant: 10)
constraints.append(constraintLeft)
constraintRight = NSLayoutConstraint(item: textView,
attribute: NSLayoutAttribute.Trailing,
relatedBy: NSLayoutRelation.Equal,
toItem: self.view,
attribute: NSLayoutAttribute.Trailing,
multiplier: 1.0,
constant: 10)
constraints.append(constraintRight)
self.view.addConstraints(constraints)
You can fix this in one of two ways:
Change the constant to -10 for your bottom and trailing constraints.
or
Switch the order of the item: and toItem: values in the bottom and trailing constraints. That is, make item: self.view and toItem: textView for the bottom and trailing constraints. This is how it is done if you set the constraints in the StoryBoard.

UIScrollView with dynamically created UIButtons not Scrolling

I am using Xcode 6.2 and Swift. I have set up with storyboard a UIScrollView which I fill with varying numbers of programmatically created buttons. Each button varies in height due to the data contained. All of this usually creates a list longer than a single screen thus requiring scroll ability. As I loop and create each button I also adjust the height of the button to meet the needs of the content. So, in short varying numbers of buttons of varying size. I am able to set the height for the scrollView by summing up each buttons height during creation. Suddenly I cannot scroll the buttons anymore. I have tried many of the suggestions made on this site for other related questions but none fix my problem.
some code:
// Add constraints depending on what number button it is
let heightConstraint:NSLayoutConstraint = NSLayoutConstraint(item: codeData, attribute: NSLayoutAttribute.Height, relatedBy: NSLayoutRelation.Equal, toItem: nil, attribute: NSLayoutAttribute.NotAnAttribute, multiplier: 1, constant: CGFloat(buttonHeight))
codeData.addConstraint(heightConstraint)
let leftMarginConstraint:NSLayoutConstraint = NSLayoutConstraint(item: codeData, attribute: NSLayoutAttribute.Left, relatedBy: NSLayoutRelation.Equal, toItem: self.scrollviewContentView, attribute: NSLayoutAttribute.Left, multiplier: 1, constant: 900)
let rightMarginConstraint:NSLayoutConstraint = NSLayoutConstraint(item: codeData, attribute: NSLayoutAttribute.Right, relatedBy: NSLayoutRelation.Equal, toItem: self.scrollviewContentView, attribute: NSLayoutAttribute.Right, multiplier: 1, constant: 900)
let topMarginConstraint:NSLayoutConstraint = NSLayoutConstraint(item: codeData, attribute: NSLayoutAttribute.Top, relatedBy: NSLayoutRelation.Equal, toItem: self.scrollviewContentView, attribute: NSLayoutAttribute.Top, multiplier: 1, constant: CGFloat(sumOfButtonTops))
self.scrollviewContentView.addConstraints([leftMarginConstraint,rightMarginConstraint, topMarginConstraint])
let contentViewHeight:NSLayoutConstraint = NSLayoutConstraint(item: self.scrollviewContentView, attribute: NSLayoutAttribute.Height, relatedBy: NSLayoutRelation.Equal, toItem: self.codeButtonArray[0], attribute: NSLayoutAttribute.Height, multiplier: 1, constant: CGFloat(sumOfButtonTops + 30))