UITableView inside UITableViewCell with dynamic cell height - ios

I have a dynamic list of items, each item could have different teamplate/layout (and height). And one those item types could have an internal list of items to select from, regularly 5-6 rows, each has different height.
If I try to describe it further, in my scenario I have one tableview (#slave) inside tableviewcells (#master-cell) of another tableview (#master). Moreover cells (#slave-cell) in my #slave tableview could have different height as well. So I need to layout my #slave to have #master automatically calc and update its size.
I have the issue with the inner table (#slave). In case of auto-layout, to fit all the cell space, the table will be collapsed unlike UILabel or other controls. So what I need here is to get the projected size of #slave table and set the height of the #slave = content height of the #slave.
I found similar post and it works if all rows have the same height but I'm using custom rows with dynamic height so the tableView.contentSize.Height gives me invalid result (basically just multiply rowNumbers * estimatedRowHeight, you can see it on the screenshot, master item #3 has 4 inner cells). Even after calling #slave.reloadData I couldn't get that size.
What is the proper way to build that kind of UI?
Source code with a test project attached (Xamarin.iOS)

I just ran into the same problem a few days ago,and tried to work it around.
The #master-cell works like a childViewController,it's the delegate datasource of the #slave TableViewController.But you cann't have a childViewController in the UITableViewcell.
Customize UITableViewCell to hold necessary property and acts as #slave TableViewController's delegate datasource,and configure #slave-cell's
height and data.
The real problem is the height for #master-cell,
If your data is simple and static,you can compute the height in advance,and return it in method func tableView(_ tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat of the ViewController.
Otherwise,add a method to #master-cell which return the height for the whole cell when its property is set.And create a proxy #master-cell to compute the height and return it :
override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
let cell = CustomUITableViewCell();
let model = self.getModel(indexPath)
cell.model = model
let height = cell.requiredHeight()
return height;
}
It's complex and expensive,but it works.

I think you do not have need of take UITableView inside UITableView. You can take more than one section in UITableView. And use different cellReuseIdentifier. This way your goal will be achieved.

For such a layout ios provide section in tableview, for master items use SectionView(there is delegate method for sectionView -> in which you can provide view for a section) and as different section may have different type of row so make rows according your need and return them according to section.

Perhaps it is because I do not know the background of you project or what you are trying to accomplish, but tableViews inside of tableVIew cells sounds unnecessarily trivial. Rather than using a master tableView with #slave tableViews, it would be cleaner to just break things out by section in a single tableView as stated in a previous answer. There are UITableViewDelegate methods designed to streamline this for you!

first you have to get string's height then the height have to give in below tableView delegate
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
return stringHeight;
}
it is working for me.

I'm using Xcode 8.3.2 and Swift3.1.
I had the same requirement, have tried all, nothing worked for me.
Finally, UIStackView is what worked for me.
In a tableviewcell, I have added a UIStackView(Verticle), keep adding sub cells to that UIStackView. And it automatically increased the cell height.
Check the following to add UIStackView programmatically.
Add views in UIStackView programmatically

If you Use Different Sections and Rows use the below format, its working for me,
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
if (indexPath.section == 0) {
return 121;
}
if(indexPath.section==1)
{
return 81;
}
if (indexPath.section%2 == 0 && indexPath.row == 1) {
return 161;
}
if (indexPath.section%2 != 0 && indexPath.row == 0) {
return 81;
}
if (indexPath.section==16 && indexPath.row==0) {
return 161;
}
else
{
return 44;
}
}
i have Template code, different section and row, its each row have different sizes, so i have give this type of code, if you get idea see the above code then its helpful for you,
or
If you change the height for Content text size use the below code, its calculate the content size then change the height(UILabel) size, its working for me
-(CGFloat)tableView:(UITableView*)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
ListModel *model = [ListArray objectAtIndex:indexPath.row];
CGRect labelRect = [model.content boundingRectWithSize:CGSizeMake(tableView.frame.size.width - 90 - 15, 0)
options:NSStringDrawingUsesLineFragmentOrigin
attributes:#{
NSFontAttributeName : [UIFont fontWithName:#"Arial" size:14.0]
}
context:nil];
CGFloat heightOfCell = labelRect.size.height + 60;
if(heightOfCell > 106)
return heightOfCell;
return 106;
}
hope its helpful

yes of course you can have as many prototypes cells as you want for example check this piece of code:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
if indexPath.section == 0 {
let cell = tableView.dequeueReusableCellWithIdentifier("TodayWeatherCell", forIndexPath: indexPath) as! SITodayWeatherTableViewCell
cell.setupCell(upCommingWeather)
cell.aboutCityUpdateTableViewClousure = {
self.tableView.reloadData()
}
return cell
}else {
let cell = tableView.dequeueReusableCellWithIdentifier("cityDetailCell", forIndexPath: indexPath) as! SICityDetailTableViewCell
let detail = detailCity[indexPath.row]
cell.setupCityDetail(detail)
return cell
// Configure the cell...
}
}
There are two different cells in one single UITableView.
Hope it helps.

Related

estimatedHeightForRowAtIndexPath best practice

I have a UITableCell that displays dynamic content of various type.
There are labels that can be 1 or 2 lines, textViews with various heights, images with various heights.
What is the best way to estimate height for these rows? I can make functions that calculates height for labels and textviews. But for the constraints, can i make outlet and sum upp all the height constants?
If you set up your auto layout correctly. You don't have to calculate the constant yourself. You can simply set two properties (or do it in table view delegates):
tableView.estimatedRowHeight = 80;
tableView.rowHeight = UITableViewAutomaticDimension
and OS will check the correct cell height for you.
If you have a few different cells the best way to make it works smooth is implement a delegate method instead of do it via property tableView.estimatedRowHeight, for example:
func tableView(tableView: UITableView, estimatedHeightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
// 1. get table view cell for indexPath
// 2. check which cell type have you got
// 3. return as closed estimated value for the cell type as you possible can.
// Example
//if cell type is my cell A return 80
// else if cell is B return 120, etc
}
maybe it can help
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSAttributedString *attrStr = [[NSAttributedString alloc] init];
height = [self calculateHeightForAttributeString:attrStr];
return height;
}
-(CGFloat)calculateHeightForAttributeString:(NSAttributedString *)attrStr {
CGRect frame;
frame = [attrStr boundingRectWithSize:CGSizeMake(labelMaxWidth, MAXFLOAT)
options:(NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading)
context:nil];
return frame.size.height;
}

Adding Margin to UITableViewCell

I am trying to achieve a view I mocked out on sketch. I've replicated it on Android cause I'm really good on that platform. I'm good on iOS, but the UI is kind of my weak point.
I extended a UIViewController and on my StoryBoard have the top to be a view and the bottom a tableview. The problem I'm having is centering the UITableViewCell to look like that in the app itself. Below is the solution I've tried. But, it just squeeze it all to the top. NB. I use UIView to draw those Tiny Lines in the UITableViewCell
func configureTableView() {
//First One I tried then later commented it out
loanStateTable.rowHeight = UITableViewAutomaticDimension
loanStateTable.scrollToNearestSelectedRowAtScrollPosition(UITableViewScrollPosition.Middle, animated: true)
//Second One I tried
var edgeInset = UIEdgeInsets(top: 16, left: 16, bottom: 0, right: 16)
loanStateTable.contentInset = edgeInset
}
And the storyboard view
Any help would be appreciated. Thanks
Output:
Leave everything as it is. Don't try to inset your whole TableView. Create a container View inside your TableViewCell instead:
Set the row height:
Also in code:
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return 160.0
}
Specify the right distance to the edges:
Now add the elements and specify the constraints as well:
Don't forget to set the cellIdentifier:
The result in the simulator:
If you want to look at it under the hood: I've uploaded it as github project
I don't know if I understood your question. I thought that your question was to centre vertically those two rows on screen. my code do that.
Approach:
I usually play this by adding extra cell(s) at the start and/or end of the
UITableView
#define cell_height 100
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return array.count + 1;
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
if (indexPath.row > 0)
return cell_height;
else
{
CGFloat tableHeight = tableview.frame.size.height;
CGFloat contentHeight = array.count * cell_height;
CGFloat whiteAreaHeight = tableHeight - contentHeight;
if (whiteAreaHeight > 0)
return whiteAreaHeight/2.0;
else
return 0;
}
}
- (UITableViewCell *)tableView:(nonnull UITableView *)tableView cellForRowAtIndexPath:(nonnull NSIndexPath *)indexPath
{
if (indexPath.row > 0)
{
NSInteger realArrayIndex = indexPath.row - 1;
// your existing code here
return cell;
}
else
{
//return empty cell. add it to the storyboard with different identifier and get it.
}
}
I hope that helps!
From your screenshots it seems that you have a problem with the auto layout system and dynamic cell. For this is suggest to read this very good answer on stack overflow that explain how to build dynamic cells with auto layout. An important thing to consider is that creating views with auto layout is very fast and intuitive but it expensive from a performance point of view. This problem is accentuated for the UITableView where the scroll can result not smoothly. If you have simple cell (only few views and a view hierarchy with few levels) it can be ok but to the auto layout manager I suggest to:
specify a value for the property estimatedRowHeight if all the cells have the same height
implement the method tableView(_:estimatedHeightForRowAtIndexPath:) of the UITableViewDelegate if the cells have different heights.
Regarding the problem with margins I suggest to nest a UIView inside the contentView of the cell and use it as container for all the other views of the cell. In this way you can add left and right margins between this view container and the contentView so that you can center it.

How to make fixed height cell on uitableview?

So I want to make timeline content like instagram, I'm using custom cell on my uitableview. My problem is I already set the cell height to 345 on the storyboard but I get cell table like below:
and here is my custom cell on storyboard:
How can I fix it, so I can get the result like on my storyboard?
The reason that you are having this issue is you have probably set your Custom tableview cell's row height to 345 but that is not set as custom while your UITableview's row height is less than 345. So, what you need to do is go to storyboard and select the Table(UITableview) and set its row height to the maximum possible row height.
Let's assume that you are going to have two different kind of row heights. One with 345 and another with 325. As 325<345, you set your tableview's row height to 345.
Now, select the custom tableview cell and make its row height as custom and set that to either 345 or 325. In your case, it will be 345.
It should be good now.
However, more appropriate way when you have different cell size would be to use the delegate method for row height specification.
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
if(indexPath.row==0){
return 345;
}
else if(indexPath.row==1){
return 325;
}
else{
return 300; //a default size if the cell index path is anything other than the 1st or second row.
}
}
Use this delegate method:
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return 345
}
In your UITableViewController subclass, set self.tableView.rowHeight = 345. Might be a bug with storyboard heights not being translated.
You can set Table View Cell height here.

UITableView duplicate section header after updating row heights

In my iOS app, I have a UITextView inside a tableview cell.
The UITextView and hence the cell height expands when the frame required for the text entered by user exceeds the current height of the cell.
In order to achieve the above, I am calling [tableView beginUpdates] followed by [tableView endUpdates] to reload the height for the cells.
The above is resulting duplicate section headers overlapping the expanded cell.
Is there a way to fix this without calling [tableView reloadData]?
Appended below is some relevant code:
When there is a text change, I verify if the text will fit in current text view, if not the cell is expanded to the new height:
- (void)textViewDidChange:(UITextView *)textView {
CGFloat oldTextViewHeight = [(NSNumber *)[self.cachedTextViewHeightsDictionary objectForKey:indexPath] floatValue];
CGFloat newTextViewHeight = [textView sizeThatFits:CGSizeMake(textView.frame.size.width, CGFLOAT_MAX)].height + CELL_HEIGHT_PADDING;
if (newTextViewHeight > oldTextViewHeight ||
(newTextViewHeight != oldTextViewHeight && oldTextViewHeight != TEXTVIEW_CELL_TEXTVIEW_HEIGHT)) {
[self reloadRowHeights];
}
}
- (void)reloadRowHeights {
// This will cause an animated update of the height of the UITableViewCell
[self.tableView beginUpdates];
[self.tableView endUpdates];
}
It's also important to note that I am using a custom section header, which makes my problem similar to one mentioned here:
UITableView Custom Section Header, duplicate issue
I cannot however use the solution to above problem because I cannot reloadData for the tableView in middle of user entering text.
Try implementing
tableView(_ tableView: UITableView, estimatedHeightForHeaderInSection section: Int)
delegate method if you didn't
Little late to the party, but I couldn't find a working solution on SO, and then I figured one out, so I thought I'd share.
I use UITableViewAutomaticDimension both for cell heights and for section header heights. My header view class is just a UIView subclass with some subviews as needed. Inside my tableView(:viewForHeaderInSection:) class, I just initialized a new header view as needed, and I was experiencing this duplicate headers issue. Not even reloadData helped.
What seems to have fixed it for me was to implement basic "cell re-use" for the headers. Something like this:
Store the header views in a dictionary somewhere in your view controller.
class ViewController: UIViewController {
var sectionHeaders: [Int: UIView] = [:]
// etc...
}
Then, upon request, return your existing section header view if available, or else create and store a new one.
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
if let sectionHeader = self.sectionHeaders[section] {
return sectionHeader
} else {
let sectionHeader = YourSectionHeader()
// Setup as needed...
self.sectionHeaders[section] = sectionHeader
return sectionHeader
}
}

iOS 8 UITableView first row has wrong height

I'm working on an app where I face a strange issue. I've created a UITableViewController in the storyboard and added a prototype cell. In this cell, I've added an UILabel element and this UILabel takes up the whole cell. I've set it up with Auto Layout and added left, right, top and bottom constraints. The UILabel contains some text.
Now in my code, I initialize the the rowHeight and estimatedRowHeight of the table view:
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.rowHeight = UITableViewAutomaticDimension
self.tableView.estimatedRowHeight = 50
}
And I create the cell as follows:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell : UITableViewCell? = tableView.dequeueReusableCellWithIdentifier("HelpCell") as? UITableViewCell
if(cell == nil) {
cell = UITableViewCell(style: .Default, reuseIdentifier: "HelpCell")
}
return cell!
}
I return two rows in my table view. Here comes my problem: the height of the first row is way to large. It appear that the second, third row etc all have a correct height. I really don't understand why this is the case. Can someone help me with this?
I had a problem where the cells' height were not correct on the first load, but after scrolling up-and-down the cells' height were fixed.
I tried all of the different 'fixes' for this problem and then eventually found that calling these functions after initially calling self.tableView.reloadData.
self.tableView.reloadData()
// Bug in 8.0+ where need to call the following three methods in order to get the tableView to correctly size the tableViewCells on the initial load.
self.tableView.setNeedsLayout()
self.tableView.layoutIfNeeded()
self.tableView.reloadData()
Only do these extra layout calls after the initial load.
I found this very helpful information here: https://github.com/smileyborg/TableViewCellWithAutoLayoutiOS8/issues/10
Update:
Sometimes you might have to also completely configure your cell in heightForRowAtIndexPath and then return the calculated cell height. Check out this link for a good example of that, http://www.raywenderlich.com/73602/dynamic-table-view-cell-height-auto-layout , specifically the part on heightForRowAtIndexPath.
Update 2: I've also found it VERY beneficial to override estimatedHeightForRowAtIndexPath and supply somewhat accurate row height estimates. This is very helpful if you have a UITableView with cells that can be all kinds of different heights.
Here's a contrived sample implementation of estimatedHeightForRowAtIndexPath:
public override func tableView(tableView: UITableView, estimatedHeightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
let cell = tableView.cellForRowAtIndexPath(indexPath) as! MyCell
switch cell.type {
case .Small:
return kSmallHeight
case .Medium:
return kMediumHeight
case .Large:
return kLargeHeight
default:
break
}
return UITableViewAutomaticDimension
}
Update 3: UITableViewAutomaticDimension has been fixed for iOS 9 (woo-hoo!). So you're cells should automatically size themselves without having to calculate the cells height manually.
As the Apple says in the description of setNeedsLayout:
This method does not force an immediate update, but instead waits for the next update cycle, you can use it to invalidate the layout of multiple views before any of those views are updated. This behavior allows you to consolidate all of your layout updates to one update cycle, which is usually better for performance.
Because of this you should add needed lines of code (which should be executed with right layout) in dispatch_after block(which will put your method in queue of RunLoop). And your code will be executed after needs layout applies.
Example:
- (void)someMethod {
[self.tableView reloadData];
[self.tableView setNeedsLayout];
[self.tableView layoutIfNeeded];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
//code which should be executed with the right size of table
});
In iOS 8, assigning the estimatedRowHeight a value turns on the new iOS 8 automatic row height calculation feature. This means that the cell's height is derived by using its internal constraints from the inside out. If there's something wrong with those constraints you'll get odd results. So there's something wrong with your cell constraints. They are probably ambiguous; that is the usual reason for inconsistency. However that's all I can tell you, since you have not actually shown / described the constraints.
I suggest removing the bottom constraint on the UILabel. It will resize according to the text and the cell should resize as well.
If that didn't resolve the issue, try adding the following in viewDidLoad() :
self.tableView.reloadData()
This worked for me
- (void)layoutSubviews
{
[super layoutSubviews];
// Your implementation
}
I got this from here >> http://askstop.com/questions/2572338/uitableview-displays-separator-at-wrong-position-in-ios8-for-some-cells

Resources