Change frame of UIView in UICollectionViewCell in cellForItemAtIndexPath - ios

My problem is very similar to this one, though the selected correct answer (which is just to use tags) didn't work for me (nor did any other answer): Changing label position in UICollectionViewCell.
I have a UICollectionView with a custom UICollectionViewCell. The cell has a UIView in it, which is referenced via IBOutlet property. I'm simply trying to change the frame within the cellForItemAtIndexPath method, and while the frame of the UIView is in fact changed, it is subsequently ignored, or not honored.
However, once the UICollectionView is scrolled a bit and it begins reusing cells, the reused cells do honor the previous change to the UIView's frame, and the UIView looks just like it should. So for some reason the first time cellForItemAtIndexPath is called for a particular cell, the frame change is ignored, but is subsequently honored once the cell is reused.
Actually, something more curious which I just noticed is that a reused cell has the size of the frame that had been previously changed, but keeps the original origin, which happens to be (0, 0), though I'm trying to update it to something like (0, 50).
Changing other properties of the UIView works fine, such as changing the background color. What might be happening to prevent the UIView's frame from initially changing?

UICollectionViewCell does not update frames for views that are defined in storyboard or xib files.
You have to create the view programmatically in your custom cells initWithFrame and/or awakeFromXib files.
Cheers!
hmmmm
I may be mistaken. I know this is true for UITableViewCell
Cannot update UI object's frame in UITableViewCell

Related

Multiline label in UIView vs UITableViewCell

I created a custom UITableViewCell with two labels and a button.
It behaved perfectly as expected, and looked like this:
Later, I realised I needed to use this particular view multiple places, one of which was not in a TableView. Therefore I decided to delete my cell, and create a custom UIView instead - and create an empty custom UITableViewCell only containing this custom view - constrained to 0 in all directions.
With the exact same elements and constraints, the UIView turned out like this when shown in a TableView as a cell:
Everything is identical. NumberOfLines is set to 0, all the constraints are the same.
I also observed something weird, that when I rotated the entire phone to landscape, it turned out like this:
This is exactly how I'd like it to look when in landscape.
If I now turn it back to portrait, suddenly it looks like this:
Now it suddenly looks exactly like I want it to look in portrait.. I just had to flip it to landscape and back to portrait.. Why is that?..
I played around with some variables, among them: preferredMaxLayoutWidth, and found that if I set this value to the size of the screen (minus the 16*2px padding) it looks as expected at first launch in portrait. However, I don't want to set this value. There is no magic number in px that will be right. The whole point of this is to get it fit properly on all screen sizes and orientations. I probably could re-set the preferred width every time superview's layoutSubviews or something is called, but I figured there has to be a solution for this..? Why did it work for UITableViewCell and not for UIView inside a UITableViewCell?
I have tried all sorts of sizeToFit etc., and don't find any questions or answers that targets this particular difference in UIView and UITableViewCell..
The problem here is a bit complex but I will try to explain it... When the view lays out, it initially does so independently of its container and then reports its height to the system when the system needs to determine the height of the cell, which happens before the cell is constructed. Once the cell exists and the device is rotated, the view "realizes" it's in a cell and constraints itself and reports its height appropriately.
Sadly, this is an issue in the table view system stemming from legacy issues with how cells were originally sized back before constraints existed.
The simplest solution is to put the labels and button back directly in the cell. Another option might be to make your external view a table view cell. You can instantiate a table view cell outside of a table view for that one spot it's needed in. Another option is to implement the table view delegate's tableView:heightForRowAtIndexPath: method and manually determine the size.
I am assuming that you are using following method for table view's height:
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableViewAutomaticDimension
}
So, Just add all your view's in stack and set top, bottom, leading and trailing constraints of UIStackView with respect to UIView of xib as:

CollectionViewCell keeps properties of old cell when reused

I have a UICollectionView with several cells. At time properties of views inside the UICollectionViewCell change. I noticed that when one of the cells is reused the "new" cell "inherits" these changed properties from the old cell – even though they are set to their initial state in the initializer of the cell.
This is happening with AutoLayout Constraints posed on Views inside the Cell and the alpha property of views (things like the text of a UILabel and the image of a UIImageView are changing exactly like they should).
Is this common behavior or is there something wrong with my code? Is there anything I can do about it?

Calling layoutSubviews to dynamically resize a uilabel within a uicollectionviewcell

I'm trying to resize a UILabel frame in a custom UICollectionViewCell. I added the label to the cell in Storyboards. The frame gets adjusted in -layoutSubviews. The frame is getting resized when layoutSubviews is called, but it isn't displaying the resize when the cell is displayed.
Here's what I'm assuming based on what I've seen...
It seems as though calling -layoutSubviews forces a re-layout on
every cell in the CollectionView, which hurts performance.
With auto-layout on, you simply can't resize the label.
If I delete the label in Storyboards, create the it
programmatically, and set the frame when the cell is called in
-cellForRowAtIndexPath, it works like a charm.
Are my assumptions above correct, and if so, is there an alternate to -layoutSubviews better suited for CollectionViews.

UITableView Visible Cells Not Laying Out Subviews When View Appears

I have a UITableView that has multiple sections, with custom UITableViewCell subclasses populating the table. Within these UITableViewCell subclasses, I am implementing layoutSubviews to customize the position of labels, etc.
All of the cell's subviews are adjusting as they should, except the first batch of visible cells upon the view loading. They are identical to how they are laid out in storyboard, which is not what I want. For example:
http://i.stack.imgur.com/L5GJh.png
Note: The green and orange borders are a visual aid to see if the labels are resizing.
Upon scrolling, all of the new cells that appear have their subviews the way that I programmed them to be in layoutSubviews. As far as the first batch of visible cells, I can scroll them offscreen and then back on, and then the subviews are laid out perfectly. For example:
http://i.stack.imgur.com/jgjch.png
Within tableView: cellForRowAtIndexPath:, I call [cell layoutIfNeeded] before the method returns the cell. If I change this to [cell layoutSubviews], then the inverse happens, where the first batch of visible cells are laid out as they should be, but all of the cells that get loaded upon scrolling are not laid out properly.
I have tried to put [cell layoutIfNeeded] within [tableview: willDisplayCell: with no luck. Any ideas on how to fix this?
Thanks in advance!
This is the behavior you'd see if you were setting frames in your layout code with Auto Layout enabled. This won't work. The Auto Layout system is responsible for setting frames and will overwrite the values you set.
You should either specify your layout using constraints or turn off Auto Layout.

UITableViewCell frame height not matching tableView:heightForRowAtIndexPath:

I'm constructing a UITableView with variable height custom table cells, their height determined by the size of a contained multi-line UILabel. I've got the tableView:heightForRowAtIndexPath: delegate method wired up and calculating the final height correctly using sizeWithFont:constrainedToSize:.
I've run across strange issue: by the time the data source method tableView:cellForRowAtIndexPath: is called, the correct per-row height has already been determined as described above, but the frame of the cell does not match that height. Instead, the frame.size.height property of the cell is the default cell height of the table view (86 px, as I've set it in Interface Builder, the correct height when the contained UILabel has just one line of text), instead of being the height that tableView:heightForRowAtIndexPath: determined correct for that index path.
I'm producing the cells in cellForRowAtIndexPath: using dequeuing, that is,
// Using storyboards, this never returns nil, no need to check for it
CustomCell *cell = (CustomCell *)[tableView dequeueReusableCellWithIdentifier:#"SomeIdentifier"];
NSLog(#"%f", cell.frame.size.height); // 86, not correct if the cell contains a multi-line UILabel
It seems, then, that whatever iOS is doing behind the scenes, the dequeuing is not setting the frame property of the cell to match the calculated height. This in itself is not that surprising, dequeuing concerns itself with cell instances, not their geometry. The cells are rendered correctly, though, so the height property is being set somewhere, but it happens after cellForRowAtIndexPath:.
So: when I initially populate the table view, cell.frame.size.height is 86 for all the cells as they appear for the first time when I scroll the list downwards. Since the correct geometry is set sometime after the first cellForRowAtIndexPath: for each row before it's displayed, when I scroll back up, the height property is correct for each cell that comes back into view after being reused.
After this I can scroll the table view back and forth at will, and the height property remains correct for each cell from that point on.
What's the correct way of getting the correct cell height the first time around, before any dequeue-based reuse happens? I need this to do a bit of re-positioning of the subviews of the table cell. Do I need to manually call heightForRowAtIndexPath: in cellForRowAtIndexPath: and then manually set the frame of the freshly created CustomCell instance to match that height? This seems redundant, and I'd need to create a mechanism to detect when the cell is created for the first time with the wrong frame height vs. when it is dequeued with the correct frame height later to avoid this redundancy.
So, if someone can shed some light into what the logic is behind this, I'd appreciate it.
As suggested by Flexo, answering this myself is apparently better than adding an edit to the question. So, here's the previous edit as an answer:
Nevermind, I should read the docs better. I can get the correct frame in the tableView:willDisplayCell:forRowAtIndexPath: method of UITableViewDelegate, so that is the correct place to do subview customization that relies on the correct frame being set, not cellForRowAtIndexPath:.
Interesting that the docs say this, though:
After the delegate returns, the table view sets only the alpha and frame properties, and then only when animating rows as they slide in or out.
...since the correct frame is already there when this delegate method is called. But anyway, problem solved.
Don't forget that the cell is a UIView, so overriding layoutSubviews is also a valid way to get the correct frame and adjust size/position of subviews. Just don't forget to call [super layoutSubviews].
Easiest way I found was just to call cell.layoutIfNeeded() before you do any setup on the cell. This makes sure all the layout constraints are calculated and the frames are set.

Resources