How to properly add custom TableViewCell? - ios

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.

Related

How to Add Margin Between UITableViewCells Without Being Hacky

A lot of the solutions I've seen here include changing the cell's background to an image and using sections for rows rather than just rows themselves. I'm looking to have only two sections and have each cell expand in height on tap, so neither of those solutions would work.
I saw one solution includes setting the frame of the cell in the layoutSubviews() function like so:
override func layoutSubviews() {
super.layoutSubviews()
self.frame = CGRectOffset(self.frame, 0, 10);
}
When I do this however, it only gives margin to one cell and that's only when I tap on the cell.
Is there a surefire way to add spacing in between UITableViewCells without being hacky and breaking the cell layouts in the process?
I did this yesterday pretty easily with auto layout.
I set the background of the cell and it's content view to clear, then I created a new view and setup constraints all around it and put my labels inside of it. The height changes dynamically based on the label so I needed to use UITableViewAutomaticDimension for the row height and give it an estimated row height as well.
I don't see why this wouldn't work for expanding it on a tap as well, you just might have to reload the cell.
make the cell and it's contentView transparent
contentView addSubview customContentView and layout your cell on customContentView
customContentView pin to contentView top leading trailing with offset 0 but pin to bottom with offset 10 //the margin height

Is it possible to subclass UITableView and make it have an intrinsic content size for its height based on the number of cells?

I want my UITableView to only be as tall as it needs to be, as it floats above the main UIViewController's view. If I hardcode a height of 200 and theres' only one cell in the table, it looks silly.
I'm aware in my view controller I could monitor the table view and define the height of it based on the number of cells it has, but the height is a property of the view, and for MVC it doesn't make much sense for the controller to be actively managing a view's height.
Is it possible to have a UITableView subclass, and have it define an intrinsic height based on the number of cells it holds? So with Auto Layout I could add the subclass to my view, specify its width, center it vertically, and perhaps define a "less than or equal" height constraint saying to keep it smaller than 200pts. But for the most part have the intrinsic content size of the view define the height of the view automatically?
This would be just like a UILabel being able to be centered horizontally and vertically with some distance from the left and right, and have it grow and shrink vertically automatically.
Could I feasibly do this with a UITableView subclass?
You can do this easily by having the table view use its contentSize property to "know" how tall it needs to be. This value could be somewhat inaccurate if you're using estimated row heights, but it should be good enough. In this example, I gave the table view a height constraint (as well as width and centerY), and made an IBOutlet to it (heightCon). The only code needed was this,
#interface RDTableView ()
#property (weak,nonatomic) IBOutlet NSLayoutConstraint *heightCon;
#end
#implementation RDTableView
-(void)reloadData {
[super reloadData];
self.heightCon.constant = MIN(200, self.contentSize.height);
}
You would also have to override reloadRowsAtIndexPaths:withRowAnimation: and
reloadSections:withRowAnimation: if you're updating your table with either of those as well.
This feels like a chicken/egg problem you have with designing your UI. Sounds like you want to set the height of the floating tableview based on how many rows it contains * height per row, but the tableview doesn't know any of this information until its been drawn, ie [tableView reloadData] and the corresponding height for row delegate methods are called.
I'd suggest rendering the tableview offscreen somewhere, draw all the rows, sum up all the heights for each row, then present the view to the user with the appropriate CGRect.
When you (re)load data in your TableView, you can do this. The TableView will take only the height it needs
CGRect frame = youTableView;
frame.size.height = CELL_HEIGHT*[yourArray count];
if(frame.size.height > MAX_TABLEVIEW_HEIGHT)
{
frame.size.height = MAX_TABLEVIEW_HEIGHT
}
youTableView.frame = frame;

How to layout custom UITableViewCell with varying height

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.

How to draw connecting lines between two uitableviewcells ios

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)

UITableView separators drawn incorrectly through cell reuse?

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];
}

Resources