I created a tableview cell using stactView with multiple labels so they can automatically adjust the dynamic content of the label. And it is perfectly working in the tableview.
But now I'm in need to use the same cell in a view with dynamic content.
I'm calling the cell is like
NSArray * arr =[[NSBundle mainBundle] loadNibNamed:#"StaticCalloutView" owner:nil options:nil];
StaticCalloutView * staticView = [arr firstObject];
staticView.imageView.image = [UIImage imageNamed:#"permit-pin.png"];
staticView.lblTitle.text = (NSString *) [feature attributeForKey:ATTRIBUTE_DESCRIPTION];
staticView.lblDesc.text = (NSString *) [feature attributeForKey:ATTRIBUTE_LOCATION];
But the problem here is it is not resizing itself in this case.
For the tableview i use the AutomaticDimension but what should i use in this case so that the view I'm calling will expand or compress accrding to the data pass to the labels. ??
One thing I noticed during debugging when I remove the bottom constraint of the topmost stackView then the stackview resizes itself with the content but the view in which it is showing didnt.
You need to override in your StaticCalloutView class the intrinsicContentSize variable this variable allow the view autoresize according the content or whatever criteria you use, there are some UIControls that have this method implemented by default UILabel and UIButton are two examples
This is an example of intrinsic contentSize implementation, note that this CustomAutoresizableView will grow according to the number of items in arrayOfValues multiplied by 40 in height
class CustomAutoresizableView: UIView {
var arrayOfValues : [Int] = []{
willSet
{
self.invalidateIntrinsicContentSize()
}
}
override var intrinsicContentSize: CGSize
{
get
{
return CGSize(width: self.frame.width, height: CGFloat(self.arrayOfValues.count * 40))
}
}
}
The Apple documentation says
For all alignments except the fill alignment, the stack view uses each
arranged view’s intrinsicContentSize property when calculating its
size perpendicular to the stack’s axis
The reason why it is not expanding is because of ambiguity in the width for the label. UILabel needs to know width to properly layout in multiple lines. Check and try these options
Set numberOfLines property of UILabel to 0.
Set lineBreak property to WordWrap
Set the width constraint for the detail
label or the view containing the labels.
Related
I'm building a list view with self-sizing in my app using UICollectionView. Getting UICollectionViewFlowLayout to do a vertical list layout is a pain, so I'm writing my own UICollectionViewLayout subclass to do it.
My cells look a lot like regular table view cells - an image view on the left, a couple of labels vertically stacked in the center, and an accessory view on the right. The labels can wrap to a few lines and grow in font size according to the system font size setting, which is why they need to be self-sizing. Constraints are sensible - left to image view, image view to label, label to accessory, accessory to right, label to top and bottom.
To size my cells via preferredLayoutAttributesFittingAttributes, I need to get the desired height of the cell given the width of the collection view. I'd expect to use systemLayoutSizeFittingSize:horizontalPriority:verticalPriority: for this, passing a size like {desiredWidth, crazyLargeHeight}, UILayoutPriorityRequired for horizontal priority, and 1 for vertical priority. But the size I get back from systemLayoutFittingSize isn't sensible. The width is some previous width value from the cell (which doesn't necessarily match self.bounds.size.width or the passed-in layoutAttributes' size.width), and a height that works with that width. I also have tried passing a small height instead of a large one, and it still doesn't return the size it actually needs.
So how do I get the preferred height for the cell given a width value?
Sample code:
-(UICollectionViewLayoutAttributes *)preferredLayoutAttributesFittingAttributes:(UICollectionViewLayoutAttributes *)layoutAttributes
{
UICollectionViewLayoutAttributes *result = [super preferredLayoutAttributesFittingAttributes:layoutAttributes];
CGSize exampleSize = CGSizeMake(layoutAttributes.size.width, 20);
CGSize size = [self systemLayoutSizeFittingSize:exampleSize withHorizontalFittingPriority:UILayoutPriorityRequired verticalFittingPriority:1];
result.size = size;
return result;
}
Turns out if you ask the cell's content view for a size instead of the cell itself, it works. And you have to give a sample height that is smaller than will be needed.
-(UICollectionViewLayoutAttributes *)preferredLayoutAttributesFittingAttributes:(UICollectionViewLayoutAttributes *)layoutAttributes
{
UICollectionViewLayoutAttributes *result = [super preferredLayoutAttributesFittingAttributes:layoutAttributes];
CGSize exampleSize = CGSizeMake(layoutAttributes.size.width, 20); //magic number for example purposes...my cell will definitely be taller
CGSize size = [self.contentView systemLayoutSizeFittingSize:exampleSize withHorizontalFittingPriority:UILayoutPriorityRequired verticalFittingPriority:1];
result.size = size;
return result;
}
I'm guessing that the content view isn't pinned to the cell's bounds the way I expected. And this exact implementation will likely fall down if the content view is inset for some reason. But the key to solving this was the fact that having the cell contents pinned to the cell's content view does not mean that the cell itself will compute a size as you expect.
So I'm trying to use the built-in UITableViewCell styles - specifically UITableViewCellStyleSubtitle - with a (single) line textLabel but multiline detailTextLabel. But the (auto) calculated cell height is consistently too short, and appears to ignore that there is more than 1 line of detail.
I've tried using numberOfLines=0, estimatedRowHeight, UITableViewAutomaticDimension, preferredMaxWidthLayout, etc, but in all the permutations the behavior - indeed for all the UITableViewCell styles - is it appears the UITableViewAutomaticDimension cell height calculation will correctly account for a multiline textLabel (yay!), but incorrectly assumes the detailTextlabel is at most single line (nay!). Consequently, cells with a multiline detailTextLabel are too short, and hence the cell content spills over the top and bottom of the cell.
I've posted a quick test app showing this behavior on GitHub here. Adding additional lines of text is fine - all the cell styles appropriately increase in height to accommodate - but adding additional lines of detail does nothing to change the cell height, and quickly causes the content to spill over; the text+detail are themselves laid out correctly, and together centered correctly over the middle of the cell (so in that sense layoutSubviews is working correctly), but the overall cell height itself is unchanged.
It almost seems like there are no actual top & bottom constraints between the cell.contentView and the labels, and instead the cell height is being calculated directly from the height of the (possibly multi-line) textLabel and (only single-line) detailTextLabel, and then everything is centered over the middle of the cell... Again, multiline textLabel is fine, and I'm doing nothing different between the textLabel and detailTextLabel, but only the former (correctly) adjusts the cell height.
So my question is, if it is possible to use the built-in UITableViewCell styles to reliably display multiline detailTextLabels, or is it simply not possible and you need to create a custom subclass instead? [or, almost equivalently, without having to override layoutSubviews in a subclass and rewire all the constraints manually].
[4 May 2016] Conclusion: as of iOS9 multi-line detailTextLabels dont work as expected with UITableViewAutomaticDimension; the cell will be consistently too short and the text/detail will spill over the top and bottom. Either you must manually compute the correct cell height yourself, or create and layout your own equivalent custom UITableViewCell subclass, or (see my answer below) subclass UITableViewCell and fix systemLayoutSizeFittingSize:withHorizontalFittingPriority:verticalFittingPriority: to return the correct height [recommended]
Further investigations (see UITableViewCellTest) indicate that when UITableViewAutomaticDimension is enabled the system calls -systemLayoutSizeFittingSize:withHorizontalFittingPriority:verticalFittingPriority: to calculate the cell height, and that this pretty much ignores the height of the detailTextLabel in its computation (bug !?). As a result, for UITableViewCellStyleSubtitle the cell height is always going to be too short [a single-line detailTextLabel may not quite spill over the cell, but that's only because of the existing top and bottom margins], and for UITableViewCellStyleValue1 or UITableViewCellStyleValue2 the height will be too short whenever the detailTextLabel is taller (eg more lines) than the textLabel. This is all a moot point for UITableViewCellStyleDefault which has no detailTextLabel.
My solution was to subclass and fix with:
- (CGSize)systemLayoutSizeFittingSize:(CGSize)targetSize
withHorizontalFittingPriority:(UILayoutPriority)horizontalFittingPriority
verticalFittingPriority:(UILayoutPriority)verticalFittingPriority
{
// Bug finally fixed in iOS 11
if ([UIDevice.currentDevice.systemVersion compare:#"11" options:NSNumericSearch] != NSOrderedAscending) {
return [super systemLayoutSizeFittingSize:targetSize
withHorizontalFittingPriority:horizontalFittingPriority
verticalFittingPriority:verticalFittingPriority];
}
[self layoutIfNeeded];
CGSize size = [super systemLayoutSizeFittingSize:targetSize
withHorizontalFittingPriority:horizontalFittingPriority
verticalFittingPriority:verticalFittingPriority];
CGFloat detailHeight = CGRectGetHeight(self.detailTextLabel.frame);
if (detailHeight) { // if no detailTextLabel (eg style = Default) then no adjustment necessary
// Determine UITableViewCellStyle by looking at textLabel vs detailTextLabel layout
if (CGRectGetMinX(self.detailTextLabel.frame) > CGRectGetMinX(self.textLabel.frame)) { // style = Value1 or Value2
CGFloat textHeight = CGRectGetHeight(self.textLabel.frame);
// If detailTextLabel taller than textLabel then add difference to cell height
if (detailHeight > textHeight) size.height += detailHeight - textHeight;
} else { // style = Subtitle, so always add subtitle height
size.height += detailHeight;
}
}
return size;
}
And in the view controller:
- (void)viewDidLoad {
[super viewDidLoad];
self.tableView.estimatedRowHeight = 44.0;
self.tableView.rowHeight = UITableViewAutomaticDimension;
}
You can pull the full subclass from here: MultilineTableViewCell
So far this fix appears to work well, and has let me successfully use the built-in UITableViewCellStyles with multiline text and details, in self-sizing cells with dynamic type support. This avoids the trouble (and mess) of manually computing the desired cell heights in tableView:heightForRowAtIndexPath:, or having to create custom cell layouts.
[(PARTLY)FIXED IN iOS11]
Apple finally fixed this bug in iOS11 (but apparantly only for UITableViewCellStyleSubtitle). I've updated my solution to only apply the necessary correction to pre-11 devices (otherwise you'll end up with extra space top and bottom of your cell!).
#tiritea 's answer in Swift 3 (Thanks again! :D)
// When UITableViewAutomaticDimension is enabled the system calls
// -systemLayoutSizeFittingSize:withHorizontalFittingPriority:verticalFittingPriority: to calculate the cell height.
// Unfortunately, it ignores the height of the detailTextLabel in its computation (bug !?).
// As a result, for UITableViewCellStyleSubtitle the cell height is always going to be too short.
// So we override to include detailTextLabel height.
// Credit: http://stackoverflow.com/a/37016869/467588
override func systemLayoutSizeFitting(_ targetSize: CGSize, withHorizontalFittingPriority horizontalFittingPriority: UILayoutPriority, verticalFittingPriority: UILayoutPriority) -> CGSize {
self.layoutIfNeeded()
var size = super.systemLayoutSizeFitting(targetSize, withHorizontalFittingPriority: horizontalFittingPriority, verticalFittingPriority: verticalFittingPriority)
if let textLabel = self.textLabel, let detailTextLabel = self.detailTextLabel {
let detailHeight = detailTextLabel.frame.size.height
if detailTextLabel.frame.origin.x > textLabel.frame.origin.x { // style = Value1 or Value2
let textHeight = textLabel.frame.size.height
if (detailHeight > textHeight) {
size.height += detailHeight - textHeight
}
} else { // style = Subtitle, so always add subtitle height
size.height += detailHeight
}
}
return size
}
It looks like Apple has resolved this bug in iOS 11.
Swift 3
After reading various answers, I have used following method for get ride of detail text label UITableViewAutomaticDimension issue . Use Basic style cell with title label only and use attributed string for Text and detail text view. Don't forget to Change tableview cell style from Subtitle to Basic.
func makeAttributedString(title: String, subtitle: String) -> NSAttributedString {
let titleAttributes = [NSFontAttributeName: UIFont.preferredFont(forTextStyle: .headline), NSForegroundColorAttributeName: UIColor.purple]
let subtitleAttributes = [NSFontAttributeName: UIFont.preferredFont(forTextStyle: .subheadline)]
let titleString = NSMutableAttributedString(string: "\(title)\n", attributes: titleAttributes)
let subtitleString = NSAttributedString(string: subtitle, attributes: subtitleAttributes)
titleString.append(subtitleString)
return titleString
}
How to use in cellforrowatindexpath
cell.textLabel?.attributedText = makeAttributedString(title: "Your Title", subtitle: "Your detail text label text here")
Add Following lines in viewdidload
YourTableView.estimatedRowHeight = UITableViewAutomaticDimension
YourTableView.rowHeight = UITableViewAutomaticDimension
YourTableView.setNeedsLayout()
YourTableView.layoutIfNeeded()
From my experience the built in cells don't support auto resize with constraints, I think the best solution is to create a custom cell, it really takes a couple of minutes and you don't need to override layoutSubview, it is really simple .
Just change the type of the cell in the IB to custom, drag a label , set constraints (in the IB), set number of rows , create a subclass, change the cells class in the IB to your subclass, create an outlet in the subclass and that's most of the work,
I am sure there are a lot of tutorials on the net you can follow.
In iOS 8 the UICollectionViewFlowLayout supports automatically resizing cells based on their own content size. This resizes the cells in both width and height according to their content.
Is it possible to specify a fixed value for the width (or height) of all the cells and allow the other dimensions to resize?
For a simple example consider a multi-line label in a cell with constraints positioning it to the sides of the cell. The multi-line label could be resized different ways to accommodate the text. The cell should fill the width of the collection view and adjust it's height accordingly. Instead, the cells are sized haphazardly and it even causes a crash when the cell size is larger than the non-scrollable dimension of the collection view.
iOS 8 introduces the method systemLayoutSizeFittingSize: withHorizontalFittingPriority: verticalFittingPriority: For each cell in the collection view the layout calls this method on the cell, passing in the estimated size. What would make sense to me would be to override this method on the cell, pass in the size that is given and set the horizontal constraint to required and a low priority to the vertical constraint. This way the horizontal size is fixed to the value set in the layout and the vertical size can be flexible.
Something like this:
- (UICollectionViewLayoutAttributes *)preferredLayoutAttributesFittingAttributes:(UICollectionViewLayoutAttributes *)layoutAttributes {
UICollectionViewLayoutAttributes *attributes = [super preferredLayoutAttributesFittingAttributes:layoutAttributes];
attributes.size = [self systemLayoutSizeFittingSize:layoutAttributes.size withHorizontalFittingPriority:UILayoutPriorityRequired verticalFittingPriority:UILayoutPriorityFittingSizeLevel];
return attributes;
}
The sizes given back by this method, however, are completely strange. The documentation on this method is very unclear to me and mentions using the constants UILayoutFittingCompressedSize UILayoutFittingExpandedSize which just represent a zero size and a pretty large one.
Is the size parameter of this method really just a way to pass in two constants? Is there no way to achieve the behavior I expect of getting the appropriate height for a given size?
Alternate Solutions
1) Adding constraints that will be specify a specific width for the cell achieves the correct layout. This is a poor solution because that constraint should be set to the size of the cell's collection view which it has no safe reference to. The value for that constraint could be passed in when the cell is configured, but that also seems completely counterintuitive. This is also awkward because adding constraints directly to a cell or it's content view is causing many problems.
2) Use a table view. Table views work this way out of the box as cells have a fixed width, but this would not accommodate other situations like an iPad layout with fixed width cells in multiple columns.
It sounds like what you are asking for is a way to use UICollectionView to produce a layout like UITableView. If that's really what you want, the right way to do this is with a custom UICollectionViewLayout subclass (maybe something like SBTableLayout).
On the other hand, if you're really asking if there is a clean way to do this with the default UICollectionViewFlowLayout, then I believe there is no way. Even with iOS8's self-sizing cells, it is not straightforward. The fundamental problem, as you say, is that the flow layout's machinery provides no way to fix one dimension and let another respond. (In addition, even if you could, there would be additional complexity around needing two layout passes to size the multi-line labels. This might not fit with how self-sizing cells want to compute all sizing via one call to systemLayoutSizeFittingSize.)
However, if you still want to create a tableview-like layout with a flow layout, with cells that determine their own size, and respond naturally to the collection view's width, of course it is possible. There is still the messy way. I have done it with a "sizing cell", i.e., a non-displayed UICollectionViewCell that the controller keeps only for calculating cell sizes.
There are two parts to this approach. The first part is for the collection view delegate to calculate the correct cell size, by taking in the collection view's width and using the sizing cell to calculate the cell's height.
In your UICollectionViewDelegateFlowLayout, you implement a method like this:
func collectionView(collectionView: UICollectionView,
layout collectionViewLayout: UICollectionViewLayout,
sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize
{
// NOTE: here is where we say we want cells to use the width of the collection view
let requiredWidth = collectionView.bounds.size.width
// NOTE: here is where we ask our sizing cell to compute what height it needs
let targetSize = CGSize(width: requiredWidth, height: 0)
/// NOTE: populate the sizing cell's contents so it can compute accurately
self.sizingCell.label.text = items[indexPath.row]
let adequateSize = self.sizingCell.preferredLayoutSizeFittingSize(targetSize)
return adequateSize
}
This will cause the collection view to set the width of the cell based on the enclosing collection view, but then ask the sizing cell to calculate the height.
The second part is to get the sizing cell to use its own AL constraints to calculate the height. This can be harder than it should be, because of the way multi-line UILabel's effectively require a two-stage layout process. The work is done in the method preferredLayoutSizeFittingSize, which is like so:
/*
Computes the size the cell will need to be to fit within targetSize.
targetSize should be used to pass in a width.
the returned size will have the same width, and the height which is
calculated by Auto Layout so that the contents of the cell (i.e., text in the label)
can fit within that width.
*/
func preferredLayoutSizeFittingSize(targetSize:CGSize) -> CGSize {
// save original frame and preferredMaxLayoutWidth
let originalFrame = self.frame
let originalPreferredMaxLayoutWidth = self.label.preferredMaxLayoutWidth
// assert: targetSize.width has the required width of the cell
// step1: set the cell.frame to use that width
var frame = self.frame
frame.size = targetSize
self.frame = frame
// step2: layout the cell
self.setNeedsLayout()
self.layoutIfNeeded()
self.label.preferredMaxLayoutWidth = self.label.bounds.size.width
// assert: the label's bounds and preferredMaxLayoutWidth are set to the width required by the cell's width
// step3: compute how tall the cell needs to be
// this causes the cell to compute the height it needs, which it does by asking the
// label what height it needs to wrap within its current bounds (which we just set).
let computedSize = self.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize)
// assert: computedSize has the needed height for the cell
// Apple: "Only consider the height for cells, because the contentView isn't anchored correctly sometimes."
let newSize = CGSize(width:targetSize.width,height:computedSize.height)
// restore old frame and preferredMaxLayoutWidth
self.frame = originalFrame
self.label.preferredMaxLayoutWidth = originalPreferredMaxLayoutWidth
return newSize
}
(This code is adapted from the Apple sample code from the sample code of the WWDC2014 session on "Advanced Collection View".)
A couple points to notice. It's using layoutIfNeeded() to force layout of the entire cell, in order to compute and set the width of the label. But that's not enough. I believe you also need to set preferredMaxLayoutWidth so that the label will use that width with Auto Layout. And only then can you use systemLayoutSizeFittingSize in order to get the cell to compute its height while taking the label into account.
Do I like this approach? No!! It feels way too complex, and it does layout twice. But as long as performance doesn't become an issue, I'd rather perform layout twice at runtime than have to define it twice in code, which seems to be the only other alternative.
My hope is that eventually self-sizing cells will work differently and this will all get a lot simpler.
Example project showing it at work.
But why not just use self-sizing cells?
In theory, iOS8's new facilities for "self-sizing cells" should make this unnecessary. If you've defined a cell with Auto Layout (AL), then the collection view should be smart enough to let it size itself and lay itself out correctly. In practice, I haven't seen any examples that have gotten this to work with multi-line labels. I think this is partly because the self-sizing cell mechanism is still buggy.
But I'd bet it's mostly because of the usual trickiness of Auto Layout and labels, which is that UILabels require a basically two-step layout process. It's not clear to me how you can perform both steps with self-sizing cells.
And like I said, this is really a job for a different layout. It is part of flow layout's essence that it positions things that have a size, rather than fixes a width and lets them choose their height.
And what about preferredLayoutAttributesFittingAttributes: ?
The preferredLayoutAttributesFittingAttributes: method is a red herring, I think. That is only there to be used with the new self-sizing cell mechanism. So this isn't the answer as long as that mechanism is unreliable.
And what's up with systemlayoutSizeFittingSize:?
You're right the docs are confusing.
The docs on systemLayoutSizeFittingSize: and systemLayoutSizeFittingSize:withHorizontalFittingPriority:verticalFittingPriority: both suggest that you should only pass UILayoutFittingCompressedSize and UILayoutFittingExpandedSize as the targetSize. However, the method signature itself, the header comments, and the behavior of the functions indicate that they are responding to the exact value of the targetSize parameter.
In fact, if you set the UICollectionViewFlowLayoutDelegate.estimatedItemSize, in order to enable the new self-sizing cell mechanism, that value seems to get passed in as the targetSize. And UILabel.systemLayoutSizeFittingSize seems to return the exact same values as UILabel.sizeThatFits. This is suspicious, given that the argument to systemLayoutSizeFittingSize is supposed to be a rough target and the argument to sizeThatFits: is supposed to be a maximum circumscribing size.
More Resources
While it is sad to think that such a routine requirement should require "research resources", I think it does. Good examples and discussions are:
http://www.objc.io/issue-3/advanced-auto-layout-toolbox.html
http://devetc.org/code/2014/07/07/auto-layout-and-views-that-wrap.html
code for WWDC2014 session 232, "Advanced User Interfaces with Collection Views"
There's a cleaner way to do this than some of the other answers here, and it works well. It should be performant (collection views load fast, no unnecessary auto layout passes etc), and doesn't have any 'magic numbers' like a fixed collection view width. Changing the collection view size, e.g. on rotation, and then invalidating the layout should work great too.
1. Create the following flow layout subclass
class HorizontallyFlushCollectionViewFlowLayout: UICollectionViewFlowLayout {
// Don't forget to use this class in your storyboard (or code, .xib etc)
override func layoutAttributesForItemAtIndexPath(indexPath: NSIndexPath) -> UICollectionViewLayoutAttributes? {
let attributes = super.layoutAttributesForItemAtIndexPath(indexPath)?.copy() as? UICollectionViewLayoutAttributes
guard let collectionView = collectionView else { return attributes }
attributes?.bounds.size.width = collectionView.bounds.width - sectionInset.left - sectionInset.right
return attributes
}
override func layoutAttributesForElementsInRect(rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
let allAttributes = super.layoutAttributesForElementsInRect(rect)
return allAttributes?.flatMap { attributes in
switch attributes.representedElementCategory {
case .Cell: return layoutAttributesForItemAtIndexPath(attributes.indexPath)
default: return attributes
}
}
}
}
2. Register your collection view for automatic sizing
// The provided size should be a plausible estimate of the actual
// size. You can set your item size in your storyboard
// to a good estimate and use the code below. Otherwise,
// you can provide it manually too, e.g. CGSize(width: 100, height: 100)
flowLayout.estimatedItemSize = flowLayout.itemSize
3. Use the predefined width + custom height in your cell subclass
override func preferredLayoutAttributesFittingAttributes(layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes {
layoutAttributes.bounds.size.height = systemLayoutSizeFittingSize(UILayoutFittingCompressedSize).height
return layoutAttributes
}
A simple way to do it in iOS 9 in a few lines of codes - the horizontal way exemple (fixing its height to its Collection View height) :
Init your Collection View Flow Layout with an estimatedItemSize to enable self-sizing cell :
self.scrollDirection = UICollectionViewScrollDirectionHorizontal;
self.estimatedItemSize = CGSizeMake(1, 1);
Implement the Collection View Layout Delegate (in your View Controller most of the time), collectionView:layout:sizeForItemAtIndexPath: . The goal here is to set the fixed height (or width) to the Collection View dimension. The 10 value can be anything, but you should set it to a value that doesn't break constraints :
- (CGSize)collectionView:(UICollectionView *)collectionView
layout:(UICollectionViewLayout *)collectionViewLayout
sizeForItemAtIndexPath:(NSIndexPath *)indexPath
{
return CGSizeMake(10, CGRectGetHeight(collectionView.bounds));
}
Override your custom cell preferredLayoutAttributesFittingAttributes: method, this part actually calculate your dynamic cell width based on your Auto Layout constraints and the height you have just set :
- (UICollectionViewLayoutAttributes *)preferredLayoutAttributesFittingAttributes:(UICollectionViewLayoutAttributes *)layoutAttributes
{
UICollectionViewLayoutAttributes *attributes = [layoutAttributes copy];
float desiredWidth = [self.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].width;
CGRect frame = attributes.frame;
frame.size.width = desiredWidth;
attributes.frame = frame;
return attributes;
}
Try fixing your width in the preferred layout attributes:
- (UICollectionViewLayoutAttributes *)preferredLayoutAttributesFittingAttributes:(UICollectionViewLayoutAttributes *)layoutAttributes {
UICollectionViewLayoutAttributes *attributes = [[super preferredLayoutAttributesFittingAttributes:layoutAttributes] copy];
CGSize newSize = [self systemLayoutSizeFittingSize:CGSizeMake(FIXED_WIDTH,layoutAttributes.size) withHorizontalFittingPriority:UILayoutPriorityRequired verticalFittingPriority:UILayoutPriorityFittingSizeLevel];
CGRect newFrame = attr.frame;
newFrame.size.height = size.height;
attr.frame = newFrame;
return attr;
}
Naturally you also want to ensure that you setup your layout correctly to:
UICollectionViewFlowLayout *flowLayout = (UICollectionViewFlowLayout *) self.collectionView.collectionViewLayout;
flowLayout.estimatedItemSize = CGSizeMake(FIXED_WIDTH, estimatedHeight)];
Heres something I put on Github that uses constant width cells and supports dynamic type so the height of the cells updates as the system font size changes.
YES it can be done using auto layout programmatically and by setting constraints in storyboard or xib. You need to add constraint for width size to remain constant and set height greater than or equal to.
http://www.thinkandbuild.it/learn-to-love-auto-layout-programmatically/
http://www.cocoanetics.com/2013/08/variable-sized-items-in-uicollectionview/
Hope this will be helpful and solve your issue.
I am making app in which there is a chat window. In this window there is one image and a label on that image in custom cell.
I have take two custom cells, one for sender and other for receiver. Both cell are same with left and right alignment.
I want that when the length of comment is increased then whole comment shows in multiline within that image(increases the image size also) .
how can I handle this situation?
I am using setVariable method to set content on cell. I am trying comment code for framing like below comment code but it doesn't work
- ( void ) setComment : ( NSString* ) Comment
{
[ txtComment setText : Comment ] ;
/*CGRect frame1 = txtComment.frame;
frame1.size.height = txtComment.contentSize.height;
txtComment.frame=frame1;*/
}
I would suggest you use auto layout to define the custom cell height. It will help you create a dynamic cell height depending on the length of the comment. You can read about using dynamic height using auto layout in this link
To make this done you can do the following:
Subclass UITableViewCell and also create xib file.
Go to the xib file and add UIImageView and UILabel objects to your cell. Also create an outlet for the label.
As Pavan Kotesh mentioned the easiest way is to use auto layout.
Add top space and bottom space constraints to the cell content view for both image view and label. Then set constraints for x position for both subviews and finally set width and height constraints.
Height constraints must be "Greater of equal" type to let you change size of the views.
Having done that in Interface Builder all you need is to add one method to your subclass for setting a message.
- (void) setMessage: (NSString*) message
{
CGFloat oldLabelSize = _label.frame.size.height;
_label.text = message;
[_label sizeToFit];
CGFloat newLabelSize = _label.frame.size.height;
CGRect frame = self.frame;
frame.size.height += newLabelSize - oldLabelSize;
self.frame = frame;
}
After calling this method add the cell as subview to you chat view.
EDIT:
I think your -cellForRowAtIndexPath method implementation is wrong. What does [ChatViewCell send] perform?
I would have done it like this:
First declare two arrays in your table view controller class. The first is for storing the text of messages and the second for storing cell heights. Also you can create some structure to store those values. After user has finished inputing a message you should somehow estimate cell height and put both height and cell's message to the appropriate arrays. After that you should insert new row(section) in your tableview using insertRowsAtIndexPaths:withRowAnimation: method.
Then in your cellForRowAtIndexPath method:
If dequeueReusableCellWithIdentifier returns nil you should just initialise new ChatViewCell and after if (cell == nil) statement set its message from the appropriate array (due to reuse you should set cell's content every time it goes on screen).
In your heightForRowAtIndexPath: method return values from array that stores cell heights.
I have a uilabel setup in a view. It doesn't have a width constraint, but its width is instead determined by a leading constraint to the thumbnail image, and a trailing constraint to the edge of the view.
The label is set to have 0 lines, and to word wrap. My understanding is that this should cause the frame of the uilabel to grow, and indeed it does sometimes. (Previous to auto layout, I would calculate and update the frame of the label in code).
So the result is, it works correctly in some instance and not others. See most cells working correctly there, but the last cell appears to be too big. In fact it's the right size. The title "Fair Oaks Smog Check Test" actually ends with "Only". So my calcuation for the cell size is right, it should be that size. However the label doesn't wrap the text for whatever reason. It's frame width does not extend off to the right, so that's not the issue.
So what is going on here? It's 100% consistent, always on that cell and not the ones above it, which makes me think it's related to the size of the text, and UILabel isn't re-laying out the text once this view is added to the cell (which makes it actually smaller width wise).
Any thoughts?
Some additional information
The height of the cells is calculated from one sample cell I create and store in a static variable:
- (CGFloat) tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
if (self.items.count == 0) {
return 60;
}
static TCAnswerBuilderCell *cell = nil;
static dispatch_once_t pred;
dispatch_once(&pred,
^{
// get a sample cellonce
cell = [tableView dequeueReusableCellWithIdentifier:TC_ANSWER_BUILDER_CELL];
});
[cell configureCellWithThirdPartyObject:self.items[indexPath.row]];
return [cell heightForCellWithTableWidth:self.tableView.frame.size.width];
}
I configure the cell with my data object on the fly, and then call a method I have on it which calculates the height of the cell with a given table width (can't always rely on the cell frame being correct initially).
This in turn calls a height method on my view, since it is really where the label lives:
- (CGFloat)heightForCellWithTableWidth:(CGFloat)tableWidth {
// subtract 38 from the constraint above
return [self.thirdPartyAnswerView heightForViewWithViewWidth:tableWidth - 38];
}
This method determines the height by figuring out the correct width of the label, and then doing a calculation:
- (CGFloat)heightForViewWithViewWidth:(CGFloat)viewWidth {
CGFloat widthForCalc = viewWidth - self.imageFrameLeadingSpaceConstraint.constant - self.thumbnailFrameWidthConstraint.constant - self.titleLabelLeadingSpaceConstraint.constant;
CGSize size = [self.titleLabel.text sizeWithFont:self.titleLabel.font constrainedToSize:CGSizeMake(widthForCalc, CGFLOAT_MAX) lineBreakMode:NSLineBreakByWordWrapping];
CGFloat returnHeight = self.frame.size.height - self.titleLabel.frame.size.height + size.height;
CGFloat height = returnHeight < self.frame.size.height ? self.frame.size.height : returnHeight;
return height;
}
This works 100% correctly.
The cells are created obviously in cellForRowAtIndexPath and immediately configured:
if (self.items.count > 0) {
TCAnswerBuilderCell *cell = [tableView dequeueReusableCellWithIdentifier:TC_ANSWER_BUILDER_CELL forIndexPath:indexPath];
[cell configureCellWithThirdPartyObject:self.items[indexPath.row]];
return cell;
}
In configuration of the cell, my view is loaded from a nib (it's re-used elsewhere, which is why it's not directly in the cell). The cell adds it as follows:
- (void) configureCellWithThirdPartyObject:(TCThirdPartyObject *)object {
self.detailDisclosureImageView.hidden = NO;
if (!self.thirdPartyAnswerView) {
self.thirdPartyAnswerView = [TCThirdPartyAPIHelper thirdPartyAnswerViewForThirdPartyAPIServiceType:object.thirdPartyAPIType];
self.thirdPartyAnswerView.translatesAutoresizingMaskIntoConstraints = NO;
[self.contentView addSubview:self.thirdPartyAnswerView];
[self.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"|[_thirdPartyAnswerView]-38-|" options:NSLayoutFormatAlignAllCenterY metrics:nil views:NSDictionaryOfVariableBindings(_thirdPartyAnswerView)]];
}
[self.thirdPartyAnswerView configureViewForThirdPartyObject:object forViewStyle:TCThirdPartyAnswerViewStyleSearchCell];
}
Finally my view configuration looks like this:
- (void) configureViewForThirdPartyObject:(TCTPOPlace *)object forViewStyle:(TCThirdPartyAnswerViewStyle) style {
self.titleLabel.text = object.name;
self.addressLabel.text = [NSString stringWithFormat:#"%#, %#, %#", object.address, object.city, object.state];
self.ratingsLabel.text = [NSString stringWithFormat:#"%d Ratings", object.reviewCount];
NSString *ratingImageName = [NSString stringWithFormat:#"yelp_star_rating_%.1f.png", object.rating];
UIImage *ratingsImage = [UIImage imageNamed:ratingImageName];
if (ratingsImage) {
self.ratingImageView.image = ratingsImage;
}
if (object.imageUrl) {
[self.thumbnailImageView setImageWithURL:[NSURL URLWithString:object.imageUrl] completed:nil];
}
}
A sort of solution, but I don't understand why
My subview was designed at 320 width, but has no constraints of its own for width
The subview was added to the cell, but with horizontal constraints that look like this:
#"|[_thirdPartyAnswerView]-38-|"
The view was configured immediately after being added to the cell, meaning the text for the titleLabel was set right then.
For whatever reason, the text was laid out as if the view had the full 320 instead of 282.
The label was never updated, even though the frame of the subview was updated to 282, and there were constraints on the label that would keep it sized correctly.
Changing the size of the view in the xib to be 282 fixed the issue, because the label has the right size to begin with.
I'm still not understanding why the label doesn't re-lay out after the size of the parent view is updated when it has both leading and trailing constraints.
SOLVED
See Matt's answer below: https://stackoverflow.com/a/15514707/287403
In case you don't read the comment, the primary problem was that I was unknowingly setting preferredMaxLayoutWidth via IB when designing a view at a bigger width than it would be shown (in some cases). preferredMaxLayoutWidth is what is used to determine where the text wraps. So even though my view and titleLabel correctly resized, the preferredMaxLayoutWidth was still at the old value, and causing wrapping at unexpected points. Setting the titleLabel instead to it's automatic size (⌘= in IB), and updating the preferredMaxLayoutWidth dynamically in layoutSubviews before calling super was the key. Thanks Matt!
I'm someone who has written an app that uses autolayout of five labels in a cell in a table whose cells have different heights, where the labels resize themselves according to what's in them, and it does work. I'm going to suggest, therefore, that the reason you're having trouble might be that your constraints are under-determining the layout - that is, that you've got ambiguous layout for the elements of the cell. I can't test that hypothesis because I can't see your constraints. But you can easily check (I think) by using po [[UIWindow keyWindow] _autolayoutTrace] when paused in the debugger.
Also I have one other suggestion (sorry to just throw stuff at you): make sure you're setting the label's preferredMaxLayoutWidth. This is crucial because it's the width at which the label will stop growing horizontally and start growing vertically.
I had the same problem and solved it using a suggestion from this answer. In a subclass of UILabel I placed this code:
- (void)layoutSubviews
{
[super layoutSubviews];
self.preferredMaxLayoutWidth = self.bounds.size.width;
}
I don't understand why this is not the default behavior of UILabel, or at least why you cannot just enable this behavior via a flag.
I am a little concerned that preferredMaxLayoutWidth is being set in the middle of the layout process, but I see no easy way around that.
Also, check that you are passing integral numbers to your layout constraints in code.
For me it happened that after some calculations (e.g. convertPoint:toView:), I was passing in something like 23.99999997, and eventually this lead to a 2-line label displaying as a one-liner (although its frame seemed to be calculated correctly). In my case CGRectIntegral did the trick!
Rounding errors could kill ya :)