Auto-layout issue on scroll - ios

I have .xib which has some auto-layout like so (might need to click to zoom):
Nothing special, I have 13pt spacing with the border of the superview.
This xib is then loaded in the code into a tableViewCell.
I'm also setting the height in the viewDidLoad of the table like so:
[self.tableView setEstimatedRowHeight:40];
[self.tableView setRowHeight:UITableViewAutomaticDimension];
My cellForRowAtIndexPath is:
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:reuseId forIndexPath:indexPath];
---OR--- (Both don't make a difference)
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:reuseId];
Now the fun begins. The auto-layout works when it wants to, and sometimes I scroll a cell out of view, bring it back and it's not the same size. See image below:
Any ideas on what I am doing wrong?
PS: Not in the screengrab here, but I do have some cells with a lot of text that look like the same height of the big cell on left.
PPS: I've tried the following with no success (based on this article http://useyourloaf.com/blog/2014/08/07/self-sizing-table-view-cells.html ):
Added [self.tableView reloadData]; in the viewDidLoad
I removed the explicit and preferred width on the label

I had the same issue, it seems if you set the text of the label exclusively in - tableView:willDisplayCell:forRowAtIndexPath: instead of - tableView:cellForRowAtIndexPath: then you will observe the behaviour of random incorrectly sized cells.
I've created a test project to demonstrate this https://github.com/sja26/iOS-TestSelfSizingCellsWithUILabel

One reason you get this behaviour - you use
cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
Try to use
cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
instead. This will reuse your cell with the same size as needed
And, of course check heightForRowAtIndexPath method for properly setup value

Related

How to update UITableViewCell height because of text entered into UITextView

I have different examples where we can update the UITableViewCell height based on growing UITextView which actually is working for me. The issue which I am facing is, I have more subviews below UITextView inside a UITableViewCell. This way, the cell's height updates but the position of the subviews remain fixed which causes overlap of UITextView and the subviews.
Just to mention, I am not using auto layout.
How do I fix this ?
These are the three screenshots which will help in understanding my issue :
1. Before TextView is shown :
2. After TextView is shown :
3. After text is entered :
I'm assuming you are using auto layout for this cell (but we could use a code example or Xcode screenshot to help you more specifically). If you are using auto layout, you'll want to make sure that:
You have a constraint between the UITextView and the other UIView subviews below it in the cell
The other UIView subviews are eventually constrained to the bottom of the cell.
Because UITableViewCells are reused, you will find that when adding objects to the contentView, those objects will also be reused without being removed, leading to overlapping subviews. The way around this it to start your cellForRowAtIndexPath with the following code:
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [aTableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier];
}
for (UIView * view in [cell.contentView subviews]) {
// clears the cell before reusing
[view removeFromSuperview];
}
Hope this helps.
Are you using auto layout? I have done this before several times with UITableViewCells though it’s mainly been with UILabel and not UITextView.
Your problem might be resolved with auto layout. You layout all your subviews (inside of UITableViewCell) relative to each other. If one view (i.e. UITextView) changes size the other views will then adjust relative to that view. Here are a few useful links.
This isn’t specific to UITableViewCell but has a lot of good examples of various scenarios.
Stanford University Developing iOS 7 Apps: Lecture 9 - Animation and Autolayout
https://www.youtube.com/watch?v=r1sc7NI6-wo

UITableviewCell subview appears only on scrolling or cell reuse (iOS)

As in the image below, the UITableView subview appears only on tableview reload or cell reuse (during scrolling, mostly). The blue color circle is what I want in my UITableViewCell. When it first appears, it will be a small dot as you can see in the picture, and on scrolling or refreshing the tableview, it appears as the full circle.
What can be the issue?
I use the following code in cellforRowAtIndexPath method
cell.categoryRoundBackground.layer.cornerRadius=cell.categoryRoundBackground.frame.size.height/2;
try using dequeueReusableCellWithIdentifier: forIndexPath:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
CustomTableViewCell *cell1 = (CustomTableViewCell *)[tableView dequeueReusableCellWithIdentifier:#"CustomID" forIndexPath:indexPath];
The most likely problem is that at the moment when you access your cell's frame height the first time by calling
cell.categoryRoundBackground.frame.size.height / 2
the cell has never been placed in a table view, it has no idea what its frame height is going to be, and so it uses some default value. The actual height depends on the value returned by your code in heightForRowAtIndexPath: method.
You can work around this problem by computing the frame size yourself. You should be able to do that, because your code supplies the value to heightForRowAtIndexPath:.
It's possible that you change the corner radius before that the view layouts its subviews.
You should try to put the line
cell.categoryRoundBackground.layer.cornerRadius=cell.categoryRoundBackground.frame.size.height/2;
inside
- (void)viewDidLayoutSubviews {}
You have to Override the method in CustomTableViewCell
- (void)layoutSubviews{
[super layoutSubviews];
self.categoryRoundBackground.layer.cornerRadius=self.categoryRoundBackground.frame.size.height/2;
self.categoryRoundBackground.layer.masksToBounds = YES;
}
and In CellForRowAtIndexPath: you have to write these lines at the end
// Update layout
[cell.contentView setNeedsLayout];
[cell.contentView layoutIfNeeded];
Hope it will solve your problem

Two customize cells in UITableView

I would like to know if it's possible to have 2 customize cells in one UITableView ?
Because I would like to have two different type of cells in one view : the first row will be big (with white background on screenshot), and after simple row (with red background on screenshot).
Tell me if that's possible or not, and how to make that :)
I let you see what I want to make :
http://www.noelshack.com/2015-13-1427415385-sans-titre.png
Or maybe put a UIView for big label, and after table cell ?
There are 2 approaches, assuming that the big screenshot is only on the first row. You could set the table view's property
tableView.tableHeaderView = myHugeImage;
Otherwise, if you design 2 rows using nibs and custom classes, you'll need to call the following in viewDidLoad. Do note that custom classes must be subclasses of UITableViewCell
[tableView registerNib:[UINib nibWithNibName:#"bigrow" bundle:nil] forCellReuseIdentifier:#"big"]
[tableView registerNib:[UINib nibWithNibName:#"normal row" bundle:nil] forCellReuseIdentifier:#"normal"]
If you used classes and no nibs, then you would use registerClass:forCellReuseIdentifier
If you used classes and designed the cell directly inside the prototype cell, then neither of those calls are necessary.
Lastly, inside tableView:cellForRowAtIndexPath:
if (indexPath.row == 0) {
BigRowCell *c = [tableView dequeueReusableCellWithIdentifier:#"big" forIndexPath:indexPath];
return c;
}
SmallRowCell *c = [tableView dequeueReusableCellWithIdentifier:#"normal" forIndexPath:indexPath];
return c;

UITableViewCell being reused while still on-screen?

I have what probably seems like a really weird problem (it does to me!)
I am using a UITableView to display cells which each contain a UIWebView. I realise that this is a bad idea on the face of it, but I can't really do this any other way.
I am caching the heights of each cell when the UIWebView finishes loading, and then calling:
[self.tableView beginUpdates];
[self.tableView reloadRowsAtIndexPaths:#[cellIndexPath]
withRowAnimation:UITableViewRowAnimationNone];
[self.tableView endUpdates];
All of the germane code is in a Gist here.
I also have the UIWebViews cached in a dictionary on the data source, so it can be reused when the cell is reloaded.
This seems to sort of work, but I am encountering a lot of issues whereby the cells' contents will randomly disappear. I have added some logging into determine what's going on, and in what order, and it seems like some of the cells are being reused while they're still on-screen.
I see this in my logs while scrolling down:
2014-02-11 13:45:49.091 EApp[45936:70b] Generating cell for 1: Panning
2014-02-11 13:45:49.245 EApp[45936:70b] Generating cell for 2: Calibration
2014-02-11 13:45:50.063 EApp[45936:70b] Generating cell for 3: Aperture Priority
2014-02-11 13:45:50.063 EApp[45936:70b] Reusing cell: Stopping down
"Stopping down" in this case is a cell that is still on-screen. The "generating cell" items are logged inside the data source's cellForRowAtIndexPath and the "reusing" messages inside the cells' prepareForReuse.
Does anyone know what could be happening here? I know this seems complex.
The following line in your prepareForReuse is probably the culprit:
if ([self.contentWebView isDescendantOfView:self.contentWebView]) {
[self.contentWebView removeFromSuperview];
}
As the contentWebView is never a descendant of itself, it will not be removed from the cell, and the contentView will contain two webviews after the cellForRowAtIndexPath:
You probably meant to say:
if ([self.contentWebView isDescendantOfView:self.contentView]) {
[self.contentWebView removeFromSuperview];
}
Or simply:
[self.contentWebView removeFromSuperview];
One of the features/limitations of UITableView is that you don't know if, and can't depend on, a cell is being created or reused. You should always be able to handle both.
GENERALLY, when you call -reloadRowsAtIndexPaths:withRowAnimation:, you will get the cell from that indexPath to reuse. If that indexPath was on screen, it will be a cell that was on screen.
I don't know if it's the problem, but in the code you provided, you don't even initialize your cell...
I'm even surprise it works.
static NSString *CellIdentifier = #"FeedItemCell";
EFeedItemCell *cell = [self.tableViewController.tableView dequeueReusableCellWithIdentifier:CellIdentifier];
you should add to it :
if (!cell) {
cell = [EfeedItemCell alloc] initWithReus....];
}
From your code it seems that you are caching the webViews and then are adding them to cells programmatically. This can create random problems similar to what I had faced in the passed.
You must use EFeedItemCellWebView in your storyboard. Just add a UIWebView and change the class name to your custom class. And then when the data is loaded just simply change its contents in - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath;

UILabel in a UITableViewCell With Dynamic Height

I'm having some problems implemented dynamic row heights in a UITableView - but it isn't the cells that I'm having a problem with, its the UILabel inside of the cell.
The cell just contains a UILabel to display text. My tableView:heightForRowAtIndexPath: is correctly resizing each cell by calculating the height of the label that will be in it using NSString's sizeWithFont: method.
I have a subclass of UITableViewCell that just holds the UILabel property that is hooked up in storyboard. In storyboard I've set its lines to 0 so it will use as many lines as it needs, and I've set its lineBreak to Word Wrap.
Here is how I'm setting up the cells:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
ExpandCell *cell = [tableView dequeueReusableCellWithIdentifier:#"Cell" forIndexPath:indexPath];
SomeObject *object = self.tableObjects[index.row];
cell.myLabel.text = [object cellText];
[cell.myLabel sizeToFit];
return cell;
}
When I build this, I get my table view with the cell's all sized to the correct height for their content, but the labels are all 1 line that just runs off the side of the cells. However, if I scroll the table so cell's leave the screen, and then scroll back to them, their label will be resized correctly and the cell will look how I expected it to initially.
I have also attempted calculating the labels frame with the same method I'm calculating the row height with, and I get the same behavior - it doesn't draw correctly until it scrolls off of the screen and back on again.
I have found two ways to work around this, and neither are acceptable solutions.
First, if in viewDidAppear: I call reloadData on my tableview, the cells and labels draw themselves correctly the first time. This won't work for my situation because I will be adding and removing cells to this table, and I don't want to call reloadData every time a cell is added.
The second workaround seems very strange to me - if I leave the font settings at the default System Font 17 on the UILabel, the cells draw themselves correctly. As soon as I change the font size, it reverts to its behavior of not drawing a label correctly until it leaves the screen and comes back, or gets reloadData called on the tableView.
I'd appreciate any help with this one.
I ended up resolving this by alloc/init'ing the label in cellForRowAtIndexPath. I'm not entirely sure why this is a solution - but it appears the problem I was experiencing has to do with how storyboard (or when, perhaps?) creates the objects within the cell. If I alloc/init the label in the cell in cellForRowAtIndexPath, everything loads and sizes correctly.
So... my current fix is to check if the cell has my custom label in it. If it doesn't, I alloc/init the label and put it in the cell. If it does have one, as in its a cell that's been dequeued, then I just set the text in the label that is already there.
Not sure if its the best solution, but its working for now.
I ended up resolving this by unchecking the AutoSizing checkbox in IB. It is unclear why auto-layout was causing this problem.
I ran over the same problem and I end up solving it by calling [cell layoutIfNeeded] before return the cell
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
ExpandCell *cell = [tableView dequeueReusableCellWithIdentifier:#"Cell" forIndexPath:indexPath];
SomeObject *object = self.tableObjects[index.row];
cell.myLabel.text = [object cellText];
[cell layoutIfNeeded];
return cell; }

Resources