Autolayout Width Constants in UICollectionViewCell doesn't work well - ios

Below is a uicollectionviewcell, bar width must be according to number at right of it (vote count), event I log its width contants in console you can see it's calculated correctly, but on ui, two of them seems doesn't work well, if I scroll collectionview for several times it will be fixed, and then it will be fail again, how to set a view's contraint contant in a cell correctly?
code is too long but I want to summarize here
1) in viewcontroller's cellForItem
cell.setup()
cell.layoutIfNeeded()
return cell
2) in cell's setup()
setupBarWidths()
3) in setupBardWidhts()
resetAllWitdhs -> 0
calculate and set widths
is this correct way?

It depends upon how you are setting your constants for your subview inside uicollectionviewcell.
If your cell does not update properly constraints, possible solution is there
You must provide separate width constraint for your horizontal bar, and set it every time your reload cell.
"if I scroll collectionview for several times it will be fixed"
This problem occurs when you does not handle properly constraints changing, for example,you have "if - else " clause in your code, and you don't implement one branch.
Anyway, it's much easier to provide solution if you share your collectionView(_:cellForItemAt:)
UPDATED:
UICollectionView has many bugs internal, so one possible solution is to override UICollectionViewCell and insert the following method:
override var bounds: CGRect {
didSet {
contentView.frame = bounds
}
}
See this thread for more info.

Related

What is the best strategy for creating a UIScrollView with variable contents including a UICollectionView?

I've run into this problem from time to time and still haven't come up with a satisfying solution. I am trying to create a complex scrollable view. Consider the following example:
[UIView 1]
[UIView 2]
[UIView 3]
[UICollectionView]
[UIView 4]
In this case, any of the four UIViews can be hidden or visible. The UICollectionView should not scroll, rather it should show ALL of the cells. All five of these views should be in a UIScrollView that scrolls all of them.
I have yet to find the secret sauce to make this work.
Option 1] Lay stuff out completely manually in code. I know this will work, but it is very inconvenient as it impacts the subviews, which are complex in their own right.
Option 2] Each of the five views is in a UICollectionView cell, and UICollectionView handles sizing and scrolling. I likewise know this will work, but it seems like a lot of overhead to accomplish this.
Option 3] Wrap the five views in a UIStackView. This seems to be the most natural solution to this problem, but it relies on being able to do three things at the same time that I have yet to be able to accomplish:
Set the height of the stack view based on the height of the [visible] five views. I have tried to accomplish this with a height constraint linked from interface builder into the code.
Setup the collection view to have a variable height based on the number of cells it contains.
Set the UIScrollView content to have the same height as the stack view.
Up to this point, I haven't found the right set of constraints to make all of these things happen, and it is starting to make me think a UIScrollView with a UIStackView in it is not a viable solution to this problem. With everything I have tried, I either break the collection view's ability to display all of the cells or I cannot access the entire stack view within the scroll view (IE, it gets cut off before the bottom).
Thoughts on the correct strategy to solve such a problem? (thanks in advance)
I would definitely go with Option 3. I faced a similar but different hierarchy (a form with UIImageView, UIStackView, UIButton, and inside the stack view is two text fields, one text view, and a table view.
I gave the text view a height constraint greater than a certain value, then used a subclass of UITableView to make its height dynamic based on the content. I was able to create a similar subclass for UICollectionView that you might want to use to make your collection view height dynamic:
import UIKit
class DynamicHeightCollectionView: UICollectionView {
override var contentSize: CGSize {
didSet {
invalidateIntrinsicContentSize()
}
}
override var intrinsicContentSize: CGSize {
layoutIfNeeded()
return CGSize(width: UIView.noIntrinsicMetric, height: contentSize.height)
}
override func layoutSubviews() {
super.layoutSubviews()
}
}
then from storyboard, make sure you select the collection view, go to the Size inspector, and set the Intrinsic Size to Placeholder to avoid storyboard warnings.
This should solve all layout issues in your storyboard and hopefully work.

What actually defines the height of a UITableViewCell?

I'm working on an iOS App right now and I want to build a view controller that uses a UITableView to create new events in a calendar (very similarly to how iOS handles event creation in the system calendar, actually). The table view has two sections, the first section holding a date picker and the second section holding two custom cells for entering an event name and notes via a text field and a text view. After playing around with them I managed to force-set them to the right size, but in the process I realized that I don't actually understand how iOS calculates individual cell heights, especially in a table view with multiple sections and multiple custom cell classes. So far, I've found a number of things that seem to play a role:
Contents of a cell, e.g. a text field and its constraints
Hugging priority and compression resistance priority of a cells content
Settings for row height and view height in the size inspector of the cell itself:
Arrangement and Autolayout settings in the size inspector of the cell
Settings for the rowHeight and estimatedRowHeight properties of a UITableViewController
The more I look into it, the more complex and confusing it all gets. Maybe one of you can shed some light on this shady bit of Swift magic?
Basically, the rule is that if the table view's rowHeight is UITableView.automaticDimension, then as long as the estimatedRowHeight isn't 0, you'll get automatic row heights, meaning that the height is determined by the cell's autolayout constraints from the inside out.
The settings can be made in respect to the table view as a whole (in code or in the storyboard) or for a single cell using the height delegate method.
Add your constraints in the cell in right way.
don't use tableview "height for cell" delegate method.
use this in your viewDidLoad
self.tableView.estimatedRowHeight = 44.0
self.tableView.rowHeight = UITableView.automaticDimension
I would say that table view has a bit tricky.
Originally it needed to know size of cell before the cell was created.
The height of cell is defined by UITableViewDelegate optional function tableView(_:heightForRowAt:)
If this function is not defined (or delegate is set to nil) then it will take value of tableView.rowHeight
For performance reasons there was also added tableView(_:estimatedHeightForRowAt:) and tableView.estimatedRowHeight
The idea was not to calculate height of every cell during fast scrolling (such calculation may be costly) and use height that is good enough.
So that are the basics before constraints layout.
Then magic came. You can return UITableView.automaticDimension as height (by delegate method or by setting tableView.rowHeight). It will force tableView to calculate height from cells' constraints (note that constraints must define that height so very likely you want to set content hugging and resistance priority of every label, and you will encounter 'errors' in storyboard/xib).
Since that operation is costly you Apple forces you to specify estimated height by yourself. Also it's important to set that value to something that makes sense, otherwise things like programatically scroll won't work correctly.

Intrinsic content sized UICollectionView inside UITableView

Let me preface by saying that I've spent probably 100 hours over the past few months searching Google and StackOverflow trying to answer this question. I've followed many tutorials and have not been able to solve my problem. Every one is a major disappointment and is disheartening.
Expectation
Reality
Problem
I have a UITableView that contains a UIView. The UIView has auto layout constraints to pin it 15pt off the edges of the cell, and then I give that a shadow and rounded corners. Inside that UIView, I have a few labels, then a UICollectionView, and a stack view.
My UICollectionView keeps having a height of 0 and I can't figure out how to make it have intrinsic size. The only way I can seem to make it display properly is to force a specific height to it via auto layout inside the storyboard (which is what I did to take this screenshot). Unfortunately, forcing a height like this is not realistic for 2 reasons:
The height varies based on the width of the device. Since the images are squares, the height is loosely (but not exactly) half the width of the device.
If there are no images, the height of the collection view should be 0.
It really seems like it should be possible to use the intrinsic content size of the flow layout here.
Storyboard
Constraints
I've got about 15pt of space between each of the labels, collection view, and stack view (so, the vertical space). The collection view prototype cell has an image view in it. The image view has top, bottom, trailing and leading space all set to 0 to the collection view cell, and also has a 1:1 ratio.
Collection View Layout
To achieve the layout of one large image and 4 small images, I'm using SNCollectionViewLayout.
Data Source / Delegate
In my controller, in viewDidLoad, I've set the estimatedRowHeight of the UITableView to 400, and I've set the rowHeight to UITableView.automaticDimension.
In the tableView cellForRowAt, I dequeue a reusable custom UITableViewCell class, set various outlets on the table view cell, and call reloadData() on the collection view for the table view cell. In the storyboard, I've set the table view cell as the collection view's data source and delegate.
In the table view cell's awakeFromNib function, I've got it setting the corner radius, shadow, and using the following code for the flow layout on the collection view (which you can see in their example):
let snCollectionViewLayout = SNCollectionViewLayout()
snCollectionViewLayout.fixedDivisionCount = 4
snCollectionViewLayout.delegate = self
snCollectionViewLayout.itemSpacing = 10
collectionView.collectionViewLayout = snCollectionViewLayout
I've also implemented the optional SNCollectionViewLayoutDelegate protocol by writing the following function:
func scaleForItem(inCollectionView collectionView: UICollectionView, withLayout layout: UICollectionViewLayout, atIndexPath indexPath: IndexPath) -> UInt {
if indexPath.row == 0 || collectionView.numberOfItems(inSection: indexPath.section) <= 2 {
return 2
} else {
return 1
}
}
This function is why the first image is twice the height and width as the rest.
Research
In trying to figure this out, I've checked the collectionView.collectionViewLayout.collectionViewContentSize inside the table view cell's cellForRowAt and willDisplay methods. In either case, it's (0,0).
I've also tried checking it in the collection view cell's cellForItemAt and willDisplay. Those log lines don't even print unless I add a height constraint to the collection view to force it tall, but then I don't have a good way to bring the height back down to what it should be, and any time I've tried, I got conflicting constraint warnings and really wonky stretched views (such as the stack view being totally squished).
In testing, I've forced a height constraint that's large enough for the collection view to fit, and when that happens, it renders correctly and the collection view content size is correct, so I know it's capable of keeping track (which can be seen here).
Does anyone have any insight into this? I'd be happy to provide more code if you have any specifics on things to look into. I tried to give the full picture, while keeping this as short as possible (I know it's long).
I feel like the issue has something to do with the collection view being inside a table view cell.
Thanks in advance.
Keep a height constraint connection in your table view cell.
#IBOutlet weak var collectionViewHeightConstraint: NSLayoutConstraint!
And update the height constraint value programatically as per height calculations
self. collectionViewHeightConstraint.constant = 100 // set it to 0 if nothing to display

UICollectionView self-sizing-cell on iOS 8 will crash with UIDynamic flowLayout and repeating call to _updateVisibleCellsNow

I am trying to use the so called "self sizing cell" which means:
set estimatedItemSize on flowLayout
overide preferredLayoutAttributesFittingAttributes in cell class
Such as this: UICollectionView Self Sizing Cells with Auto Layout
And I need dynamic effect like this:http://www.teehanlax.com/blog/implementing-a-bouncy-uicollectionviewlayout-with-uikit-dynamics/
It works fine without UIDynamic, but I need UIDynamic. As what I see, it will call the prepareLayout and layoutAttributesForElementsInRect until die, there will be too many _updateVisibleCellsNow waiting in line.
I have no idea how to solve that, please help me in case u see. Or, if I am using those technologies in wrong way, please let me know.
Two things worked for me:
Make sure your collection view itself has layout constraints defined for placement within its superview.
I got this crash when the estimated size was larger than the final size. If I set the estimated size to a smaller value the crash stopped.
If any size or frame position changed, it will trigger all cells' preferredLayoutAttributesFittingAttributes until all cells' frames did not change.
The behavior flow is as the following:
1. Before self-sizing cell
2. Validated self-sizing cell again after other cells recalculated.
3. Did changed self-sizing cell
If second steps can't get stable all cells' position and size, it will become infinite loop.
Please reference my post here: UICollectionView Self Sizing Cells with Auto Layout

Detected a case where constraints ambiguously suggest a height of zero

After updating to Xcode 6.1 beta 2 when I run my app that contains tableview cells, the debug assistant says:
Warning once only: Detected a case where constraints ambiguously suggest a height of zero for a tableview cell's content view. We're considering the collapse unintentional and using standard height instead.
Before, when I used Xcode 5 on this project, I would get a few errors but those have gone away since I upgraded. I have no other errors or warnings now. I have already tried adjusting the sizes of all the tableview cells and also tried using standard height but I still get the same warning:
Warning once only: Detected a case where constraints ambiguously suggest a height of zero for a tableview cell's content view. We're considering the collapse unintentional and using standard height instead.
I have also read through all similar topics on this but none of their solutions help. When I test the app with the simulator, the app runs fine except the pictures that are supposed to be in the tableView cells aren't there.
You're encountering the side effect of a fantastic new feature in iOS8's Tableviews: Automatic Row Heights.
In iOS 7, you either had rows of a fixed size (set with tableView.rowHeight), or you'd write code to calculate the height of your cells and you'd return that in tableView:heightForRowAtIndexPath. Writing code for the calculation of a cell's height could be quite complex if you had numerous views in your cell and you had different heights to consider at different font sizes. Add in Dynamic Type and the process was a pain in the ass.
In iOS 8, you can still do the above, but now the height of the rows can be determined by iOS, provided that you've configured the content of your cell using Auto Layout. This is huge benefit for developers, because as the dynamic font size changes, or the user modifies the text size using Accessibility Settings, your UI can be adaptive to the new size. It also means if you have a UILabel that can have multiple rows of text, your cell can now grow to accommodate those when the cells needs to, and shrink when it does not, so there isn't any unnecessary whitespace.
The warning message you're seeing is telling you that there aren't enough constraints in your cell for Auto Layout to inform the tableview of the height of the cell.
To use dynamic cell height, which, along with the techniques already mentioned by other posters, will also get rid of this message, you need to ensure your cell has sufficient constraints to bind the UI items to the top and bottom of the cell. If you've used Auto Layout before, you are probably accustomed to setting Top + Leading constraints, but dynamic row height also requires bottom constraints.
The layout pass works like this, which occurs immediately before a cell is displayed on screen, in a just-in-time manner:
Dimensions for content with intrinsic sizes is calculated. This includes UILabels and UIImageViews, where their dimensions are based on the text or UIImages they contain, respectively. Both of these views will consider their width to be a known (because you've set constraints for trailing/leading edges, or you set explicit widths, or you used horizontal constraints that eventually reveal a width from side to side). Let's say a label has a paragraph of text ("number of lines" is set to 0 so it'll auto-wrap), it can only be 310 points across, so it's determined to be 120pt high at the current font size.
The UI is laid out according to your positioning constraints. There is a constraint at the bottom of the label that connects to the bottom margin of the cell. Since the label has grown to be 120 points tall, and since it's bound to the bottom of the cell by the constraint, it must push the cell "down" (increasing the height of the cell) to satisfy the constraint that says "bottom of the label is always standard distance from the bottom of the cell.
The error message you reported occurs if that bottom constraint is missing, in which case there is nothing to "push" the bottom of the cell away from the top of the cell, which is the ambiguity that's reported: with nothing to push the bottom from the top, the cell collapses. But Auto Layout detects that, too, and falls back to using the standard row height.
For what it's worth, and mostly to have a rounded answer, if you do implement iOS 8's Auto Layout-based dynamic row heights, you should implement tableView:estimatedHeightForRowAtIndexPath:. That estimate method can use rough values for your cells, and it'll be called when the table view is initially loaded. It helps UIKit draw things like the scrollbar, which can't be drawn unless the tableview knows how much content it can scroll through, but does't need totally accurate sizes, since it's just a scrollbar. This lets the calculation of the actual row height be deferred until the moment the cell is needed, which is less computationally intensive and lets your UITableView be presented quicker.
Three things have managed to silence this warning so far. You can pick up the most convenient for you. Nothing pretty though.
To set up default cell's height in viewDidLoad
self.tableView.rowHeight = 44;
Go to storyboard and change row height on your tableview to something different than 44.
To implement tableview's delegate method heightForRowAtIndexPath
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
return 44;
}
Weird.
To resolve this without a programmatic method, adjust the row height of the table view in the Size Inspector from the storyboard.
I had this problem after creating a custom UITableViewCell and adding my subviews to the cell instead of its contentView.
This is an autolayout issue. Make sure that your subviews have all the constraints. For me, the bottom constraint was missing for the Title Label in the cell. When I added that, the warning went away and everything showed up perfectly.
Just enable Self-Sizing Table View Cells
tableView.estimatedRowHeight = 85.0
tableView.rowHeight = UITableViewAutomaticDimension
& make sure you added constraints on all sides of UITableViewCell as-
Example Link 1
Example Link 2
If u are using static cell or dynamic cell ,simply add some row height to table view in inspector table and uncheck the automatic to the right side of row height ,that's it u will stop getting this warning .
I got this warning today. Here is what made it disappear for me(in interface builder)
1.Set the row height field for the table view to something other than 44
2 Set the row height field for the tableView cell to something other than 44
I did not have to make any changes in code
In my case, I was building the cell programmatically and kept getting this error.
I was adding the subviews and constraints in the UITableViewCell's init method like this:
addSubview(rankingLabel)
addConstraints(cellConstraints)
I solved the issue by adding them to the cell's contentView instead:
contentView.addSubview(rankingLabel)
contentView.addConstraints(cellConstraints)
Set the estimated row height to zero and the warning disappears:
If you have created a Custom tableViewCell for tableView, make sure you have given both bottom and top constraints to you cells,
you could also get this message if your subviews inside custom cells are aligned in center Y which wouldnt pop any error message but would mess up with identifying height of row for tableview in turn like in Image I have attached , here we have both top and bottom constraints
When you create a Custom Cell for tableView you must specific row height or top and bottom constraints for you custom cell's subviews inside cell (e.g. label in custom cell like in below image)
But if this doesn't work you can try setting row height for your cell instead of being automatic like in this image
But be sure if you turn that automatic tick off you have to adjust your row size for changes programmatically which could have been done automatically
I got this Warning today All I did is just added one extra line to my code
tableView.rowHeight = 200;
add this line of code inside the
func tableView(_ tableView: UITableView, numberOfRowsInSection section:Int) -> Int {
...
}
and the final code look like
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
tableView.rowHeight = 200;
...
}
this code will increase the table Row cell height to 200 the default height is 44
I too experienced this warning with moving to Xcode 6 GM. I was only getting the warning when I rotated the device back to its original position.
I am using custom UITableViewCells. The storyboard table view is set to my custom size (100.0 in my case). While the table cells render properly as they have in previous releases, I did not like warning message.
In addition to the above ideas, I added this
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
return 100.0;
}
Screen renders... responds to rotation and no more warning messages.
In xcode 6.0.1 I had removed this warnings specifying the row height using:
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
return 44.0;
}
You may also see this message if your only constraints are set to align all items vertically and you don't have/want a height specified for the cell. If you set a top/bottom constraint on the item the warning will disappear.
I had this problem when my labels and views in the custom tableViewCell were constrained to the customCell, not its Content View. When I cleared the constraints and connected them to cells Content View the problem was solved.
I had the same error message,
make sure all your outlets are valid like table view and tableview Constraints
I have also similar issue for custom tableview cell which has dynamic row height. Dynamic height wasn't reflected and got the same warning in console. The solution is Adding subviews to cell instead of contentView. BTW, I have created subviews programatically.
I have this issue on TableViewCells where the constraints are set on initialisation but where the cell's contents are loaded afterwards, this means the autolayout engine can't determine the height. The other solutions here don't work because I need the cell's height to be UITableView.automaticDimension.
I just added an extra constraint to the cell:
contentView.heightAnchor.constraint(equalToConstant: 44, priority: .defaultLow)
In the storyboard set the cell Row height field with the same value as Row height in tableView (both with the same value worked for me).
If you add heightForRowAtIndexPath function to your code it may induce a performance issue because it will be called for each cell so be careful.
If you are making a dynamic height calculation,
you should have all elements linked to each other in terms of constraints like top and bottom.
you should definitely have a bottom constraint that is linked to the element at the bottom of your cell
if you are extending your ViewController class with UITableView and also using navigation controller to show the screen then you dont need to perform segue with identifier this may cause an error of identifier ViewController, you can use pushViewController method to show the chat screen in order to get rid from this error so here is the code just paste it in to your UItableView delegate
let chatBox = ChatBoxViewController()
navigationController?.pushViewController(chatBox, animated: true)
just put the name of your viewcontroller which you want to show next and yeah done.
I have same error, due to this line this error was shown.
self.layer.backgroundColor = UIColor(white: 1, alpha: 0.2) as! CGColor
I just change the line as following to fix the error
self.layer.backgroundColor = UIColor(white: 1, alpha: 0.2).cgColor

Resources