Subview (UILabel) doesn't respect the constraints when placed in superview (UITextView) - ios

I tried my own "placeholder in a UITextView" implementation.
My approach was this:
I create a UILabel in a UITextView subclass, and I set the constraints of the UILabel to match the size of its superview (UITextView).
This is the code where I create the UILabel and assign it to a class variable named placeholderLabel in awakeFromNib():
placeholderLabel = UILabel()
placeholderLabel.text = placeholder
placeholderLabel.numberOfLines = 0
placeholderLabel.lineBreakMode = .byWordWrapping
placeholderLabel.textAlignment = .left
The following code is where I add the UILabel as a subview, and I set the constraints, again in awakeFromNib():
placeholderLabel.translatesAutoresizingMaskIntoConstraints = false
placeholderLabel.leftAnchor.constraint(equalTo: leftAnchor, constant: textContainerInset.left + 4).isActive = true
placeholderLabel.topAnchor.constraint(equalTo: topAnchor, constant: textContainerInset.top).isActive = true
placeholderLabel.rightAnchor.constraint(equalTo: rightAnchor, constant: textContainerInset.right + 4).isActive = true
placeholderLabel.bottomAnchor.constraint(lessThanOrEqualTo: bottomAnchor, constant: textContainerInset.bottom).isActive = true
I also have a property where I set the placeholder's text, where I have a didSet observer, which sets placeholderLabel's text and then calls layoutIfNeeded() in order to have the constraints recalculated in case the UILabel extends to a second (or third, etc) row:
var placeholder: String = "" {
didSet {
placeholderLabel.text = placeholder
layoutIfNeeded()
}
}
The issue is that I have the following result:
The UILabel extends beyond it's superviews bounds (to the right), and it appears that it doesn't respect the constraints. I run the visual debugger which confirmed the same thing:
It seems that there is a width constraint which follows the UILabel's content width instead of following the constraint I have set in place (in this case it creates a width of 431 whereas the superview's width is 288).
Is there something that I miss?

First of all you have to use a negative value for the right constraint's constant (or - to use a positive value - switch the items placeholderLabel.rightAnchor / rightAnchor).
The real problem though is the fact that UITextView is a subclass of UIScrollView. In your case adding the UILabel with a large text as a subview and constraining its edges to the textview's edges results in the textview's contentSize to grow. The textview becomes horizontally scrollable.
Printing out the textview's contentSize before and after adding the label results in different values for the width (before: 335.0, after: 505.0).
Proof: https://www.dropbox.com/s/eogvl2c5r76c6cl/example.mov?dl=0
You could work around that problem by not creating the right but a width constraint instead:
// placeholderLabel.rightAnchor.constraint(equalTo: rightAnchor, constant: -(textContainerInset.right + 4)).isActive = true
placeholderLabel.widthAnchor.constraint(equalTo: widthAnchor, constant: -(textContainerInset.left + 4 + textContainerInset.right + 4)).isActive = true

with right should be minus
placeholderLabel.rightAnchor.constraint(equalTo: rightAnchor, constant: - textContainerInset.right - 4).isActive = true

Related

Constraints for dynamically size uilabel width programmatically

I have two labels set completely programmatically. Label two should always be up against the right edge of label one (with a small space between them). Label one has its width set to be equal to its content size unless it hits a max width. Visually:
|Label one| |Label two|
I need the following constraints:
Label one should resize width wise unless it hits a max size.
Label two should always be up against the right edge of label one
How do I set these constraints programmatically?
lessThanOrEqualToConstant for the widthAnchor should do the job.
let labelOne = UILabel()
labelOne.text = "label1"
let labelTwo = UILabel()
labelTwo.text = "label2"
labelOne.translatesAutoresizingMaskIntoConstraints = false
labelTwo.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(labelOne)
view.addSubview(labelTwo)
NSLayoutConstraint.activate([
labelOne.topAnchor.constraint(equalTo: view.topAnchor),
labelOne.leadingAnchor.constraint(equalTo: view.leadingAnchor),
labelOne.widthAnchor.constraint(lessThanOrEqualToConstant: 100),
labelTwo.leadingAnchor.constraint(equalToSystemSpacingAfter: labelOne.trailingAnchor, multiplier: 1),
labelTwo.topAnchor.constraint(equalTo: view.topAnchor)
])
I agree with #Kevvv answer but you have to also assign trailing constraint of labelTwo to view's trailing, because if labelTwo width will increase if content size is more.
just add one or more constraint
labeltwo.trailingAnchor.constraint(greaterThanOrEqualTo: view.trailingAnchor)

UIView in UIScrollView respects some constraints but not other

I need to add a containerView inside a UIScrollView, and then add multiple subviews in the containerView. For some reason, the containerView does not respect the top/bottom/left/rightAnchor constraints, but it works with width/height/centerX/centerYAnchor
NOTE: If the superview is a UIView instead of a UIScrollView, it works fine.
The project is 100% code based. Using Swift 4.1 and Xcode 9.4
This does not work
containerView.topAnchor.constraint(equalTo: scrollView.topAnchor, constant: 0).isActive = true
containerView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor, constant: 0).isActive = true
containerView.leftAnchor.constraint(equalTo: scrollView.leftAnchor, constant: 0).isActive = true
containerView.rightAnchor.constraint(equalTo: scrollView.rightAnchor, constant: 0).isActive = true
This works
containerView.widthAnchor.constraint(equalTo: scrollView.widthAnchor).isActive = true
containerView.heightAnchor.constraint(equalTo: scrollView.heightAnchor).isActive = true
containerView.centerXAnchor.constraint(equalTo: scrollView.centerXAnchor).isActive = true
containerView.centerYAnchor.constraint(equalTo: scrollView.centerYAnchor).isActive = true
In both cases the scrollView.constraints array includes 4 constraints total.
The interesting thing is that the printout of them is different. Some of the constraints that don't work (.top and .left) are printed using the Autolayout Visual Format Language. Also, note the (LTR) in the third one:
ScrollView [
<NSLayoutConstraint:V:|-(0)-[UIView] (active, names: '|':UIScrollView:)>,
<NSLayoutConstraint:UIView.bottom == UIScrollView.bottom (active)>,
<NSLayoutConstraint:H:|-(0)-[UIView](LTR) (active, names: '|':UIScrollView:)>,
<NSLayoutConstraint:UIView.right == UIScrollView.right (active)>]
The constraints that work are printed as follows:
ScrollView [
<NSLayoutConstraint:UIView.width == UIScrollView.width (active)>,
<NSLayoutConstraint:UIView.height == UIScrollView.height (active)>,
<NSLayoutConstraint:UIView.centerX == UIScrollView.centerX (active)>,
<NSLayoutConstraint:UIView.centerY == UIScrollView.centerY (active)>]
I researched StackOverflow and found a couple of questions like this, but they didn't really help me explain what the problem is (or the UIScrollView requirements for constraints).
Any ideas?
UIScrollView needs some contents in it to be scrolled. The view you are adding (inside scrollview), does not have size (height and width), so scroll view can't identify size of its content.
Add size for a view (inside scrollview) and it will work.
containerView.topAnchor.constraint(equalTo: scrollView.topAnchor, constant: 0).isActive = true
containerView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor, constant: 0).isActive = true
containerView.leftAnchor.constraint(equalTo: scrollView.leftAnchor, constant: 0).isActive = true
containerView.rightAnchor.constraint(equalTo: scrollView.rightAnchor, constant: 0).isActive = true
// Size constraints
containerView.widthAnchor.constraint(equalTo: scrollView.widthAnchor).isActive = true
containerView.heightAnchor.constraint(equalTo: scrollView.heightAnchor).isActive = true
// To check scrolling of container view; try this
containerView.widthAnchor.constraint(equalTo: scrollView.widthAnchor + 50.0).isActive = true
containerView.heightAnchor.constraint(equalTo: scrollView.heightAnchor + 50.0).isActive = true
It's because the UIScrollView requires it's contentSize to be set in some way. By anchoring the UIView's layout to the sides of the UIScrollView, auto layout still doesn't have an explicit idea of what contentSize of the UIScrollView.
Since the UIScrollView is probably anchored to some parent view, the height and width of the UIScrollView is already defined. By giving a UIView these constraints, auto layout can determine the size of the UIView and then use that size to set the contentSize of the UIScrollView.
Go through the following points in order to use scrollview in your application.
1. First add UIScrollview and give it constrain in view(left, right,width,height).[![enter image description here][1]][1]
2. Now each scrollview has content view which should be there , we cannot add our required views directly to UIScrollview.
3. Add view to scrollview(we name it content view) , give it top,bottom, left and right constrain. Apart from these we need to add height and width constrain to the content view.
4. If you want to have vertical scrollview then give width equal to scrollview and a proper height (like height constrain = 600)or greater than scrollview height.
5. If you want to have horizontal scrollview then give height equal to scrollview and width greater than actual width of scrollview.
Have a look at the constrain of content view added below

Profile Image cuts off in the UITableViewCell

I have a simple custom UITableViewCell which has profile image on the left, title and detailsLabel on the right. I have used Auto Layout constraints to set all the views on the screen. But the detailsLable text is short and profile image cuts off.
Let me know how to fix it. I could make the image small which is shorter than the height of title and label combined, but I want the big image.
// adding subviews
contentView.addSubview(profileImageView)
contentView.addSubview(nameLabel)
contentView.addSubview(jobTitleDetailedLabel)
// constraint for the views
profileImageView.topAnchor.constraint(equalTo:self.contentView.topAnchor, constant:10).isActive = true
profileImageView.leadingAnchor.constraint(equalTo:self.contentView.leadin gAnchor, constant:10).isActive = true
profileImageView.widthAnchor.constraint(equalToConstant:50).isActive = true
profileImageView.heightAnchor.constraint(equalToConstant:50).isActive = true
nameLabel.topAnchor.constraint(equalTo:self.contentView.topAnchor, constant:10).isActive = true
nameLabel.leadingAnchor.constraint(equalTo:self.profileImageView.trailingAnchor, constant:10).isActive = true
nameLabel.trailingAnchor.constraint(equalTo:self.contentView.trailingAnchor).isActive = true
jobTitleDetailedLabel.topAnchor.constraint(equalTo:self.nameLabel.bottomAnchor).isActive = true
jobTitleDetailedLabel.leadingAnchor.constraint(equalTo:self.profileImageView.trailingAnchor, constant:10).isActive = true
jobTitleDetailedLabel.trailingAnchor.constraint(equalTo:self.contentView.trailingAnchor).isActive = true
jobTitleDetailedLabel.bottomAnchor.constraint(equalTo:self.contentView.bottomAnchor, constant:-10).isActive = true
Just add jobTitleDetailedLabel height constrain to be greaterThanOrEqualToConstant profileImageView height + 10 for Margin
because if jobTitleDetailedLabel hight is less than Image hight it will make it small cell row
self.jobTitleDetailedLabel.heightAnchor.constraint(greaterThanOrEqualToConstant: 60).isActive = true
Make sure to constrain the cell's contentView's bottom anchor to be greaterThanOrEqualTo both the profileImageView's bottom anchor and the jobTitleDetailedLabel's bottom anchor. The cell will expand according to whichever is larger.
So you need to remove the jobTitleDetailedLabel's bottom anchor constraint and add:
contentView.bottomAnchor.constraint(greaterThanOrEqualTo: profileImageView.bottomAnchor, constant: 10).isActive = true
contentView.bottomAnchor.constraint(greaterThanOrEqualTo: jobTitleDetailedLabel.bottomAnchor, constant: 10).isActive = true
Also set the contentHuggingPriority of the nameLabel to high in order to ensure that the nameLabel keeps its intrinsic height (the height the text takes up).
nameLabel.setContentHuggingPriority(UILayoutPriority.defaultHigh, for: .vertical)
Finally set the cell's row height to UITableviewAutomaticDimension in the storyboard or through code - tableView.rowHeight = UITableViewAutomaticDimension

ios correct way to use constraintLessThanOrEqualToSystemSpacingAfter for trailingAnchor

I'd like to programmatically layout a UILabel that should fit the width of the screen, with the system spacing as left and right insets. Here's my code:
statusLabel.font = UIFont.systemFont(ofSize: UIFont.smallSystemFontSize)
statusLabel.numberOfLines = 0
statusLabel.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(statusLabel)
statusLabel.topAnchor.constraint(equalTo: otherView.bottomAnchor, constant: 0),
statusLabel.leadingAnchor.constraintEqualToSystemSpacingAfter(view.safeAreaLayoutGuide.leadingAnchor, multiplier: 1),
statusLabel.trailingAnchor.constraintLessThanOrEqualToSystemSpacingAfter(view.safeAreaLayoutGuide.trailingAnchor, multiplier: 1),
statusLabel.bottomAnchor.constraint(equalTo: someOtherView.topAnchor, constant: 0)
Here is the result:
the label is laid out using the system spacing as the left inset, as intended, but its trailingAnchor seems to be equal to the superview's trailingAnchor rather than adding a system spacing between the two.
I've tried using constraintEqualToSystemSpacingAfter and constraintGreaterThanOrEqualToSystemSpacingAfter but got the same results.
Any ideas on how to get the system spacing between the label and its superview's trailing anchors?
Reverse the order Like this
view.safeAreaLayoutGuide.trailingAnchor.constraintEqualToSystemSpacingAfter(statusLabel.trailingAnchor, multiplier: 1).isActive = true
view first & statusLabel next.

UIScrollView Subviews not expanding to fill width (Autolayout)

I'm using the following code to constrain a view to the left and right anchors of a parent UIScrollView.
Despite the right anchor and the left anchor being set to the ScrollView's left and right anchors, the view does not expand to fill the scrollview.
Note: The gray background in this image is the UIScrollView's background, so I know that's properly constrained to its parent view.
Code:
self.wtfView.translatesAutoresizingMaskIntoConstraints = false
self.wtfView.backgroundColor = UIColor.orange
self.wtfView.topAnchor.constraint(equalTo: self.passwordField.bottomAnchor, constant: 40.0).isActive = true
self.wtfView.leftAnchor.constraint(equalTo: self.containerView.leftAnchor, constant: 40.0).isActive = true
self.wtfView.rightAnchor.constraint(equalTo: self.containerView.rightAnchor, constant: 40.0).isActive = true
self.wtfView.heightAnchor.constraint(equalToConstant: 50.0).isActive = true
self.wtfView.bottomAnchor.constraint(equalTo: self.containerView.bottomAnchor, constant: 40.0).isActive = true
https://imgur.com/a/U88iW
Edit:
The following code works correctly, but I would prefer to use the left+right anchor technique to specify the width, and not at a width constraint. Shouldn't that be possible?
self.wtfView.translatesAutoresizingMaskIntoConstraints = false
self.wtfView.backgroundColor = UIColor.orange
self.wtfView.topAnchor.constraint(equalTo: self.passwordField.bottomAnchor, constant: 40.0).isActive = true
self.wtfView.leftAnchor.constraint(equalTo: self.containerView.leftAnchor, constant: 40.0).isActive = true
self.wtfView.widthAnchor.constraint(equalTo: self.containerView.widthAnchor, constant: -80.0).isActive = true //THE DIFFERENT ONE
self.wtfView.heightAnchor.constraint(equalToConstant: 50.0).isActive = true
self.wtfView.bottomAnchor.constraint(equalTo: self.containerView.bottomAnchor, constant: 040.0).isActive = true
The reason for this is that the contentView of the UIScrollView still doesn't know that you want it to take up the width of it's parentView.
You can fix this by adding the following constraint in iOS11:
self.containerView.contentLayoutGuide.widthAnchor.constraint(equalTo: self.view.widthAnchor).isActive = true
This says "Hey, I want you to lock the content Width to the width of the superview.
Pre iOS 11 you can simply constrain a subview to both the parent view's left and right anchors AND the content view's left and right anchors.
Like so:
self.wtfView.leftAnchor.constraint(equalTo: self.view.leftAnchor, constant: 40.0).isActive = true
self.wtfView.rightAnchor.constraint(equalTo: self.view.rightAnchor, constant: 40.0).isActive = true
Much like, Aleksei's recommendation you are now constraining the width to a rigid value ( the width of the parent view ), and the scrollview will use that to decide the width of the scrollview.
may be try to provide:
self.wtfView.widthAnchor.constraint(equalTo: self.containerView.widthAnchor, constant: -40.0).isActive = true

Resources