Adding programmatically label with relative constraint issue - ios

I've an UITableView with some cells, in the first cell I would like to add multiple UILabel. That's the code I am using inside a function in UITableViewCell sub class (called in cellForRowAt):
...
let constantTop = 16
for (index,optional) in optionals.enumerated(){
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.text = "\(optional.name)"
label.tag = 10010132
label.font = CocoFonts.semibold(size: 15)
label.textColor = CocoColors.FedericoMalagoni.textVeryDarkBlue
self.contentView.addSubview(label)
let constant:CGFloat = CGFloat(constantTop * (index + 1))
print(constant)
let horizontalConstraint = NSLayoutConstraint(item: label, attribute: NSLayoutAttribute.leading, relatedBy: NSLayoutRelation.equal, toItem: self.contentView, attribute: NSLayoutAttribute.leading, multiplier: 1, constant: 16)
let verticalConstraint = NSLayoutConstraint(item: label, attribute: NSLayoutAttribute.trailing, relatedBy: NSLayoutRelation.equal, toItem: self.contentView, attribute: NSLayoutAttribute.trailing, multiplier: 1, constant: 16)
let height = NSLayoutConstraint(item: label, attribute: NSLayoutAttribute.height, relatedBy: NSLayoutRelation.equal, toItem: nil, attribute: NSLayoutAttribute.notAnAttribute, multiplier: 1, constant: 20)
let top = NSLayoutConstraint(item: label, attribute: NSLayoutAttribute.top, relatedBy: NSLayoutRelation.equal, toItem: self.lblInfo, attribute: NSLayoutAttribute.bottomMargin, multiplier: 1, constant: constant)
let bottom = NSLayoutConstraint(item: label, attribute: NSLayoutAttribute.bottom, relatedBy: NSLayoutRelation.equal, toItem: self.contentView, attribute: NSLayoutAttribute.bottom, multiplier: 1, constant: constant)
NSLayoutConstraint.activate([horizontalConstraint, verticalConstraint, top, bottom, height])
}
self.contentView.setNeedsUpdateConstraints()
self.contentView.setNeedsLayout()
self.contentView.layoutIfNeeded()
...
Now,
The labels are over the edge of the cell like this:
The gray separator divide the cell.
Essentially the height of the cell is not updated.

It would be more clear if you could share full implementation of cellForRowAt.
I faced similar issue where cell contents were overlapping. I was adding custom view to the cell as subview.
This happens because Table view re uses its cells. So one solution is to remove old label from the cell before you add new.
for content in cell.contentView.subviews {
content.removeFromSuperview()
}

Related

Programatic Autolayout UI Elements

I am trying to autolayout 3 UI elements in the order which I present in the image. A UITextfield, UIDatePicker and a UIButton in a UIView.
I am avoiding to use storyboard as I want to get a better understanding of programmatic constraints and eventually use animations for them.
So far I have got this with some constraints I have tried:
and here is the code for the one I am working on:
override func viewDidLoad() {
super.viewDidLoad()
picker.translatesAutoresizingMaskIntoConstraints = false
picker.backgroundColor = UIColor.red
button.translatesAutoresizingMaskIntoConstraints = false
button.backgroundColor = UIColor.blue
button.setTitle("Button", for: .normal)
self.view.addSubview(picker)
self.view.addSubview(button)
let PickercenterX = NSLayoutConstraint(item: self.picker, attribute: NSLayoutAttribute.centerX, relatedBy: NSLayoutRelation.equal, toItem: self.view, attribute: NSLayoutAttribute.centerX, multiplier: 1, constant: 0)
let PickercenterBottom = NSLayoutConstraint(item: self.picker, attribute: NSLayoutAttribute.bottom, relatedBy: NSLayoutRelation.equal, toItem: self.button, attribute: NSLayoutAttribute.bottom, multiplier: 1, constant: -30)
let Pickerheight = NSLayoutConstraint(item: self.picker, attribute: NSLayoutAttribute.height, relatedBy: NSLayoutRelation.equal, toItem: nil, attribute: NSLayoutAttribute.notAnAttribute, multiplier: 1, constant: 150)
let Pickerwidth = NSLayoutConstraint(item: self.picker, attribute: NSLayoutAttribute.width, relatedBy: .equal, toItem: self.view, attribute: NSLayoutAttribute.width, multiplier: 1, constant: -5)
// Centers it on the x axis. Pushes it it right if co constant has a value > 0
let centerX = NSLayoutConstraint(item: self.button, attribute: NSLayoutAttribute.centerX, relatedBy: NSLayoutRelation.equal, toItem: self.view, attribute: NSLayoutAttribute.centerX, multiplier: 1, constant: 0)
let centerBottom = NSLayoutConstraint(item: self.button, attribute: NSLayoutAttribute.bottom, relatedBy: NSLayoutRelation.equal, toItem: self.view, attribute: NSLayoutAttribute.bottom, multiplier: 1, constant: -15)
let height = NSLayoutConstraint(item: self.button, attribute: NSLayoutAttribute.height, relatedBy: NSLayoutRelation.equal, toItem: nil, attribute: NSLayoutAttribute.notAnAttribute, multiplier: 1, constant: 50)
let width = NSLayoutConstraint(item: self.button, attribute: NSLayoutAttribute.width, relatedBy: .equal, toItem: self.view, attribute: NSLayoutAttribute.width, multiplier: 1, constant: -15)
self.view.addConstraints([centerX, centerBottom, height, width, PickercenterX, PickercenterBottom, Pickerheight, Pickerwidth])
}
I am trying to work the button and date picker first before moving onto the textfield. How can I achieve this programmatically ?
Here lies the problem:-
You were setting the picker bottom to be equal to the button bottom with constant -30, although I know what were you trying to do, you were trying to give vertical space between picker and button. So it should be linked like, picker's bottom equal to button's top with constant -30.
Moreover you are missing out on activating the constraints by not adding isActive in the end.
Another way to activate all constraints at once is by using NSLayoutConstraint.activate() method
let PickercenterBottom = NSLayoutConstraint(item: self.picker, attribute: NSLayoutAttribute.bottom, relatedBy: NSLayoutRelation.equal, toItem: self.button, attribute: NSLayoutAttribute.top, multiplier: 1, constant: -30).isActive = true

Swift: how to center elements horizontally in vertical stackview

I have stackview with four elements that need to be centered horizontally:
let stackView = UIStackView()
stackView.axis = UILayoutConstraintAxis.vertical
stackView.distribution = UIStackViewDistribution.equalSpacing
stackView.alignment = UIStackViewAlignment.center
stackView.spacing = 5.0
stackView.addArrangedSubview(browseButton)
stackView.addArrangedSubview(orLabel)
stackView.addArrangedSubview(createButton)
stackView.addArrangedSubview(imageView)
let imageViewCenterXConstraint = NSLayoutConstraint(item: imageView, attribute: NSLayoutAttribute.centerX, relatedBy: NSLayoutRelation.equal, toItem: stackView, attribute: NSLayoutAttribute.centerX, multiplier: 1, constant: 0)
stackView.addConstraint(imageViewCenterXConstraint)
stackView.translatesAutoresizingMaskIntoConstraints = false
let stackViewCenterXConstraint = NSLayoutConstraint(item: stackView, attribute: NSLayoutAttribute.centerX, relatedBy: NSLayoutRelation.equal, toItem: wantMorePage!, attribute: NSLayoutAttribute.centerX, multiplier: 1, constant: 0)
let stackViewCenterYConstraint = NSLayoutConstraint(item: stackView, attribute: NSLayoutAttribute.centerY, relatedBy: NSLayoutRelation.equal, toItem: wantMorePage!, attribute: NSLayoutAttribute.centerY, multiplier: 1, constant: 0)
wantMorePage?.backgroundColor = UIColor.gray
wantMorePage!.addSubview(stackView)
wantMorePage!.addConstraint(stackViewCenterXConstraint)
wantMorePage!.addConstraint(stackViewCenterYConstraint)
self.scrollView.addSubview(wantMorePage!)
I added constraints and set alignment to center, but elements are still not centered (screenshot). What should I do to center them?
Replace:
wantMorePage!.addConstraint(stackViewCenterXConstraint)
wantMorePage!.addConstraint(stackViewCenterYConstraint)
With:
stackViewCenterXConstraint.isActive = true
stackViewCenterYConstraint.isActive = true
https://developer.apple.com/documentation/uikit/uiview/1622523-addconstraint
Also you need to add the subView before you can add constraints to it
Move this above isActive calls:
self.scrollView.addSubview(wantMorePage!)
Like so:
wantMorePage!.addSubview(stackView)
self.scrollView.addSubview(wantMorePage!)
let imageViewCenterXConstraint = NSLayoutConstraint(item: imageView, attribute: NSLayoutAttribute.centerX, relatedBy: NSLayoutRelation.equal, toItem: stackView, attribute: NSLayoutAttribute.centerX, multiplier: 1, constant: 0)
imageViewCenterXConstraint.isActive = true
let stackViewCenterXConstraint = NSLayoutConstraint(item: stackView, attribute: NSLayoutAttribute.centerX, relatedBy: NSLayoutRelation.equal, toItem: wantMorePage!, attribute: NSLayoutAttribute.centerX, multiplier: 1, constant: 0)
let stackViewCenterYConstraint = NSLayoutConstraint(item: stackView, attribute: NSLayoutAttribute.centerY, relatedBy: NSLayoutRelation.equal, toItem: wantMorePage!, attribute: NSLayoutAttribute.centerY, multiplier: 1, constant: 0)
stackViewCenterXConstraint.isActive = true
stackViewCenterYConstraint.isActive = true
Found what the issue was - the width of the view was incorrect, in my code wantMorePage was bigger than needed, I made it the same size as its superview and it fixed the issue.

Dynamically positioning UILabel to bottom of content view with Swift?

I was trying to position my dynamically created UILabels to my contentView's bottom. I'm using HTML Parser called Fuzi to catch HTML tags and creating UILabels based on them;
func stringFromHTML( _ string: String?)
{
do{
let doc = try HTMLDocument(string: string!, encoding: String.Encoding.utf8)
if let root = doc.body {
for element in root.children {
if element.tag == "h2" {
// Create new label
let label = UILabel()
label.text = element.stringValue
label.numberOfLines = 0
label.font = UIFont(name: "Avenir Next", size: 17)
label.textColor = UIColor.black
self.contentView.addSubview(label)
label.translatesAutoresizingMaskIntoConstraints = false
// Label constraints
let labelLeading = NSLayoutConstraint(item: label, attribute: .leading, relatedBy: .equal, toItem: contentView, attribute: .leading, multiplier: 1, constant: 20)
let labelTrailing = NSLayoutConstraint(item: label, attribute: .trailing, relatedBy: .equal, toItem: contentView, attribute: .trailing, multiplier: 1, constant: -20)
let labelTop = NSLayoutConstraint(item: label, attribute: .top, relatedBy: .equal, toItem: contentView, attribute: .top, multiplier: 1, constant: self.contentView.frame.origin.y)
self.contentView.addConstraints([labelLeading, labelTrailing, labelTop])
}
updateContentViewHeight()
}
}
}
catch{
print("html error\n",error)
}
}
func updateContentViewHeight(){
var totalContentHeight:CGFloat = 0.0
for i in self.contentView.subviews {
totalContentHeight += i.frame.height
}
let contentViewHeight:NSLayoutConstraint = NSLayoutConstraint(item: self.contentView, attribute: NSLayoutAttribute.height, relatedBy: NSLayoutRelation.equal, toItem: nil, attribute: NSLayoutAttribute.notAnAttribute, multiplier: 1, constant: totalContentHeight-29)
self.contentView.addConstraint(contentViewHeight)
}
I tried calling my stringFromHTML function in viewDidAppear. But it positions my UILabels to far far away (they don't seem).
I want to position my labels to the bottom of the last label, see the image below;
Anyone can help? Thanks in advance!
Change
let labelTop = NSLayoutConstraint(item: label, attribute: .top, relatedBy: .equal, toItem: contentView, attribute: .top, multiplier: 1, constant: self.contentView.frame.origin.y)
To
if label != nil {
let labelTop = NSLayoutConstraint(item: label, attribute: .top, relatedBy: .equal, toItem: label[index], attribute: .top, multiplier: 1, constant: 2)
} else {
let labelTop = NSLayoutConstraint(item: label, attribute: .top, relatedBy: .equal, toItem: contentView, attribute: .top, multiplier: 1, constant: self.contentView.frame.origin.y)
}
Because you are basically forcing each label to be at the same y position.

Issue using Auto Layout Constraints Programmatically

I have this situation:
I want this:
The black view is at X distance between violet view and if I tap I button in the violet view the violet view's height reduces(or increase) and I want that the black view is at the same X distance between violet view.
For this reason I write this code:
For button event that increase or reduce the violet's view I use this method:
#IBAction func tapOpenButton(){
self.altezza.constant = self.altezza.constant + 20
}
#IBAction func tapCloseButton(){
self.altezza.constant = self.altezza.constant - 20
}
where altezza is a NSLayoutConstraint
The both views are in a scrollview and I set constraint by code in this way:
var newView = blackView
var view = violetView
let horizontalConstraint = NSLayoutConstraint(item: newView, attribute: NSLayoutAttribute.centerX, relatedBy: NSLayoutRelation.equal, toItem: view, attribute: NSLayoutAttribute.centerX, multiplier: 1, constant: 0)
let verticalConstraint = NSLayoutConstraint(item: newView, attribute: NSLayoutAttribute.centerY, relatedBy: NSLayoutRelation.equal, toItem: view, attribute: NSLayoutAttribute.centerY, multiplier: 1, constant: 120)
let widthConstraint = NSLayoutConstraint(item: newView, attribute: NSLayoutAttribute.width, relatedBy: NSLayoutRelation.equal, toItem: nil, attribute: NSLayoutAttribute.notAnAttribute, multiplier: 1, constant: 100)
let heightConstraint = NSLayoutConstraint(item: newView, attribute: NSLayoutAttribute.height, relatedBy: NSLayoutRelation.equal, toItem: nil, attribute: NSLayoutAttribute.notAnAttribute, multiplier: 1, constant: 100)
self.altra.addConstraints([horizontalConstraint,verticalConstraint, widthConstraint, heightConstraint])
altra is the scrollview where both views are
My problem is that the distance between the violet view and black view is not the same when I reduce or increase violet's height , in particular , for example , when I increase the violet view's height , violet view goes under the black view.
I'm new in auto layout by code.
Can you help me?
Check if this works in place of your heightConstaint
let heightConstraint = NSLayoutConstraint(item: newView, attribute: NSLayoutAttribute.height, relatedBy: NSLayoutRelation.equal, toItem: view, attribute: NSLayoutAttribute.notAnAttribute, multiplier: 1, constant: 100)
Instead of pinning the views by centerY, set the top of the black view to the bottom of the purple one with a margin.
let verticalConstraint = NSLayoutConstraint(item: newView, attribute: NSLayoutAttribute.top, relatedBy: NSLayoutRelation.equal, toItem: view, attribute: NSLayoutAttribute.bottom, multiplier: 1, constant: 10)

Autolayout - UIView does not resize correctly to subview's size

I'm trying to programmatically create a UIView with a UILabel as a subview using autolayout.
Constraints
I used the following constraints on view:
CenterX of view to fastAttacksContainerView (superview).
Top constraint of constant 8 to superview.
Then created a label which is a subview of view and added constraints of constant 8 for Top, Bottom, Left, and Right to view.
Problem
The view only resizes to the frame of the label and does not account for the 4 constraints of constant 8 on all 4 sides. Which causes the label to be displayed partially outside the view.
let view = UIView()
view.backgroundColor = pokemon.secondaryColor
let label = UILabel()
fastAttacksContainerView.addSubview(view)
view.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(label)
label.translatesAutoresizingMaskIntoConstraints = false
label.text = fa
let gest = UITapGestureRecognizer(target: self, action: #selector(self.selectFastAttack))
view.addGestureRecognizer(gest)
fastAttackButtons.append(view)
fastAttackLables.append(label)
let top = NSLayoutConstraint(item: view, attribute: .Top, relatedBy: .Equal, toItem: fastAttacksContainerView, attribute: .Top, multiplier: 1, constant: 8)
let centerX = NSLayoutConstraint(item: view, attribute: .CenterX, relatedBy: .Equal, toItem: fastAttacksContainerView, attribute: .CenterX, multiplier: 1, constant: 0)
let labLeft = NSLayoutConstraint(item: label, attribute: .Left, relatedBy: .Equal, toItem: view, attribute: .Left, multiplier: 1, constant: 8)
let labTop = NSLayoutConstraint(item: label, attribute: .Top, relatedBy: .Equal, toItem: view, attribute: .Top, multiplier: 1, constant: 8)
let labRigth = NSLayoutConstraint(item: label, attribute: .Right, relatedBy: .Equal, toItem: view, attribute: .Right, multiplier: 1, constant: 8)
let labBottom = NSLayoutConstraint(item: label, attribute: .Bottom, relatedBy: .Equal, toItem: view, attribute: .Bottom, multiplier: 1, constant: 8)
view.addConstraints([labLeft, labTop, labRigth, labBottom])
fastAttacksContainerView.addConstraints([top, centerX])
Output
view has ambiguous height.
You should add constraint for height of view or distance from it to bottom of fastAttacksContainerView.
I managed to fix this problem by making a similar setup on storyboard and copying the constraints in.
Things that I changed:
Used Leading and Trailing instead of Left and Right layout attributes.
For the labLeft and labRight constraints, I interchanged item and toItem. (This seems like a bug to me. Can anyone verify, please?)
Code change:
let labLeft = NSLayoutConstraint(item: label, attribute: .Leading, relatedBy: .Equal, toItem: view, attribute: .Leading, multiplier: 1, constant: 8)
let labTop = NSLayoutConstraint(item: label, attribute: .Top, relatedBy: .Equal, toItem: view, attribute: .Top, multiplier: 1, constant: 8)
let labRigth = NSLayoutConstraint(item: view, attribute: .Trailing, relatedBy: .Equal, toItem: label, attribute: .Trailing, multiplier: 1, constant: 8)
let labBottom = NSLayoutConstraint(item: view, attribute: .Bottom, relatedBy: .Equal, toItem: label, attribute: .Bottom, multiplier: 1, constant: 8)

Resources