EXC_BAD_ACCESS in heightForRowAtIndexPath iOS - objective-c

I'm working on an application where I have a custom subclass of UITableViewCell. I want to make the cell's height dynamic based on the text inside it. I try do do that in my heightForRowAtIndexPath method. But I'm having some issues, the following code causes and EXC_BAD_ACCESS(code=2 address=0xb7ffffcc) error.
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
PostCell *cell = (PostCell *)[tableView cellForRowAtIndexPath:indexPath];
CGSize postTextSize;
if (![cell.postText.text isEqualToString:#""]) {
postTextSize = [cell.postText.text sizeWithFont:[UIFont systemFontOfSize:14] constrainedToSize:CGSizeMake(cell.postText.frame.size.width, 200)];
} else {
postTextSize = CGSizeMake(cell.postText.frame.size.width, 40);
}
return postTextSize.height + cell.userName.frame.size.height + 10;
}
If I instead have:
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
return 100;
}
It works.
It seems to be crashing on this line: PostCell *cell = (PostCell *)[tableView cellForRowAtIndexPath:indexPath];, and I don't know why. I have tried to build clean and reset the simulator, but neither worked. Any ideas why this is happening?

I have tried to reproduce the problem. It turns out that calling cellForRowAtIndexPath: inside heightForRowAtIndexPath causes heightForRowAtIndexPath to be called recursively. Here is an extract of the stack backtrace after the 3 recursion steps:
frame #0: 0x000042d0 DocInteraction`-[DITableViewController tableView:heightForRowAtIndexPath:] + 48 at DITableViewController.m:262
frame #1: 0x0054f688 UIKit`-[UISectionRowData refreshWithSection:tableView:tableViewRowData:] + 3437
frame #2: 0x0055040f UIKit`-[UITableViewRowData(UITableViewRowDataPrivate) _ensureSectionOffsetIsValidForSection:] + 144
frame #3: 0x00551889 UIKit`-[UITableViewRowData numberOfRows] + 137
frame #4: 0x00553dac UIKit`-[UITableViewRowData globalRowsInRect:] + 42
frame #5: 0x003f82eb UIKit`-[UITableView(_UITableViewPrivate) _visibleGlobalRowsInRect:] + 177
frame #6: 0x004001e6 UIKit`-[UITableView cellForRowAtIndexPath:] + 113
frame #7: 0x000042f2 DocInteraction`-[DITableViewController tableView:heightForRowAtIndexPath:] + 82 at DITableViewController.m:262
frame #8: 0x0054f688 UIKit`-[UISectionRowData refreshWithSection:tableView:tableViewRowData:] + 3437
frame #9: 0x0055040f UIKit`-[UITableViewRowData(UITableViewRowDataPrivate) _ensureSectionOffsetIsValidForSection:] + 144
frame #10: 0x00551889 UIKit`-[UITableViewRowData numberOfRows] + 137
frame #11: 0x00553dac UIKit`-[UITableViewRowData globalRowsInRect:] + 42
frame #12: 0x003f82eb UIKit`-[UITableView(_UITableViewPrivate) _visibleGlobalRowsInRect:] + 177
frame #13: 0x004001e6 UIKit`-[UITableView cellForRowAtIndexPath:] + 113
frame #14: 0x000042f2 DocInteraction`-[DITableViewController tableView:heightForRowAtIndexPath:] + 82 at DITableViewController.m:262
frame #15: 0x0054f688 UIKit`-[UISectionRowData refreshWithSection:tableView:tableViewRowData:] + 3437
frame #16: 0x0055040f UIKit`-[UITableViewRowData(UITableViewRowDataPrivate) _ensureSectionOffsetIsValidForSection:] + 144
frame #17: 0x00551889 UIKit`-[UITableViewRowData numberOfRows] + 137
frame #18: 0x003ff66d UIKit`-[UITableView noteNumberOfRowsChanged] + 119
frame #19: 0x003ff167 UIKit`-[UITableView reloadData] + 764
Finally the program crashes. On my Simulator this happens when the back trace is about 57000 levels deep.
Old answer (not wrong, but does not explain the
EXC_BAD_ACCESS):
The problem is that
[tableView cellForRowAtIndexPath:indexPath]
returns nil for rows that are currently not visible. A table view allocates only so many cells that are required to display the currently visible rows. The cells are reused when you scroll the table view.
But heightForRowAtIndexPath is called for all cells of the table view before any row is displayed.
As a consequence, you should not get the text from the table view cells to compute the height in heightForRowAtIndexPath. You should get the text from your data source instead.

tableView heightForRowAtIndexPath is called before cellForRowAtIndexPath, before a cell is displayed, the height needs to be calculated first.
you should get text from your data source, not from cell

I had a similar problem and came across, just to share what I do now which works well for me
CGFloat mycell_height;
- (void)viewDidLoad
{
[super viewDidLoad];
UITableViewCell *cell = [self.tableView dequeueReusableCellWithIdentifier:#"MyCell"];
mycell_height = cell.frame.size.height;
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
return mycell_height; //assuming all cells are the same
}
Hope this helps anyone looking for this and new to iOS programming like me :-)

In this case you should call UITableViewDataSource method for getting cell for indexPath instead of cellForRowAtIndexPath: method of UITableView because at the moment when tableView: heightForRowAtIndexPath: first time called there are no any cells in tableView.
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath: (NSIndexPath *)indexPath {
PostCell *cell = (PostCell *)[self tableView:tableView cellForRowAtIndexPath:indexPath];
CGSize postTextSize;
if (![cell.postText.text isEqualToString:#""]) {
postTextSize = [cell.postText.text sizeWithFont:[UIFont systemFontOfSize:14] constrainedToSize:CGSizeMake(cell.postText.frame.size.width, 200)];
} else {
postTextSize = CGSizeMake(cell.postText.frame.size.width, 40);
}
return postTextSize.height + cell.userName.frame.size.height + 10;
}
It works and no causes EXC_BAD_ACCESS exception.

SWIFT:
Use this to know when the tables have loaded:
extension UITableView {
func reloadData(completion: ()->()) {
UIView.animateWithDuration(0, animations: { self.reloadData() })
{ _ in completion() }
}
}
Create a boolean value
var tables_loaded = Bool(false)
Then in viewDidLoad:
tableView.reloadData {
tables_loaded = true
// call functions or other stuff regarding table manipulation.
// tableView.beginUpdates()
// tableView.endUpdates()
}
then in
override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
if tables_loaded {
let cell = tableView.cellForRowAtIndexPath(indexPath)
// Do your magic here!
}
}

Related

UITableviewCell automatic sizing based on label not working

UITableview cells are returning heights that don't really correlate to their text.
I have been dealing with a rather annoying bug where xcode returns incorrect heights for cells and just in general is return pixels heights for elements in cells that are terribly inconsistent.
I thought I could implement the methods below to clean things up, but they turned out just to make things worse.
The first image in the google doc is what my cells look like when I use these methods. Please tell me any ideas you have to fix them. The crux of the problem is a special case which I have shown in the second image of the google doc.
The reason behind the special case and a deeper discussion of the reason it occurs is in the google doc. Here's the google doc link:
https://docs.google.com/document/d/1tT43nE-1Wq8leRIaoQ29S0aQhZUWP88kIX3WlG_RcOg/edit?usp=sharing
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
if (indexPath.section==0) { //postcell
return 500.0; //TODO add some autolayout stuff for this case...
} else { //comment cell
UIFont * font=[UIFont fontWithName:#"Helvetica Neue" size:13.0];
NSIndexPath *adjustedIndexPath = [NSIndexPath indexPathForRow:indexPath.row inSection:indexPath.section-1];
BRComment *comment = [self.commentsController objectAtIndexPath:adjustedIndexPath];
CGRect screenRect = [[UIScreen mainScreen] bounds];
CGFloat screenWidth = screenRect.size.width;
//CommentCell * commentCell=(CommentCell *)[self.tableView cellForRowAtIndexPath:indexPath];
CGSize labelHeight = [self heigtForCellwithString:comment.body andLabelWidth:screenWidth-78.0 withFont:font];
return labelHeight.height; // the return height + your other view height
}
}
-(CGSize)heigtForCellwithString:(NSString *)stringValue andLabelWidth:(CGFloat)labelWidth withFont:(UIFont *)font{
CGSize constraint = CGSizeMake(labelWidth,9999); // Replace 300 with your label width //TODO replace
NSDictionary *attributes = #{NSFontAttributeName: font};
CGRect rect = [stringValue boundingRectWithSize:constraint
options: (NSStringDrawingUsesLineFragmentOrigin|NSStringDrawingUsesFontLeading)
attributes:attributes
context:nil];
return rect.size;
}
iOS 8 introduces the super handy UITableViewAutomaticDimension const to UITableView. To get cells to size themselves automatically simply return UITableViewAutomaticDimension from both -tableView:heightForRowAtIndexPath: and -tableView:estimatedHeightForRowAtIndexPath:.
- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath
{
return UITableViewAutomaticDimension;
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
return UITableViewAutomaticDimension;
}
Voila, your cells should be sized correctly. Note this'll also take into account cell layoutMargins and indentationWidth/indentationLevel if you need to use those.

UIScrollView scrolling area inside a UICollectionViewCell

I have the following UICollectionView Controller
My problem is when I zoom into the image, I am able to "scroll past" the image boundary. It is almost exactly similar to this question Keep zoomable image in center of UIScrollView but the answer didn't solve it for me.
From what I understand, I believe this is because the content size of the scrollView isn't set to the size of the image. However, I'm not sure where to set something like self.scrollView.contentSize = self.imageView.image.size in my subclassed UICollectionViewCell.
Default View
Zoomed and Moved Beyond Image Boundary
My expected behaviour is for the image to sorta bounce back if the user tries to scroll beyond the image.
The relevant code thus far
Imgur Cell Subclass
- (void)awakeFromNib {
self.scrollView.minimumZoomScale = 1;
self.scrollView.maximumZoomScale = 3.0;
self.scrollView.delegate = self;
}
- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView
{
return self.imageView;
}
View Controller
- (void)viewDidLoad {
[super viewDidLoad];
[self setStyle];
ImgurCellDetail *cell = (ImgurCellDetail *)[self.collectionView cellForItemAtIndexPath:self.indexPath];
cell.scrollView.minimumZoomScale = cell.scrollView.frame.size.width / cell.imageView.frame.size.width;
cell.scrollView.maximumZoomScale = 3.0;
cell.scrollView.contentSize = cell.imageView.image.size;
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[self loadSelectedImage];
}
- (void)loadSelectedImage
{
[self.collectionView scrollToItemAtIndexPath:self.indexPath
atScrollPosition:UICollectionViewScrollPositionCenteredHorizontally
animated:NO];
}
- (ImgurCellDetail *)collectionView:(UICollectionView *)collectionView
cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *cellIdentifier = #"Cell";
ImgurCellDetail *cell = [collectionView dequeueReusableCellWithReuseIdentifier:cellIdentifier forIndexPath:indexPath];
[self resetImage:cell];
cell.imageView.image = [UIImage imageWithContentsOfFile:self.imageArray[indexPath.row]];
[self.collectionView addGestureRecognizer:cell.scrollView.pinchGestureRecognizer];
[self.collectionView addGestureRecognizer:cell.scrollView.panGestureRecognizer];
return cell;
}
-(CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout
sizeForItemAtIndexPath:(NSIndexPath *)indexPath {
// Need this for 3.5"
return self.collectionView.frame.size;
}
- (void)collectionView:(UICollectionView *)collectionView
didEndDisplayingCell:(ImgurCellDetail *)cell
forItemAtIndexPath:(NSIndexPath *)indexPath {
[self.collectionView removeGestureRecognizer:cell.scrollView.pinchGestureRecognizer];
[self.collectionView removeGestureRecognizer:cell.scrollView.panGestureRecognizer];
}
- (void)resetImage:(ImgurCellDetail *)cell {
//reset zoomScale back to 1 so that contentSize can be modified correctly
cell.scrollView.zoomScale = 1;
}
I should probably mention that I have no issues when the Image View is set to aspect fill but as you can see, it's currently set to aspect fit which is where my troubles begin.
I think you have to start by setting the contentSize of the scroll view to match the size of the scaled down size of your image (the size shown in your first screenshot) and not to the actual size of the picture.
// Not this
//cell.scrollView.contentSize = cell.imageView.image.size;
// But this
CGSize originalSize = cell.imageView.image.size;
CGSize sizeToFit = cell.scrollView.bounds.size;
CGFloat scaleDownFactor = MIN(sizeToFit.width / originalSize.width,
sizeToFit.height / originalSize.height);
CGSize scaledDownSize = CGSizeMake(nearbyintf(originalSize.width * scaleDownFactor),
nearbyintf(originalSize.height * scaleDownFactor));
cell.scrollView.contentSize = scaledDownSize;
// Also use scaledDownSize for the viewForZooming
From there you keep adjusting the content insets as done in the answer you linked.
Finally the scroll view's actual size may not be fully adjusted yet in viewDidLoad!
I managed to solve my own bug. I adapted the method found here: http://www.raywenderlich.com/10518/how-to-use-uiscrollview-to-scroll-and-zoom-content into my solution.
The changes I had to make were:
Generating the image programmatically instead of using the storyboard
Adding scrollview min/max zoom init code in my awakeInNib method for my UICollectionView subclass
Generating the image in my cellForRowIndexPath
That's about it I think.

NSTableView crashes in Custom View that is added to a SplitView

My intent is to have 2 separate view controllers for a NSSplitView with two panes. In the left pane I want a table view, but it crashes. Here's the scenario:
I have a simple project with a MainMenu.xib, AppDelegate.h/m. In this project I add a LeftPaneViewController.h/.m/.xib.
In MainMenu.xib I add a NSSplitView to the default view.
In AppDelegate.h:
#property (weak) IBOutlet NSSplitView *splitView;
In AppDelegate.m:
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
// Insert code here to initialize your application
LeftPaneViewController *lpvc = [[LeftPaneViewController alloc] initWithNibName:#"LeftPaneViewController" bundle:nil];
[self.splitView replaceSubview:[[self.splitView subviews] objectAtIndex:0] with:lpvc.view];
}
I connect the NSSplitView in MainMenu.xib to splitView in the AppDelegate.
When I run this works fine, but of course there is nothing to see yet.
In LeftPaneViewController.xib I add a NSTableView to the default custom view. I delete one of the columns (the bottom one in the list).
In LeftPaneViewController.h:
#interface LeftPaneViewController : NSViewController <NSTableViewDelegate, NSTableViewDataSource>
#property (weak) IBOutlet NSTableView *tableView;
#property (nonatomic, retain) NSMutableArray *tableDataSource;
In LeftPaneViewController.m:
#import "LeftPaneViewController.h"
#interface LeftPaneViewController ()
#end
#implementation LeftPaneViewController
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Initialization code here.
}
return self;
}
- (void)awakeFromNib
{
self.tableDataSource = [[NSMutableArray alloc] initWithObjects:#"Row1", #"Row2", nil];
[self.tableView setNeedsDisplay];
}
- (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView
{
// This is a defensive move
// There are cases where the nib containing the NSTableView can be loaded before the data is populated
// by ensuring the count value is 0 and checking to see if the namesArray is not nil, the app
// is protecting itself agains that situation
NSInteger count=0;
if (self.tableDataSource)
count=[self.tableDataSource count];
return count;
}
- (NSView *)tableView:(NSTableView *)tableView viewForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row {
// Get an existing cell with the MyView identifier if it exists
NSTextField *result = [tableView makeViewWithIdentifier:#"LeftPaneCell" owner:self];
// There is no existing cell to reuse so create a new one
if (result == nil) {
// Create the new NSTextField with a frame of the {0,0} with the width of the table.
// Note that the height of the frame is not really relevant, because the row height will modify the height.
NSTextField *textField;
textField = [[NSTextField alloc] initWithFrame:NSMakeRect(2, 456, 125, 20)];
[textField setStringValue:#"My Label"];
[textField setBezeled:NO];
[textField setDrawsBackground:NO];
[textField setEditable:NO];
[textField setSelectable:NO];
[self.view addSubview:textField];
// The identifier of the NSTextField instance is set to MyView.
// This allows the cell to be reused.
result.identifier = #"LeftPaneCell";
}
// result is now guaranteed to be valid, either as a reused cell
// or as a new cell, so set the stringValue of the cell to the
// nameArray value at row
result.stringValue = [self.tableDataSource objectAtIndex:row];
// Return the result
return result;
}
#end
In the LeftPaneViewController.xib I hook the TableView dataSource and delegate to "File's Owner" (of LeftPaneViewController of course). and hook the tableView referencing outlet to the tableView in LeftPaneViewController.h. I checked these at least a thousand times ;-)
Then when I run I get the following crash log:
* thread #1: tid = 0x23427, 0x00007fff82893250 libobjc.A.dylib`objc_msgSend + 16, queue = 'com.apple.main-thread, stop reason = EXC_BAD_ACCESS (code=EXC_I386_GPFLT)
frame #0: 0x00007fff82893250 libobjc.A.dylib`objc_msgSend + 16
frame #1: 0x00007fff88f8d972 AppKit`-[NSTableRowData _addViewToRowView:atColumn:row:] + 324
frame #2: 0x00007fff88f8d63f AppKit`-[NSTableRowData _addViewsToRowView:atRow:] + 151
frame #3: 0x00007fff88f8bbd5 AppKit`-[NSTableRowData _addRowViewForVisibleRow:withPriorView:] + 415
frame #4: 0x00007fff88f8b95a AppKit`-[NSTableRowData _addRowViewForVisibleRow:withPriorRowIndex:inDictionary:withRowAnimation:] + 272
frame #5: 0x00007fff88f8ac29 AppKit`-[NSTableRowData _unsafeUpdateVisibleRowEntries] + 740
frame #6: 0x00007fff88f8a7c1 AppKit`-[NSTableRowData updateVisibleRowViews] + 119
frame #7: 0x00007fff88f625a7 AppKit`-[NSTableView layout] + 165
frame #8: 0x00007fff88f15e65 AppKit`-[NSView _layoutSubtreeHeedingRecursionGuard:] + 112
frame #9: 0x00007fff8d11b4a6 CoreFoundation`__NSArrayEnumerate + 582
frame #10: 0x00007fff88f15fc6 AppKit`-[NSView _layoutSubtreeHeedingRecursionGuard:] + 465
frame #11: 0x00007fff8d11b4a6 CoreFoundation`__NSArrayEnumerate + 582
frame #12: 0x00007fff88f15fc6 AppKit`-[NSView _layoutSubtreeHeedingRecursionGuard:] + 465
frame #13: 0x00007fff8d11b4a6 CoreFoundation`__NSArrayEnumerate + 582
frame #14: 0x00007fff88f15fc6 AppKit`-[NSView _layoutSubtreeHeedingRecursionGuard:] + 465
frame #15: 0x00007fff8d11b4a6 CoreFoundation`__NSArrayEnumerate + 582
frame #16: 0x00007fff88f15fc6 AppKit`-[NSView _layoutSubtreeHeedingRecursionGuard:] + 465
frame #17: 0x00007fff8d11b4a6 CoreFoundation`__NSArrayEnumerate + 582
frame #18: 0x00007fff88f15fc6 AppKit`-[NSView _layoutSubtreeHeedingRecursionGuard:] + 465
frame #19: 0x00007fff8d11b4a6 CoreFoundation`__NSArrayEnumerate + 582
frame #20: 0x00007fff88f15fc6 AppKit`-[NSView _layoutSubtreeHeedingRecursionGuard:] + 465
frame #21: 0x00007fff88f15cfe AppKit`-[NSView layoutSubtreeIfNeeded] + 615
frame #22: 0x00007fff88f114ac AppKit`-[NSWindow(NSConstraintBasedLayout) layoutIfNeeded] + 201
frame #23: 0x00007fff88e0b0a8 AppKit`_handleWindowNeedsDisplayOrLayoutOrUpdateConstraints + 446
frame #24: 0x00007fff893d6901 AppKit`__83-[NSWindow _postWindowNeedsDisplayOrLayoutOrUpdateConstraintsUnlessPostingDisabled]_block_invoke_01208 + 46
frame #25: 0x00007fff8d0e9417 CoreFoundation`__CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__ + 23
frame #26: 0x00007fff8d0e9381 CoreFoundation`__CFRunLoopDoObservers + 369
frame #27: 0x00007fff8d0c47b8 CoreFoundation`__CFRunLoopRun + 728
frame #28: 0x00007fff8d0c40e2 CoreFoundation`CFRunLoopRunSpecific + 290
frame #29: 0x00007fff8c17aeb4 HIToolbox`RunCurrentEventLoopInMode + 209
frame #30: 0x00007fff8c17ab94 HIToolbox`ReceiveNextEventCommon + 166
frame #31: 0x00007fff8c17aae3 HIToolbox`BlockUntilNextEventMatchingListInMode + 62
frame #32: 0x00007fff88e08533 AppKit`_DPSNextEvent + 685
frame #33: 0x00007fff88e07df2 AppKit`-[NSApplication nextEventMatchingMask:untilDate:inMode:dequeue:] + 128
frame #34: 0x00007fff88dff1a3 AppKit`-[NSApplication run] + 517
frame #35: 0x00007fff88da3bd6 AppKit`NSApplicationMain + 869
frame #36: 0x0000000100001872 TableView_In_SplitView`main(argc=3, argv=0x00007fff5fbff7e8) + 34 at main.m:13
frame #37: 0x00007fff84d4d7e1 libdyld.dylib`start + 1
With breakpoints I determine that in LeftPaneViewController.m the numberOfRowsInTableView is called and returns 2, but viewForTableColumn is not called.
The TableView is Cell Based so I do expect the viewForTableColumn to be called.
If I simply add a NSTableView to the SplitView in MainMenu.xib and transfer the same code into AppDelegate.h/.m it works fine (and changing the hooks as well of course). That is, it does not crash and both numberOfRowsInTableView and viewForTableColumn are called.
So what am I not doing that is causing the crash?
Ok, found the answer. Since the LeftPaneViewController variable (lpvc) in AppDelegate.m was local it was being released after the replaceSubview call. So adding it as (nonatomic, retain) in AppDelegate.h fixed the crash. Turned on Zombies to find the answer ;-).

Dynamically size uitableViewCell according to UILabel (With paragraph spacing)

I have a UITableView which is populated by text and images from a JSON file. The TableView Cell is currently sizing correctly for "posts" that do not contain many line breaks in the text however I cannot get it to calculate the correct height for "posts" with 4 or 5 line breaks.
Code for getting height:
-(float)height :(NSMutableAttributedString*)string
{
NSString *stringToSize = [NSString stringWithFormat:#"%#", string];
CGSize constraint = CGSizeMake(LABEL_WIDTH - (LABEL_MARGIN *2), 2000.f);
CGSize size = [stringToSize sizeWithFont:[UIFont systemFontOfSize:FONT_SIZE] constrainedToSize:contraint lineBreakMode:NSLineBreakByWordWrapping];
return size.height;
}
How do I calculate the correct size while allowing for line breaks and white space?
EDIT
The Rest of the method,
Inside of TableView CellForRow:
-(UITableViewCell*)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSString *row = [NSString stringWithFormat:#"%i", indexPath.row];
float postTextHeight = [self height:postText];
NSString *height = [NSString stringWithFormat:#"%f", heightOfPostText + 70];
[_cellSizes setObject:height forKey:row];
}
And the height of Table Cell:
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSString *imageHeightString = [NSString stringWithFormat:#"%#", [_cellSizes objectForKey:indexPath.row]];
float heightOfCell = [imageHeightString floatValue];
if (heightOfCell == 0) {
return 217;
};
return heightOfCell + 5;
}
better u need to calculate the height first, don't include the height calculation part in method:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
Better to calculate it in method:
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
since u are getting the data from json it is easy for u to calculate
in the "heightForRowAtIndexPath" method.
follwing code will give the example to calculate height of text change it ur requirement.
hopee this helps u :)
// i am using an array
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
UIFont *labelFont = [UIFont fontWithName:#"Noteworthy-Bold" size:20];
NSDictionary *arialdict = [NSDictionary dictionaryWithObject:labelFont forKey:NSFontAttributeName];
NSMutableAttributedString *message = [[NSMutableAttributedString alloc] initWithString:#"this is just the sample example of how to calculate the dynamic height for tableview cell which is of around 7 to 8 lines. you will need to set the height of this string first, not seems to be calculated in cellForRowAtIndexPath method." attributes:arialdict];
array = [NSMutableArray arrayWithObjects:message, nil];
NSMutableAttributedString *message_1 = [[NSMutableAttributedString alloc] initWithString:#"you will need to set the height of this string first, not seems to be calculated in cellForRowAtIndexPath method." attributes:arialdict];
[array addObject:message_1];
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 2;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return 2;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *Cell = [self.aTableView dequeueReusableCellWithIdentifier:#"cell"];
if(Cell == nil)
{
Cell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:#"cell"];
}
//dont include the height calculation part hear, becz heights are already set for all the cell
[Cell.textLabel sizeToFit];
Cell.textLabel.attributedText = [array objectAtIndex:indexPath.row]; // dont calculate height hear it will be called after "heightForRowAtIndexPath" method
Cell.textLabel.numberOfLines = 8;
return Cell;
}
// put ur height calculation method i took some hardcoded values change it :)
-(float)height :(NSMutableAttributedString*)string
{
/*
NSString *stringToSize = [NSString stringWithFormat:#"%#", string];
// CGSize constraint = CGSizeMake(LABEL_WIDTH - (LABEL_MARGIN *2), 2000.f);
CGSize maxSize = CGSizeMake(280, MAXFLOAT);//set max height //set the constant width, hear MAXFLOAT gives the maximum height
CGSize size = [stringToSize sizeWithFont:[UIFont systemFontOfSize:20.0f] constrainedToSize:maxSize lineBreakMode:NSLineBreakByWordWrapping];
return size.height; //finally u get the correct height
*/
//commenting the above code because "sizeWithFont: constrainedToSize:maxSize: lineBreakMode: " has been deprecated to avoid above code use below
NSAttributedString *attributedText = string;
CGRect rect = [attributedText boundingRectWithSize:(CGSize){225, MAXFLOAT}
options:NSStringDrawingUsesLineFragmentOrigin
context:nil];//you need to specify the some width, height will be calculated
CGSize requiredSize = rect.size;
return requiredSize.height; //finally u return your height
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
//whatever the height u need to calculate calculate hear only
CGFloat heightOfcell = [self height:[array objectAtIndex:indexPath.row]];
NSLog(#"%f",heightOfcell);
return heightOfcell;
}
Hope this helps u :)
For SWIFT version
class ViewController: UIViewController,UITableViewDataSource,UITableViewDelegate
{
var messageArray:[String] = [] //array to holde the response form son for example
override func viewDidLoad()
{
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
messageArray = ["One of the most interesting features of Newsstand is that once an asset downloading has started it will continue even if the application is suspended (that is: not running but still in memory) or it is terminated. Of course during while your app is suspended it will not receive any status update but it will be woken up in the background",
"In case that app has been terminated while downloading was in progress, the situation is different. Infact in the event of a finished downloading the app can not be simply woken up and the connection delegate finish download method called, as when an app is terminated its App delegate object doesn’t exist anymore. In such case the system will relaunch the app in the background.",
" If defined, this key will contain the array of all asset identifiers that caused the launch. From my tests it doesn’t seem this check is really required if you reconnect the pending downloading as explained in the next paragraph.",
]
}
func numberOfSectionsInTableView(tableView: UITableView) -> Int
{
return 1;
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{
return messageArray.count;
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
{
var cell:UITableViewCell? = tableView.dequeueReusableCellWithIdentifier("CELL") as? UITableViewCell;
if(cell == nil)
{
cell = UITableViewCell(style:UITableViewCellStyle.Default, reuseIdentifier: "CELL")
cell?.selectionStyle = UITableViewCellSelectionStyle.None
}
cell?.textLabel.font = UIFont.systemFontOfSize(15.0)
cell?.textLabel.sizeToFit()
cell?.textLabel.text = messageArray[indexPath.row]
cell?.textLabel.numberOfLines = 0
return cell!;
}
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat
{
var height:CGFloat = self.calculateHeightForString(messageArray[indexPath.row])
return height + 70.0
}
func calculateHeightForString(inString:String) -> CGFloat
{
var messageString = inString
var attributes = [UIFont(): UIFont.systemFontOfSize(15.0)]
var attrString:NSAttributedString? = NSAttributedString(string: messageString, attributes: attributes)
var rect:CGRect = attrString!.boundingRectWithSize(CGSizeMake(300.0,CGFloat.max), options: NSStringDrawingOptions.UsesLineFragmentOrigin, context:nil )
var requredSize:CGRect = rect
return requredSize.height //to include button's in your tableview
}
}
#Shan had a good answer but it didn't entirely worked for me.
This is the code I used for calculating the cell height
-(float)height :(NSMutableAttributedString*)string
{
CGRect rect = [string boundingRectWithSize:(CGSize){table.frame.size.width - 110, MAXFLOAT} options:NSStringDrawingUsesLineFragmentOrigin context:nil];
return rect.size.height;
}
I do the -110 because that will give equal space at sides top and bottom.
Hope this helps.
Implement this table view delegate method:
-tableView:heightForRowAtIndexPath:
https://developer.apple.com/library/ios/documentation/uikit/reference/UITableViewDelegate_Protocol/Reference/Reference.html#//apple_ref/occ/intfm/UITableViewDelegate/tableView:heightForRowAtIndexPath:
You'll call your method for determining the height and return that value with some extra padding if you wanted.
The approach I recommend is to set the text of an actual label and get the required height by calling sizeToFit. For this to work, you've got to set the label's numberOfLines property to 0 and set the desired maximum width.
When using this technique with table views, you can use the prototype cell method discussed here to calculate height using an actual cell.

UITableViewCell row height

Is there any such method or solution to auto adjust row height of tableview depending on content.I mean that I don't want to specify row height and want row height according to content of the row.If there is no such method then tell me the solution that how can I change height when the orientation of the device changes ?
You will have to find the the height of the your content and can use it
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
return "your content height + your padding height value";
}
On orientation change just reload your table view thats it;
check this out
// --dynamic cell height according to the text--
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSString *text = <your text>;
CGSize constraint = CGSizeMake(210, 20000.0f);
CGSize size = [text sizeWithFont:[UIFont fontWithName:#"Helvetica-Light" size:14] constrainedToSize:constraint lineBreakMode:UILineBreakModeWordWrap];
// constratins the size of the table row according to the text
CGFloat height = MAX(size.height,60);
return height + (15);
// return the height of the particular row in the table view
}
hope this helps
EDIT for the orientation have you tried this method?
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
// Return YES for supported orientations
return YES;
}
or else if your table cells are custom with labels and imageviews etc then you can use setAutoresizingMask: on each to auto adjust to orientations.
[yourview setAutoresizingMask:UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight];
You can implement the
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
delegate method of UITableView.
You should be able to find plenty of tutorials about it if you google it.
Here's one for example:
http://www.cimgf.com/2009/09/23/uitableviewcell-dynamic-height/
You HAVE to use:
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
Yea - I know you know this method very well, and I have read your question carefully.
You have to use that delegate method to dynamically control the height of the rows. My suggestion is to examine the content at each cell from your backend data model for the tableview and return a number that is appropriate for that cell something like:
NSDictionary *thisCell = (NSDictionary *)[myArray objectAtIndexPath:indexPath.row];
NSString *myCellContents = [thisCell valueForKey:#"MyStringIWantToCheckOut"];
if ([mycellContents length] > 25) {
return 80;
} else {
return 40;
}
As far as when orientation changes, you would have to do that in the delegate method:
-(void) willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation duration:(NSTimeInterval)duration
and then fire reloadData on the TableView.
- (CGFloat)tableView:(UITableView *)aTableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
switch (indexPath.row){
case 0:
if(indexPath.section == 0)
//title
return 75;
default:
return 75;
}
}
func tableView(_ heightForRowAttableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat
{
return UITableViewAutomaticDimension
}
for dynamic height for row otherwise you can give static height instead of "UITableViewAutomaticDimension"