I started to get this constraints error:
"<NSLayoutConstraint:0x6000014419a0 V:|-(20)-[UIImageView:0x7f809779fae0] (active, names: '|':mChat.ChatCell:0x7f8097534600'ChatCell' )>",
"<NSLayoutConstraint:0x600001441b30 UIImageView:0x7f809779fae0.bottom == mChat.ChatCell:0x7f8097534600'ChatCell'.bottom - 20 (active)>",
"<NSLayoutConstraint:0x600001440eb0 UIImageView:0x7f809779fae0.height == 150 (active)>",
"<NSLayoutConstraint:0x600001440550 'UIView-Encapsulated-Layout-Height' mChat.ChatCell:0x7f8097534600'ChatCell'.height == 60.5 (active)>"
The problem appears when the UITableView has at least two images in it, then I get these constraints errors. Or when I add another image to the UITableView.
Here are my constraints:
func setupMediaMessage(){
mediaMessage.translatesAutoresizingMaskIntoConstraints = false
mediaMessage.contentMode = .scaleAspectFill
mediaMessage.backgroundColor = .lightGray
mediaMessage.layer.cornerRadius = 16
mediaMessage.layer.masksToBounds = true
let constraints = [
mediaMessage.topAnchor.constraint(equalTo: topAnchor, constant: 20),
mediaMessage.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -20),
mediaMessage.widthAnchor.constraint(equalToConstant: 150),
mediaMessage.heightAnchor.constraint(equalToConstant: 150)
]
outcomingConstraint = mediaMessage.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 24)
incomingConstraint = mediaMessage.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -24)
NSLayoutConstraint.activate(constraints)
}
Don't worry about the trailing and leading constraints, I'm just checking if those images are incoming or outcoming.
My Github repo: https://github.com/realpaliy/mChat/blob/master/mChat/Controllers/Chats/ChatCell.swift. So, the UITableView looks good, but why do I get those errors?
If the layout looks correct, the conflicts are likely due to the internal auto-layout engine, and the order in which the constraints are evaluated.
Try this -- it should get rid of the warnings, without changing the end result:
let bAnchor = mediaMessage.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -20)
bAnchor.priority = UILayoutPriority(rawValue: 999)
let constraints = [
mediaMessage.topAnchor.constraint(equalTo: topAnchor, constant: 20),
//mediaMessage.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -20),
bAnchor,
mediaMessage.widthAnchor.constraint(equalToConstant: 150),
mediaMessage.heightAnchor.constraint(equalToConstant: 150)
]
EDIT
Note: I do not work for Apple - these are simply my observations:
When a cell is instantiated and / or dequeued, auto-layout uses the current row height to lay out the cell contents. It then uses the constraints on the cell contents to determine the actual row height. This can cause conflict warnings if the row is not tall enough for the generated height. By changing the Priority on the bottom-most constraint - or, I believe, on explicit element height constraints - to 999, that allows auto-layout to first break the constraint and then re-enforce it... without generating debug warnings.
You should (may?) also be able to eliminate the warnings by setting a big enough value for tableView.estimatedRowHeight.
Related
I am laying out a view programatically in Swift for iOS, but struggling to get my constraint quite how I want it. This is what I currently have:
NSLayoutConstraint.activate([
Logo.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: 30),
Logo.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: -30),
Logo.heightAnchor.constraint(equalToConstant: 55),
Logo.topAnchor.constraint(equalTo: label.bottomAnchor, constant: 150),
])
This is fine on large screens but as the screen gets smaller I want to close the space between the Logo and the label. Currently this is set to a fixed constant of 150. What I would like to do is use a multiplier here that is based on the view height (or something similar) but I can not figure that out. How should I define the constraint to do this? Thanks!
You can try
/* Play with percent as you need also you can check current
device type iphone/ipad and set it accordingly */
let height = UIScreen.main.bounds.height * 0.25
logo.topAnchor.constraint(equalTo: label.bottomAnchor, constant: height),
I have a mini game in my project where you have to guess a flag of a country, I don't use Interface Builder, all UI is written in code. When user taps one of the flags I load new set of countries to guess, set their flags to buttons and since flags are different in sizes from previous ones I need to adjust UIButton constraints so I call my function setupConstraints() which looks like that:
func setupConstraints() {
NSLayoutConstraint.activate([
countryNameLabel.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 10),
countryNameLabel.centerXAnchor.constraint(equalTo: view.centerXAnchor),
countryNameLabel.widthAnchor.constraint(equalTo: view.widthAnchor, constant: -20),
countryNameLabel.heightAnchor.constraint(equalToConstant: 30),
firstCountryButton.topAnchor.constraint(equalTo: countryNameLabel.bottomAnchor, constant: 35),
firstCountryButton.centerXAnchor.constraint(equalTo: view.centerXAnchor),
firstCountryButton.heightAnchor.constraint(equalToConstant: 120),
firstCountryButton.widthAnchor.constraint(equalTo: firstCountryButton.heightAnchor, multiplier: flagImages[0].getAspectRatio()),
secondCountryButton.topAnchor.constraint(equalTo: firstCountryButton.bottomAnchor, constant: 25),
secondCountryButton.centerXAnchor.constraint(equalTo: view.centerXAnchor),
secondCountryButton.heightAnchor.constraint(equalToConstant: 120),
secondCountryButton.widthAnchor.constraint(equalTo: secondCountryButton.heightAnchor, multiplier: flagImages[1].getAspectRatio()),
thirdCountryButton.topAnchor.constraint(equalTo: secondCountryButton.bottomAnchor, constant: 25),
thirdCountryButton.centerXAnchor.constraint(equalTo: view.centerXAnchor),
thirdCountryButton.heightAnchor.constraint(equalToConstant: 120),
thirdCountryButton.widthAnchor.constraint(equalTo: thirdCountryButton.heightAnchor, multiplier: flagImages[2].getAspectRatio()),
])
}
This function works fine when I initially setup my View Controller but when I call it to update constraints I get in log following:
Probably at least one of the constraints in the following list is one you don't want.
Try this:
(1) look at each constraint and try to figure out which you don't expect;
(2) find the code that added the unwanted constraint or constraints and fix it.
(
"<NSLayoutConstraint:0x280d92940 UIButton:0x102315180.height == 120 (active)>",
"<NSLayoutConstraint:0x280d92990 UIButton:0x102315180.width == 1.5*UIButton:0x102315180.height (active)>",
"<NSLayoutConstraint:0x280db2b70 UIButton:0x102315180.width == 2*UIButton:0x102315180.height (active)>"
)
Will attempt to recover by breaking constraint
<NSLayoutConstraint:0x280db2b70 UIButton:0x102315180.width == 2*UIButton:0x102315180.height (active)>
I see that it's trying to set width twice but I don't know why. I'm not even sure if my approach to call this function every time to setup constraints is the right one.
Here's one way to handle it:
Create a struct ButtonSizeConstraints:
struct ButtonSizeConstraints {
let widthConStraint: NSLayoutConstraint
let heightConstraint: NSLayoutConstraint
}
Give your view an array of ButtonSizeConstraints, once for each button. (You could also have separate variables firstButtonSizeConstraints, secondButtonSizeConstraints, etc, but by making it an array you can loop through it.)
In your setupConstraints function, put those constraints into local vars, add them to your array of ButtonSizeConstraints, and activate them outside of the code you posted.
Then, when you load a new image into one or more of your buttons, fetch that button's ButtonSizeConstraints, update that constraint's constant value, and call the parent view's layoutIfNeeded() method.
I'm currently working with a vertical UIStackView in which each view has a UILabel to be displayed on the left and a UIButton to be displayed on the right. The leading constraint for the label should be different than the trailing constraint for the button and the entire view should take up the width of the screen. The alignment for the stackview is fill and the distribution is fillEqually.
The problem I'm running into is that Auto Layout doesn't seem to be respecting the trailing constraint that I'm trying to set for the button on fill alignment (or any other alignment, for that matter). No matter what I put for it, the system sets it to 20. I've tried center, leading, and trailing alignments but they don't work for what I'm trying to do. Is it possible to have the degree of horizontal layout control I'd like with a vertical stackview?
My constraints (radioButton trailing constraint constant is set to 0):
private func sharedInitialization() {
translatesAutoresizingMaskIntoConstraints = false
textLabel.translatesAutoresizingMaskIntoConstraints = false
radioButton.translatesAutoresizingMaskIntoConstraints = false
addSubview(textLabel)
addSubview(radioButton)
NSLayoutConstraint.activate([
radioButton.widthAnchor.constraint(equalToConstant: 30),
radioButton.heightAnchor.constraint(equalToConstant: 30),
radioButton.trailingAnchor.constraint(equalTo: trailingAnchor, constant: 0),
radioButton.centerYAnchor.constraint(equalTo: centerYAnchor),
textLabel.trailingAnchor.constraint(equalTo: radioButton.leadingAnchor, constant: -10),
textLabel.topAnchor.constraint(equalTo: topAnchor, constant: 22),
textLabel.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 10),
bottomAnchor.constraint(equalTo: textLabel.bottomAnchor, constant: 22)])
}
Visual debugger:
I've looked at Prevent vertical UIStackView from stretching a subview? but that doesn't seem quite applicable to my situation. Thanks in advance for any help!
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
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.