I'm working on an iOS 5 project with storyboards, and thus using dynamic table cell prototypes in IB. In one of the views, I've got a table view with variable-height cells, with the cell height calculated from the height of the contents.
tableView:heightForRowAtIndexPath: returns correct values for all cells when the table view is first displayed. All is well when scrolling down for a few items, but then something goes amiss: the actual cells seem to be correct height, including their touch areas, but their separators are rendered in the wrong places (inside cells instead of between them).
From measuring the separator placement, it appears as if cell reuse might have something to do with this. The separators for the first three cells are rendered correctly, but not the fourth one. heightForRowAtIndexPath: returns the correct height for it (125 pixels in this case), and its contained subviews are all in their right places. However, the separator is rendered only 108 pixels from the previous separator, placing it inside the 125 pixel high area of the cell.
Here's the kicker: 108px is the height of the first table cell, now out of sight and probably reused. I don't have definite proof of this, but it appears that the table view is ignoring heightForRowAtIndexPath: for these cells and is just rendering the separator according to the reused cell height.
This doesn't explain why a bunch of later, shorter cells are not rendered separators at all. But this is all I've got to go on.
Is there a workaround, an IB setting or something else that might help?
I had the same problem where the separators were showing at seemingly random positions.
It turned out the problem was that I was overriding layoutSubviews but forgot to call [super layoutSubviews]. Adding that call fixed the issue for me.
I also met this weird problem. For a custom table cell, add a layer as a separator may be a better choice.
in initWith*** method of the custom table cell class:
separator = [CALayer layer];
separator.backgroundColor = [UIColor colorWithWhite:0.8f alpha:1.0f].CGColor;
[self.layer addSublayer:separator];
update separator's frame in layoutSubviews method:
- (void)layoutSubviews {
CGFloat height = 1.0f;
separator.frame = CGRectMake(0.0f, self.frame.size.height - height, self.frame.size.width, height);
[super layoutSubviews];
}
Related
I've got a UICollectionView which holds 4 different types of cells, one of which is supposed to contain a UIView with a UITextView inside. The UITextView will have different attributed strings of dynamic height set in it so the entire cell also needs to resize its height accordingly - I can't have any scrolling in it.
I get the height of the attributed string through its size property. I specify the cell height as that amount in the method.
- (CGSize)collectionView:(UICollectionView *)collectionView
layout:(UICollectionViewLayout*)collectionViewLayout
sizeForItemAtIndexPath:(NSIndexPath *)indexPath {
}
That's working quite fine - I can see that the cell is longer.
Now, for some reason I can't make its subviews longer - setting a new frame simply doesn't have any effect:
CGRect frameCellOriginal = self.textTest.frame;
CGRect frameCellNew = CGRectMake(frameCellOriginal.origin.x, frameCellOriginal.origin.y, frameCellOriginal.size.width, frameCellOriginal.size.height / 2 + atString.size.height);
self.textTest.frame = frameCellNew;
I tried using the setNeedsDisplay method on the UITextView but that didn't change anything. I also tried using setAutoresizesSubviews:YES and setAutoresizingMask:UIViewAutoresizingFlexibleHeight on both the cell and its subviews but it didn't work either.
Anyone got any ideas?
Edit:
I just noticed that the 1st time I scroll through the collection, the view containing the text is as small as in the .xib file. Then, if I scroll it again, its white background UIView becomes longer and fills the entire cell - taking the height I set it to. On the 3rd scroll the UITextView also takes said height.
I've no idea why that happens if I set all 3 views in the same method...
I have a question about the usage of UITableView. I have added a UIView above the cells of my UITableView (see image).
This is very nice because I can add some images and labels there and it will scroll with the cells of the table view. Now I am calling some REST API to get an image which I want to add in this view above the cells. The problem now is that I dont know the height of the image, so I have to calculate it based on the aspect ratio which already works fine. When I add the image I can change its height correctly and move down labels and buttons BUT the image overlaps some of the visible cells.
My question: How can I move down the frame of the container? of the cells? dynamically based on my image respective View height?
I have tried to set the height of the View in the TableView but it has no effect. So I suppose that I have to set the y start position of the cells but I dont know how.
Do I need to set an y offset in the delegate method -(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath ?
Any ideas?
I think the key to this is setting your view to be the table view's tableHeaderView after you change the size of the view. I did it like this in a test app,
-(void)layoutHeader {
self.label.text = #"This is a long text to see if it expands to take up multple lines. The quick red fox jumped over the lazy brown dog.";
[self.label setPreferredMaxLayoutWidth:self.tableView.frame.size.width];
CGRect stringRect = [self.label.text boundingRectWithSize:CGSizeMake(self.tableView.bounds.size.width - 40,CGFLOAT_MAX) options:NSStringDrawingUsesLineFragmentOrigin attributes:#{NSFontAttributeName:self.label.font} context:nil];
CGRect headerFrame = self.header.frame;
headerFrame.size.height = stringRect.size.height + 40;
self.header.frame = headerFrame;
[self.tableView beginUpdates];
self.tableView.tableHeaderView = self.header;
[self.tableView endUpdates];
}
I called this with a delay from viewDidLoad as a test. The beginUpdates, endUpdates code isn't necessary if you don't want to see the rows move down to accommodate the new view size. The property, header, is an IBOutlet to the view I added to the top of the table view in IB, and "label" is a subview of that view.
I would personally just use tableView:viewForHeaderInSection: to build the view out, and in tableView:heightForHeaderInSection: calculate the new height and return that. That way you don't have to worry about bumping things down within the tableView since UITableView will handle the rest for you once you. Just make sure to call [_tableView reloadData]; on your tableView after you get the image.
I have a UITableViewCell subclass which has an image, title and description.
I am supposed to resize the cell height according to the description content length i.e. if it spans more than 5 lines, I should extend it (+other subviews like image etc) till it lasts.
The next coming cells should begin only after that.
I have my UITableViewCell subclass instantiated from xib which has a fixed row height = 160.
I know this is pretty standard requirement but I am unable to find any guidelines.
I already extended layoutSubViews like this:
- (void) layoutSubviews
{
[self resizeCellImage];
}
- (void) resizeCellImage
{
CGRect descriptionRect = self.cellDescriptionLabel.frame;
CGRect imageRect = self.cellImageView.frame;
float descriptionBottomEdgeY = descriptionRect.origin.y + descriptionRect.size.height;
float imageBottomEdgeY = imageRect.origin.y + imageRect.size.height;
if (imageBottomEdgeY >= descriptionBottomEdgeY)
return;
//push the bottom of image to the bottom of description
imageBottomEdgeY = descriptionBottomEdgeY;
float newImageHeight = imageBottomEdgeY - imageRect.origin.y;
imageRect.size.height = newImageHeight;
self.cellImageView.frame = imageRect;
CGRect cellFrame = self.frame;
cellFrame.size.height = imageRect.size.height + imageRect.origin.y + 5;
CGRect contentFrame = self.contentView.frame;
contentFrame.size.height = cellFrame.size.height - 1;
self.contentView.frame = contentFrame;
self.frame = cellFrame;
}
It pretty much tells that if description is taller than image, we must resize the image as well as cell height to fit the description.
However when I invoke this code by doing this:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
cell.cellDescriptionLabel.text = #"Some long string";
[cell.cellDescriptionLabel sizeToFit];
[cell setNeedsLayout];
return cell;
}
It appears that while cell frame is changed due to layoutSubViews call, other cells do not respect it. That is, they appear on the same position had the previous cell would not have resized itself.
Two questions:
How to make it possible what I want?
Am I doing right by calling setNeedsLayout within cellForRowAtIndexPath?
P.S.: I know heightForRowAtIndexPath holds key to changing the cell height, but I feel that the data parsing (not shown here) that I do as part of cellForRowAtIndexPath would be an overkill just to calculate height. I need something that can directly tell the UITableViewCell to resize itself according to content needs.
-tableView:heightForRowAtIndexPath: is by design how variable sized cells are calculated. The actual frame of a cell is of no importance and is changed by the table view to fit its needs.
You are sort of thinking of this backwards. The delegate tells the table view how cells need to be drawn, then the table view forces cells to fit those characteristics. The only thing you need to provide to the cell is the data it needs to hold.
This is because a table view calculates all the heights of all the cells before it has any cells to draw. This is done to allow a table view to size it's scroll view correctly. It allows for properly sized scroll bars and smooth quick-pans through the table view. Cells are only requested when a table view thinks a cell needs to be displayed to the screen.
UPDATE: How Do I Get Cell Heights
I've had to do this a couple of times. I have my view controller keep a cell which is never used in the table view.
#property (nonatomic) MyTableViewCell *standInCell;
I then use this cell as a stand in when I need measurements. I determine the base height of the cell without the variable sized views.
#property (nonatomic) CGFloat standInCellBaseHeight;
Then in -tableView:heightForRowAtIndexPath:, I get the height for all my variable sized views with the actual data for that index path. I add the variable sized heights to my stand in cell base height. I return that new calculated height.
Note, this is all non-autolayout. I'm sure the approach would be similar, but not identical to this, but I have no experience.
-tableView:heightForRowAtIndexPath: is the preferred way to tell tableview the size of its cells. You may either precalculate and cache it in a dictionary and reuse, or alternatively in ios7, you can use -tableView:estimatedHeightForRowAtIndexPath: to estimate the sizes.
Take a look at this thread - https://stackoverflow.com/questions/18746929/using-auto-layout-in-uitableview-for-dynamic-cell-layouts-variable-row-heights, the answer points to a very good example project here - https://github.com/caoimghgin/TableViewCellWithAutoLayout.
Sorry, but as far as I know you have to implement tableView:heightForRowAtIndexPath:. Warning, in iOS 6 this gets called on every row in you UITableView right away, I think to draw the scrollbar. iOS7 introduces tableView:estimatedHeightForRowAtIndexPath: which if implemented allows you to just guess at the height before doing all the calculation. This can help out a lot on very large tables.
What I found works well is just have your tableView:heightForRowAtIndexPath: call cellForRowAtIndexPath: to get the cell for that row, and then query that cell for it's height cell.bounds.size.height and return that.
This works pretty well for small tables or in iOS7 with tableView:estimatedHeightForRowAtIndexPath implemented.
I want to show links between two cells of uiTableView.
For Ex:
To show links between cells 1 and 5, it could be shown like:
Does any one has any idea how this can be achieved. Also when table scrolls, these links should be scrolled with it.
This looks like you want to build hierarchical view. Your implementation might be rejected by Apple due to not taking HIG into account.
Also what will be in case when lower part is not seen to user? Arrow with no end and need to scroll down for the user?
You might want to do a tree like structure (anything hierarchical) instead of ugly (sorry for that) arrows.
If you want arrow between two cell then make a separate UIView class for the Tablecell, in that UIView add one UILabel for text and one UIImageView for arrow, adjust there position as per your requirement.
Now pass this UIView to cell.
Hope this will help you.
UITableViewCell is just a subclass of UIView and UITableView is a subclass of UIScrollView. The only fanciness that UITableView provides is creating/reusing the cells and laying them out in the scroll view. (That's a gross over-simplification but for this It'll do the trick.)
So if I have a UIView subclass that draws an arrow, then it's just a matter of getting the frame for the cells I want to point to. CGRect frame1 = [[self.tableView cellForRowAtIndexPath:indexPath] frame];
Some pseudocode...
topCellFrame = get top cell frame;
bottomCellFrame = get bottom cell frame;
arrow = new arrow view;
arrow set frame = frame with origin of top cell frame origin, and height of distance from topCellFrame to bottomCellFrame;
tableView add subview = arrow;
There are edge cases to think about, If the top cell or bottom cell are offscreen the cellForRowAtIndexPath: will return nil and frame will be CGRectZero.
But I'll leave that as an exercise for you.
Edit: (I haven't done this exact thing, but I have done some similar things with the frames of cells)
I have a grouped tableView in my iPad-app, and I've been trying to set cell.imageView.center = cell.center to center the image instead of putting it to the leftmost position. This is apparently not possible without a subclass of the UITableviewCell(If someone could explain why, that'd also be appreciated.. For now I just assume they are 'private' variables as a Java-developer would call them).
So, I created a custom tableViewCell, but I only want to use this cell in ONE of the rows in this tableView. So in cellForRowAtIndexPath I basically write
cell = [[UITableViewCell alloc]initWith//blahblah
if(indexPath.row == 0)
cell = [[CustomCell alloc]initWith//blahblah
This is of course not exactly what I'm writing, but that's the idea of it.
Now, when I do this, it works, but the first cell in this GROUPED tableView turns out wider than the rest of them without me doing anything in the custom cell. The customCell class hasn't been altered yet. It still has rounded corners though, so it seems it knows it's a grouped tableView.
Also, I've been struggling with programmatically getting the size of a cell, in cellForRowAtIndexPath, I've tried logging out cell.frame.size.width and cell.contentView.frame.size.width, both of them returning 320, when I know they are a lot wider.. Like, all the rows are about 400 wide, and the first cell is 420 or something. It still writes out 320 for all the cells..
This code will not work for a couple of reasons:
cell.imageView.center = cell.center;
Firstly, the center is relative to its superview. I believe the cells superview is the tableView. The imageView's superview will be the content view of the cell. Therefore the coordinate systems are different so the centens will be offset. E.g. the 3rd cell down will have a center of 0.5 widths + 3.5 heights. You should be able to ge around this issue by doing:
cell.imageView.center = CGPointMake( width / 2 , height / 2 );
The second issue is related to how the table view works. The table view manages its cells view's. The width of a cell is defined by the table view's width and the height is defined by the table view's row height property. This means the cell itself has no control over its size.
You can however size its subviews, but you must do this after the cells size has been set (otherwise you can get strange results). You can do this in layout subviews (of the custom UITableViewCell class). See this answer.
- (void)layoutSubviews {
[super layoutSubviews];
self.imageView.frame = ....
}
When layoutSubviews is called the cells frame has been set, so do your view logging here instead of cellForRowAtIndexpath.
As for the GROUPED style. Im not sure if this is designed to work with custom views. I suspect it sets the size of its cells to its own width minus a 20 pixel margin on each size, then applies a mask to the top and bottom cells in a section to get the rounded effect. If you are using custom view try to stick with a standard table view style.