UITableView inside UIStackView layout margins - ios

I currently have a UITableView embedded inside a UIStackView. I've currently set the StackView to have its own padding like so:
stackView.layoutMargins = UIEdgeInsets(top: 0, left: 8, bottom: 0, right: 8)
stackView.isLayoutMarginsRelativeArrangement = true
I've also set the stackview to be set on its edges, and it's also under a scrollview.
However, applying those settings, I'm getting errors on the UITableView constraints, telling me that the layout margins constraints are the issue.
(
"<NSLayoutConstraint:0x600000645f90 UIStackView:0x7fa9bbc32260.width == UIScrollView:0x7fa9bc019800.width (active)>",
"<NSLayoutConstraint:0x6000006441e0 H:|-(0)-[UIScrollView:0x7fa9bc019800](LTR) (active, names: '|':COVID_19_Compliance_Coach.DashboardView:0x7fa9bbc11340 )>",
"<NSLayoutConstraint:0x600000644320 UIScrollView:0x7fa9bc019800.right == COVID_19_Compliance_Coach.DashboardView:0x7fa9bbc11340.right (active)>",
"<NSLayoutConstraint:0x600000646f30 '_UITemporaryLayoutWidth' COVID_19_Compliance_Coach.DashboardView:0x7fa9bbc11340.width == 0 (active)>",
"<NSLayoutConstraint:0x600000647250 'UISV-canvas-connection' UILayoutGuide:0x600001c76a00'UIViewLayoutMarginsGuide'.leading == UITableView:0x7fa9bc0a2e00.leading (active)>",
"<NSLayoutConstraint:0x6000006472f0 'UISV-canvas-connection' UILayoutGuide:0x600001c76a00'UIViewLayoutMarginsGuide'.trailing == UITableView:0x7fa9bc0a2e00.trailing (active)>",
"<NSLayoutConstraint:0x6000006470c0 'UIView-leftMargin-guide-constraint' H:|-(8)-[UILayoutGuide:0x600001c76a00'UIViewLayoutMarginsGuide'](LTR) (active, names: '|':UIStackView:0x7fa9bbc32260 )>",
"<NSLayoutConstraint:0x600000647160 'UIView-rightMargin-guide-constraint' H:[UILayoutGuide:0x600001c76a00'UIViewLayoutMarginsGuide']-(8)-|(LTR) (active, names: '|':UIStackView:0x7fa9bbc32260 )>"
)
Will attempt to recover by breaking constraint
<NSLayoutConstraint:0x6000006472f0 'UISV-canvas-connection' UILayoutGuide:0x600001c76a00'UIViewLayoutMarginsGuide'.trailing == UITableView:0x7fa9bc0a2e00.trailing (active)>
I can't seem to figure out what the error is. If someone can point me to the right direction, that'd be extremely helpful, or if there's a different approach, that'd be helpful too.
Here's also a guideline of what the view hierarchy looks like:
UIScrollView
-> UIStackView
-> UITableView

I would strongly suggest that you don't add a table view inside a scroll view since that table view already has a scroll view.
That being said I would add the constraints this way
ScrollView:
Pin it to all corners of the superview
Stack View:
Pin it to the four corners of the the scroll view
Create a height constraint that is equal to the scroll view height
Create a width constraint that is equal to the scroll view width
Table View
Add the table view to the stack view
If you need to add a height constraint to the the table view you need to remove the height constraint of the stack view
If you add additional views to the stack view you will need to add a height constraint to them as well

I realized that I was overthinking on how the designs should be implemented. I've removed the UIStackView, and replaced it with a UITableView as the root instead.
So now the Hierarchy is more:
UITableView
-> SectionHeaderView
-> CustomCells (dequeuedReusable)
I decided to put those extra views onto the section header view at the top, and then used the cells instead. Thank you guys for the suggestion and clearer approach. Much appreciated.

Related

UITableView with dynamic row height shows Auto Layout constraint errors

I am creating a table view having cells whose height is determined by Auto Layout.
The table view cell has only one subview - An UIImageView which is pinned to the 4 sides of the table view cell.
The image view has a constraint to maintain aspect ratio of 4:3.
The following image shows the view hierarchy and constraints.
The height of the row of the table view is set to automatic as the following screenshot shows.
When the app is run, the table view cell looks as expected, but the following Auto Layout constraint errors are shown in the logs
(
"<NSLayoutConstraint:0x600001d804b0 AppName.NeverClearImageView:0x7f938ce140f0.width == 1.33333*AppName.NeverClearImageView:0x7f938ce140f0.height (active)>",
"<NSLayoutConstraint:0x600001d80460 AppName.NeverClearImageView:0x7f938ce140f0.bottom == UITableViewCellContentView:0x7f938ce04790.bottom (active)>",
"<NSLayoutConstraint:0x600001d80410 H:[AppName.NeverClearImageView:0x7f938ce140f0]-(0)-| (active, names: '|':UITableViewCellContentView:0x7f938ce04790 )>",
"<NSLayoutConstraint:0x600001d82a80 H:|-(0)-[AppName.NeverClearImageView:0x7f938ce140f0] (active, names: '|':UITableViewCellContentView:0x7f938ce04790 )>",
"<NSLayoutConstraint:0x600001d82bc0 V:|-(0)-[AppName.NeverClearImageView:0x7f938ce140f0] (active, names: '|':UITableViewCellContentView:0x7f938ce04790 )>",
"<NSLayoutConstraint:0x600001dbb930 'UIView-Encapsulated-Layout-Height' UITableViewCellContentView:0x7f938ce04790.height == 125.5 (active)>",
"<NSLayoutConstraint:0x600001db8dc0 'UIView-Encapsulated-Layout-Width' UITableViewCellContentView:0x7f938ce04790.width == 256 (active)>"
)
Will attempt to recover by breaking constraint
<NSLayoutConstraint:0x600001d804b0 AppName.NeverClearImageView:0x7f938ce140f0.width == 1.33333*AppName.NeverClearImageView:0x7f938ce140f0.height (active)>
The table view cell looks as expected. Can anyone point out how to resolve this error?
Note - I observe that the above error is not shown if the priority of the aspect ratio is set to 999 instead of 1000. Can anyone point out why this solves the issue?

Scrollview working by breaking constraint

I have a UIViewController containing a scrollView: the scrollView is pinned (0,0,0,0) to the superview.
The scrollView contains a Content View with the following constraints set:
Inside this view, I put a square image view: it is pinned this way and its height equals the width of its superview.
Anything I described until now is done manually in the storyboard.
Then, using a simple button (so, after viewDidLoad, viewDidLayoutSubviews, etc.), I execute the following function (that 375 is currently hard-coded, but it is the heigth of the image view frame on the device I’m using for testing purposes):
var startH = 0
for i in 0...10 {
let v = UIView(frame: CGRect(x: 0, y: 375+startH, width: 375, height: startH))
v.backgroundColor = .random
contentView.addSubview(v)
startH += 100
}
let scrollerLayoutGuide = scrollView.contentLayoutGuide
scrollerLayoutGuide.heightAnchor.constraint(equalToConstant: CGFloat(startH+375)).isActive = true
scrollView.contentSize = contentView.frame.size
This should (and actually it does) create 11 views under the imageView, inside the contentView of the scrollView.
Problem is when I execute this function I get the following error:
[LayoutConstraints] Unable to simultaneously satisfy constraints.
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:0x6000019d7d40 UIView:0x7fe66940aa30.height == UIScrollView:0x7fe66985b400.height (active)>",
"<NSLayoutConstraint:0x6000019d7d90 V:|-(0)-[UIView:0x7fe66940aa30] (active, names: '|':UIScrollView:0x7fe66985b400 )>",
"<NSLayoutConstraint:0x6000019d7de0 V:[UIView:0x7fe66940aa30]-(0)-| (active, names: '|':UIScrollView:0x7fe66985b400 )>",
"<NSLayoutConstraint:0x6000019e4690 UILayoutGuide:0x6000003c81c0'UIViewSafeAreaLayoutGuide'.bottom == UIScrollView:0x7fe66985b400.bottom (active)>",
"<NSLayoutConstraint:0x6000019e4730 UIScrollView:0x7fe66985b400.top == UILayoutGuide:0x6000003c81c0'UIViewSafeAreaLayoutGuide'.top (active)>",
"<NSLayoutConstraint:0x6000019e11d0 _UIScrollViewLayoutGuide:0x6000003ac1c0'UIScrollView-contentLayoutGuide'.height
== 1475 (active)>",
"<NSLayoutConstraint:0x6000019e9540 'UIView-Encapsulated-Layout-Height' UIView:0x7fe66950bdb0.height == 667 (active)>",
"<NSLayoutConstraint:0x6000019e45f0 'UIViewSafeAreaLayoutGuide-bottom' V:[UILayoutGuide:0x6000003c81c0'UIViewSafeAreaLayoutGuide']-(0)-| (active, names: '|':UIView:0x7fe66950bdb0 )>",
"<NSLayoutConstraint:0x6000019e4550 'UIViewSafeAreaLayoutGuide-top' V:|-(20)-[UILayoutGuide:0x6000003c81c0'UIViewSafeAreaLayoutGuide'] (active, names: '|':UIView:0x7fe66950bdb0 )>" )
Will attempt to recover by breaking constraint <NSLayoutConstraint:0x6000019d7de0 V:[UIView:0x7fe66940aa30]-(0)-| (active, names: '|':UIScrollView:0x7fe66985b400 )>
Problem is I can't understand what constraint is causing this issue and, most of all, why. Could you help me?
There is conflict between your constraints with contentView
1. There is Top, bottom, leading, trailing with the scrollview
2. Fixed height and fixed width constraint of contentView
These two will conflict each other as OS will not be sure which constraint to fulfil.
As a solution reduce the priority of height and width constraint so that the more priority is given to the constraint with respect to the scrollView.
For more details please follow below tutorial
How to configure a UIScrollView with Auto Layout

Unable to simultaneously satisfy constraints when update height constraints of imageView swift 4

I have a stack view that look like the figure below:
So I change the height of the image programmatically to make it fit the image that download from my server,if dont have image,the height constraints of image will set to be zero.
Here is my code to doing so:
let imageUrl = URL(string :imageString)
if let data = try? Data(contentsOf: imageUrl!)
{
guard let actualImage: UIImage = UIImage(data: data) else{
print("No image!")
ImageView.image = nil
defaultImageHeightContrainst = ImageHeightContrainst.constant
ImageHeightContrainst.constant = 0
layoutIfNeeded()
return
}
let imageHeight = actualImage.size.height * actualImage.scale
print("imageHeight = \(imageHeight)")
defaultImageHeightContrainst = ImageHeightContrainst.constant
ImageHeightContrainst.constant = imageHeight
layoutIfNeeded()
//here display the image to the imageview
ImageView.kf.setImage(with: imageUrl)
}
With the code above,the image height is scale according to actual image height which download from internet.If dont have image,the "image" part also set to 0 already.
This is what I expected,but now I face an error below whenever the "image" height become taller in order to fit the actual image height
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:0x6080002868b0 UIImageView:0x7f9cbae59240.height == 50 (active)>",
"<NSLayoutConstraint:0x608000286bd0 UIView:0x7f9cbae5ad80.height == 1 (active)>",
"<NSLayoutConstraint:0x608000286d10 UIImageView:0x7f9cbae5b440.height == 458 (active)>",
"<NSLayoutConstraint:0x608000286f90 UIView:0x7f9cbae5bb50.height == 1 (active)>",
"<NSLayoutConstraint:0x6080002871c0 V:|-(0)-[UILabel:0x7f9cbae5b160] (active, names: '|':UIStackView:0x7f9cbae5af60 )>",
"<NSLayoutConstraint:0x608000287350 V:[UILabel:0x7f9cbae5b160]-(10)-[UIImageView:0x7f9cbae5b440] (active)>",
"<NSLayoutConstraint:0x6080002873a0 V:[UIImageView:0x7f9cbae5b440]-(10)-[UIStackView:0x7f9cbae5b670] (active)>",
"<NSLayoutConstraint:0x608000287580 V:[UIStackView:0x7f9cbae5b670]-(10)-[UIView:0x7f9cbae5bb50] (active)>",
"<NSLayoutConstraint:0x6080002875d0 V:[UIStackView:0x7f9cbae5c0f0]-(0)-| (active, names: '|':UIStackView:0x7f9cbae5af60 )>",
"<NSLayoutConstraint:0x608000287670 V:[UIView:0x7f9cbae5bb50]-(10)-[UIStackView:0x7f9cbae5c0f0] (active)>",
"<NSLayoutConstraint:0x6080002877b0 V:|-(10)-[UIImageView:0x7f9cbae59240] (active, names: '|':UITableViewCellContentView:0x7f9cbae5a0c0 )>",
"<NSLayoutConstraint:0x6080002878f0 V:[UIImageView:0x7f9cbae59240]-(8)-[UIView:0x7f9cbae5ad80] (active)>",
"<NSLayoutConstraint:0x6080002879e0 V:[UIStackView:0x7f9cbae5af60]-(10)-| (active, names: '|':UITableViewCellContentView:0x7f9cbae5a0c0 )>",
"<NSLayoutConstraint:0x608000287a80 V:[UIView:0x7f9cbae5ad80]-(8)-[UIStackView:0x7f9cbae5af60] (active)>",
"<NSLayoutConstraint:0x608000288c00 'UISV-canvas-connection' UIStackView:0x7f9cbae5c0f0.top == _UILayoutSpacer:0x6080001cd5c0'UISV-alignment-spanner'.top (active)>",
"<NSLayoutConstraint:0x608000288ca0 'UISV-canvas-connection' V:[_UILayoutSpacer:0x6080001cd5c0'UISV-alignment-spanner']-(0)-| (active, names: '|':UIStackView:0x7f9cbae5c0f0 )>",
"<NSLayoutConstraint:0x608000289970 'UIView-Encapsulated-Layout-Height' UITableViewCellContentView:0x7f9cbae5a0c0.height == 476.5 (active)>"
)
Before asking,I already checked all my other constraints,which is all set correctly.I even disable all the code that can change the height of "image",once disable,there is no problem.The error only occurred when I intend to change the height.
I even tried to add ImageView.translatesAutoresizingMaskIntoConstraints = false before layoutIfNeeded(),but the error is still exist.
So what is the correct way to change the height of image in order to fit the actual image download from server?
Considering that the last constraint says
"<NSLayoutConstraint:0x608000289970 'UIView-Encapsulated-Layout-Height' UITableViewCellContentView:0x7f9cbae5a0c0.height == 476.5 (active)>"
I assume you are using stackView inside of a UITableViewCell to implement automatic height cells in a tableView. If my assumption is correct, then the problem is not with the stackView, nor with the imageView, but with the way UITableView works with UITableViewAutomaticDimension and Autolayout. If the layout works as you expect, and the warning is the only thing that bugs you, then read following.
Therefore it seems to me that this is a result of an known "bug" - there is a collision of the height set by the tableView and the height calculated by the autolayout. When rendering the cell, the tableView first applies the default height, calculates the autolayout height, and then use the latter - at least it seems so. See my question. The constraint mentioned above ('UIView-Encapsulated-Layout-Height') is the one applied by the UITableView that later goes away.
That means that the constraints you are using are probably OK. Just set one of the constraints defining height to priority = 999 (so that until it deactivates the default height constraint it won't cause any conflict). In the end, it will result in using your constraint anyway, so it will not cause any layout trouble.
E.g., if you constrain the stackView to fit the cell's contentView, set the stackView.bottomAnchor to contentView.bottomAnchor just with the priority set to 999. If you did the layout programmatically, this might be your solution:
let bottomConstraint = tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
bottomConstraint.priority = UILayoutPriority(rawValue: 999)
NSLayoutConstraint.activate([
// rest of the constraints
stackView.topAnchor.constraint(equalTo: contentView.topAnchor),
stackView.leftAnchor.constraint(equalTo: contentView.leftAnchor),
stackView.rightAnchor.constraint(equalTo: contentView.rightAnchor),
bottomConstraint,
])
If you do the layout in storyboards, just select appropriate constraint in the storyboards, and in the attributes inspector set its priority to 999 (for example):

How to add UITextField programmatically to UIScrollView without breaking constraints?

The case goes like this: I have a form embed in a UIScrollView, in the form there's a part where the user can add N numbers of textField, so if at first I had this:
UITextField - textField1
UIButton - Add
When Add is pressed I want to have this:
UITextField - textField1
UITextField - textField2
UIButton - Add
And although the text field is added, the constraints cannot be satisfied and therefore the UI breaks
The log for that:
[LayoutConstraints] Unable to simultaneously satisfy constraints.
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.
(
"<_UILayoutSupportConstraint:0x1c4c800a0 _UILayoutGuide:0x106ccf4e0.height == 64 (active)>",
"<_UILayoutSupportConstraint:0x1c4c80050 V:|-(0)-[_UILayoutGuide:0x106ccf4e0] (active, names: '|':UIView:0x106ccdc10 )>",
"<_UILayoutSupportConstraint:0x1c4c80ff0 _UILayoutGuide:0x106ccf6d0.height == 0 (active)>",
"<_UILayoutSupportConstraint:0x1c4c801e0 _UILayoutGuide:0x106ccf6d0.bottom == UIView:0x106ccdc10.bottom (active)>",
"<NSLayoutConstraint:0x1c4c80550 UIImageView:0x106cce6c0.height == 5 (active)>",
"<NSLayoutConstraint:0x1c4c80640 UILabel:0x106ccf070'Completa tu Perfil'.height == 24 (active)>",
"<NSLayoutConstraint:0x1c0c886b0 form-view.height == 579 (active, names: form-view:0x10ed307c0 )>",
"<NSLayoutConstraint:0x1c0c8a1e0 V:|-(0)-[form-view] (active, names: form-view:0x10ed307c0, '|':UIScrollView:0x107a37000 )>",
"<NSLayoutConstraint:0x1c0c8a320 V:[form-view]-(0)-[profiles-view] (active, names: profiles-view:0x106ccd430, form-view:0x10ed307c0 )>",
"<NSLayoutConstraint:0x1c4c80af0 V:[_UILayoutGuide:0x106ccf4e0]-(10)-[UILabel:0x106ccf070'Completa tu Perfil'] (active)>",
"<NSLayoutConstraint:0x1c4c80cd0 V:[UILabel:0x106ccf070'Completa tu Perfil']-(8)-[UIImageView:0x106cce6c0] (active)>",
"<NSLayoutConstraint:0x1c4c80d20 V:[UIImageView:0x106cce6c0]-(0)-[UIScrollView:0x107a37000] (active)>",
"<NSLayoutConstraint:0x1c4c80e10 UIImageView:0x106ccddf0.top == profiles-view.top (active, names: profiles-view:0x106ccd430 )>",
"<NSLayoutConstraint:0x1c4c80f00 V:[UIImageView:0x106ccddf0]-(0)-[_UILayoutGuide:0x106ccf6d0] (active)>",
"<NSLayoutConstraint:0x1c0c8bdb0 'UIView-Encapsulated-Layout-Height' UIView:0x106ccdc10.height == 667 (active)>"
)
Will attempt to recover by breaking constraint
<NSLayoutConstraint:0x1c0c886b0 form-view.height == 579 (active, names: form-view:0x10ed307c0 )>
So, since the height constraint was broken, AutoLayout arrange the elements following the margin constraints which allows the views broke that way.
That being said, the way I am adding the constraints is this:
func addModelButtonAction(_ sender: Any) { // an IBAction
addButton.isHighlighted = false
let last = modelTextFields.last!
let textField = last.createCopy() // extension method for creating a copy of the last textField
modelTextFieldsView.insertSubview(textField, belowSubview: last)
let growth = last.frame.height + 8 // the 8 represents the top spacing between textFields
formViewHeight.constant += growth // increasing height of the view where the form is embeded
modelTextFieldsHeight.constant += growth // increasing height of the view where the textFields are embeded
UIView.animate(withDuration: 0.5) {
self.view.layoutIfNeeded()
textField.frame.origin.y += growth // moving new textField below the previous one
}
modelTextFields.append(textField) // array of UITextField to keep track of the textFields in screen
}
And the view's alignment rectangles:
And the view's hierarchy:
What can I do to make this work as expected? I appreciate your help in this, because I've been trying to figure this out without any success.
Thanks in advance!
You have a couple of options to solve this layout issue:
Personally, I might try embedding these views in a UIStackView. The stack view will work to manage the vertical layout for you, allowing you to insert and remove subviews as needed. UIStackView is especially easy to work with from Interface Builder if you use Storyboards.
Alternatively, you could use a UITableView for this layout; it would manage the vertical flow on your behalf, abstracting much of the layout headache from you, though you'd need to manage the data source and cells yourself.
You could probably get this layout to work without either of these by carefully managing the priority of different constraints, allowing some to be broken as the layout changes, but this can be a tedious chore.

How to resize UICollectionView item to a square size with the height of the collection view?

A collection view is placed in the upper part of the screen. The collection view is horizontal and has one row.
The row can contain multiple collection view cells.
Each cell inside has UIImageView. The UIImageView is meant to contain an UIImage.
The UIImage is a photo. A photo might be of a different size, but inside the collection view cell it has to be squared with aspect ratio 1:1.
The problem is making the collection view cell to be of square size, where the side of the square should be equal to the height of the collectionView. And the UIImageView should take whole space of the collection view cell making the UIImage squared.
The UIImageView has constraints set ratio 1:1; and the top, leading, and bottom of collection cell.
The collection cell has its size calculated programatically:
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let size = collectionView.bounds.size.height
return CGSize(width: size, height: size)
}
I get the following error in the debugger:
[LayoutConstraints] Unable to simultaneously satisfy constraints.
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.
(Note: If you're seeing NSAutoresizingMaskLayoutConstraints that you don't understand, refer to the documentation for the UIView property translatesAutoresizingMaskIntoConstraints)
(
"<NSAutoresizingMaskLayoutConstraint:0x6180000968f0 h=--& v=--& UIView:0x7f8c3f61e940.width == 217 (active)>",
"<NSAutoresizingMaskLayoutConstraint:0x618000096990 h=--& v=--& UIView:0x7f8c3f61e940.height == 100 (active)>",
"<NSLayoutConstraint:0x618000096030 UIImageView:0x7f8c3f61eae0.width == UIImageView:0x7f8c3f61eae0.height (active)>",
"<NSLayoutConstraint:0x618000096120 H:[UIImageView:0x7f8c3f61eae0]-(0)-| (active, names: '|':UIView:0x7f8c3f61e940 )>",
"<NSLayoutConstraint:0x618000096170 V:[UIImageView:0x7f8c3f61eae0]-(0)-| (active, names: '|':UIView:0x7f8c3f61e940 )>",
"<NSLayoutConstraint:0x6180000961c0 H:|-(0)-[UIImageView:0x7f8c3f61eae0] (active, names: '|':UIView:0x7f8c3f61e940 )>",
"<NSLayoutConstraint:0x618000096210 V:|-(0)-[UIImageView:0x7f8c3f61eae0] (active, names: '|':UIView:0x7f8c3f61e940 )>"
)
Will attempt to recover by breaking constraint
<NSLayoutConstraint:0x618000096030 UIImageView:0x7f8c3f61eae0.width == UIImageView:0x7f8c3f61eae0.height (active)>
Make a symbolic breakpoint at UIViewAlertForUnsatisfiableConstraints to catch this in the debugger.
The methods in the UIConstraintBasedLayoutDebugging category on UIView listed in <UIKit/UIView.h> may also be helpful.
How to make it work?
You don't need both the aspect ratio constraint and the right constraint. One is enough. But, logically if you had both they shouldn't conflict if your size calculations is correct.
Your calculation looks good. Therefore, the thing in mind that could effect your calculations is the (header size, footer size, spacing insets)
Another thing that could affect this is reloading collection view data
on viewDidLoad before the uiviews complete layouting.

Resources