I have this in my controller:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"PostCell";
PostCell *postCell = (PostCell *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
// Configure the cell...
Post *post = self.displayPosts[indexPath.row];
[postCell setPost:post];
return postCell;
}
With [postCell setPost:post]; I send my custom cell model that its going to use.
- (void)setPost:(Post *)newPost
{
if (_post != newPost) {
_post = newPost;
}
[self setNeedsDisplay];
}
Now this is version that I have but I want to change it. Now I have [self setNeedsDisplay] which calls drawRect - I want to use subviews and dunno how to initiate that.
Where do I add subviews and if using IBOutlets where do I set subviews values (imageviews image, labels text etc...) based on that _post ??
Or if easier to point me how to add buttons to my cell with drawRect and how to detect touch on images and buttons inside drawRect? That way I wont need to change current version.
- (void)setPost:(Post *)newPost
{
if (_post != newPost) {
_post = newPost;
}
[self layoutSubviews];
[self refreshContent];
}
- (void)layoutSubviews
{
[super layoutSubviews];
if (self.titleLabel == nil) {
self.titleLabel = [[UILabel alloc] init];
[self addSubview:self.titleLabel];
}
if (self.imageView == nil) {
self.imageView = [[UIImageView alloc] init];
[self addSubview:self.imageView];
}
// here I also set frames of label and imageview
}
- (void)refreshContent
{
[self.titleLabel setText:_post.title];
[self.imageView setImage:self.post.image];
}
This is the way I've done it and it works great without any lags in UITableView.
- (void)setPost:(Post *)newPost
{
if (_post != newPost) {
_post = newPost;
}
//check subviews exist and set value by newPost, if need no view, add subview, if need value, set new value
//Following code is just sample.
if(!self.imageView){
self.imageView = [[UIImageView alloc] init];
[self.contentView addSubview:self.imageView];
}
if(newImage){
self.imageView = newImage;
}
[self setNeedsDisplay];
}
Related
First of all sorry for asking this as I know there are a lot of questions which are similar to this. But so far nothing has worked for me. I have a table view with two buttons. On click of the button it loads two different custom cells. But I can't seem to clear out the old cell values no matter what I try. I have tried the prepareForReuse method to clear the old views but I end up with nothing displaying on the cells.
Here is the code that I am using.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
if([[self.sections objectAtIndex:indexPath.section] count] > 0)
{
id object = [[self.sections objectAtIndex:indexPath.section] objectAtIndex:indexPath.row];
if (indexPath.section == self.sections.count-1)
{
if(self.CategoryButtonType == 0)
{
MediaListCarousel * cell = [self getMediaCellCarousel:object];
return cell;
}
else
{
NonMediaTableViewCell * cell = [self getNonMediaCell:object];
return cell;
}
}
else
{
//Do other stuff
}
}
return nil;
}
Here are the custom cells
-(NonMediaTableViewCell *)getNonMediaCell:(NSString *)name
{
NonMediaTableViewCell * cell = [self.tableView dequeueReusableCellWithIdentifier:nonmediaCellIdentifier];
if (cell == nil) {
cell = (NonMediaTableViewCell*)[VFHelper findCellWithClassName:[NonMediaTableViewCell class] nibName:#"NonMediaTableViewCell"];
cell.selectionStyle = UITableViewCellSelectionStyleNone;
}
[cell setNonMediaWithPackageName:name
delegate:self];
[cell setSelectionStyle:UITableViewCellSelectionStyleNone];
return cell;
}
-(MediaListCarousel *)getMediaCellCarousel:(NSString *)name
{
MediaListCarousel * cell = [self.tableView dequeueReusableCellWithIdentifier:mediaCellIdentifier];
if (cell == nil) {
cell = (MediaListCarousel*)[VFHelper findCellWithClassName:[MediaListCarousel class] nibName:#"MediaListCarousel"];
cell.selectionStyle = UITableViewCellSelectionStyleNone;
}
[cell setUp:name delegate:self];
[cell setSelectionStyle:UITableViewCellSelectionStyleNone];
return cell;
}
each of those custom cell has the prepareForReuse method as defined below.
- (void)prepareForReuse {
[super prepareForReuse];
// Clear contentView
BOOL hasContentView = [self.subviews containsObject:self.contentView];
if (hasContentView) {
for(UIView *subview in [self.contentView subviews])
{
[subview removeFromSuperview];
}
}
}
What happening when i add the removeFromSuperview in the prepareForReuse method I can empty view when I switch between the two views. If i dont use the prepareForReuse to remove the view I end up with views on top of each other when I click the different button. Can some one please help with this. Thanks in advance.
EDIT TO SHOW CUSTOM CELL CODE
-(void) getNonMediaCell:(NSString *)package
delegate:(NSObject <nonMediaCellDelegate> *)delegate
{
self.package = package;
[self loadValuefFromDB];
self.addonName = addonName;
_delegate = delegate;
[self.btnSeemore addTarget:self action:#selector(btnSeemoreClicked) forControlEvents:UIControlEventTouchUpInside];
if(_addons.isHidden)
{
_planDescTextView.hidden = YES;
_heightConstraintforPlanDescView.constant = 0;
_btnSeemore.imageView.transform = CGAffineTransformIdentity;
}
else
{
_planDescTextView.text = description;
CGSize neededSize = [_planDescTextView sizeThatFits:CGSizeMake(_planDescTextView.frame.size.width, CGFLOAT_MAX)];
_planDescTextView.hidden = NO;
_heightConstraintforPlanDescView.constant = neededSize.height + 5;
_btnSeemore.imageView.transform = CGAffineTransformMakeRotation(M_PI);
_btnSeemore.imageView.clipsToBounds = NO;
_btnSeemore.imageView.contentMode = UIViewContentModeCenter;
}
if(IsEmpty(addons.state))
{
_UIViewEntitlement.hidden = YES;
_UIViewBtnRepurchase.hidden = YES;
_seperatorView.hidden = YES;
[self resetCell];
[self setPriceLabelValue];
[self.purchaseBtn addTarget:self action:#selector(buttonClicked) forControlEvents:UIControlEventTouchUpInside];
self.purchaseBtn.accessibilityLabel = [NSString stringWithFormat:#"%#_Purcahse", addonName];
self.purchaseBtn.accessibilityValue = [NSString stringWithFormat:#"%#_Purcahse", addonName];
self.purchaseBtn.accessibilityIdentifier = [NSString stringWithFormat:#"%#_Purcahse", addonName];
self.recurringIndicatorImageView.hidden = NO;
[self addOnRecurringIndicatorImages];
}
else
{
_UIViewEntitlement.hidden = NO;
UIView *headerView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, self.frame.size.width, 20)];
VFAddonPlanentitlementCell * cell = (VFAddonPlanentitlementCell*)[VFHelper findCellWithClassName:[VFAddonPlanentitlementCell class] nibName:#"VFAddonPlanentitlementCell"];
[cell setFrame:CGRectMake(0, 0, self.frame.size.width, 20)];
if(!IsEmpty(_addons.remainingDays))
{
if(recurring || allowCancel)
{
if([[_addons.state uppercaseString] isEqualToString:#"ONHOLD"])
{
[cell setDaysRemainingColorWithTitleOnHold:#"Renews" daysRemainings:[_addons.remainingDays stringValue]];
}
else
{
[cell setRemainingColorWithTitle:#"Renews" daysRemainings:[_addons.remainingDays stringValue]];
}
}
else
{
[cell setDaysRemainingColorWithTitle:#"Time left" daysRemainings:[_addons.remainingDays stringValue]];
}
[headerView addSubview:cell];
}
[_UIViewEntitlement addSubview:headerView];
}
}
try this
-(void) getNonMediaCell:(NSString *)package
delegate:(NSObject <nonMediaCellDelegate> *)delegate
{
.....//other code
UIView *headerView = [_UIViewEntitlement viewWithTag:12345];
if(headerView == nil) {
headerView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, self.frame.size.width, 20)];
[headerView setTag:12345];//set tag
} else {
NSLog(#"headerView already exist");
}
//set values
.....//other code
}
and in reuse method
- (void)prepareForReuse {
[super prepareForReuse];
UIView *header = [_UIViewEntitlement viewWithTag:12345];//use same tag
[header removeFromSuperview];
}
if you are creating and adding multiple views programatically, then set tag to each views and remove those in reuse by accessing it with same tag value.
I create a cell using this func:
-(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
BOOL isFocusOn = [_userDefault boolForKey:#"mixFocusOn"];
CDCChannelStrip *cell = [collectionView dequeueReusableCellWithReuseIdentifier:#"cell" forIndexPath:indexPath];
if (isFocusOn == TRUE) {
NSNumber *setChan = [self.focusChannels objectAtIndex:indexPath.section];
NSInteger chanInt = [setChan intValue];
[cell initData:(chanInt)];
[self getParameters:(setChan)];
} else {
NSInteger chanInt = indexPath.section;
NSNumber *chanNum = [NSNumber numberWithInt:chanInt]; // doesnt matter
[cell initData:(chanInt)]; // init
[self getParameters:(chanNum)]; // params
}
[self.mixMonitorView setChannelsStripToType:(cell)];
cell.clipsToBounds = YES;
return cell;
}
The func in question is initData which looks like this:
- (void)initData:(NSInteger)channel {
self.isBus = false;
self.isSendChan = false;
self.recallSafeFade = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"recallSafe"]];
self.recallSafeFade.y = 80;
[self.recallSafeFade setUserInteractionEnabled:YES];
self.recallSafeHead = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"recallSafeHeadamp"]];
[self.recallSafeHead setUserInteractionEnabled:NO];
self.recallSafePan = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"recallSafePan"]];
self.recallSafePan.y = 768 - 141;
[self.recallSafePan setUserInteractionEnabled:YES];
self.channelNumber = channel;
[self setClipsToBounds:NO];
_background = [[UIView alloc] initWithFrame:CGRectMake(0, 80, 122, 547)];
[_background setBackgroundColor:[UIColor colorWithPatternImage:[UIImage imageNamed:#"singleFader"]]];
[self addSubview:_background];
[self addChannelName];
[self addInput];
[self addPan];
[self addOn];
[self addMeter];
[self addFader];
}
So the issue im having is, i need to call this func to setup my custom cell to look and function how i need it to. However, every time i call reload on the collectionview due to state changes, it layers the new views over the top of the old views (due to reocurring imageview alloc's)
So how can i apply this func to each cell automatically, yet not reapply it over the top every time i reload the cell?
Image of the issue ina view debugger here:
UICollectionViewCells are reused. So, avoid doing addSubView: (directly or indirectly as in your case: collectionView:cellForRowAtIndexPath: is calling initData: which is calling addSubView:), each time in collectionView:cellForItemAtIndexPath:.
You are using a UICollectionView and UICollectionViewCell only by code.
So there is no XIB/Storyboard were is the cell, and no IBOutlet.
According to the doc of dequeueReusableCellWithReuseIdentifier:forIndexPath:, that's the part you fall into:
If you registered a class for the specified identifier and a new cell
must be created, this method initializes the cell by calling its
initWithFrame: method.
So, in CDCChannelStrip.m, override initWithFrame: and initialize its properties (that are "always" visible/used).
-(id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self)
{
_myImageViewProperty = [[UIImageView alloc] initWithFrame:someFrame];
[self addSubview:_myImageViewProperty];
etc.
}
return self;
}
-(void)updateWithChannel:(NSInteger)channel
{
self.channelNumber = channel;
self.channelLabelName = #"Something";
//etc.
}
You can also use prepareForReuse to "reinit" the cell as "virgin" (like it was just after initWithFrame: for instance.
-(void)prepareForReuse
{
[super prepareForReuse];
self.channelLabelName = #"";
self.backgroundColor = [UIColor clearColor];
//etc.
[self.sometimesThatImageIsShown setHidden:YES];
}
Simplest way is to create a separate xib for cell or prototype cell. Design you cell there and in cellForItemAtIndexPath you just manipulate your views and set values to your labels, images and other controllers.
I have created a CheckBoxView control for my UITableViewCell. The problem I am facing is that once I checkmark one of the top rows and scrolls the same check mark is visible on the bottom rows. This is because of dequeueresuable rows feature and I want to know how can I fix it. Here is the implementation.
CheckBoxView.m:
-(instancetype) initWithCoder:(NSCoder *)aDecoder {
self = [super initWithCoder:aDecoder];
[self setup];
[self registerGesturesRecognizers];
return self;
}
-(void) registerGesturesRecognizers {
UITapGestureRecognizer *tapGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(checkBoxTapped:)];
[self addGestureRecognizer:tapGestureRecognizer];
}
-(void) checkBoxTapped:(UITapGestureRecognizer *) recognizer {
if(self.checkBoxViewSelectionChanged) {
if(!self.isChecked) {
self.checkBoxViewSelectionChanged(self,self.isChecked);
self.isChecked = YES;
}
else {
self.checkBoxViewSelectionChanged(self,self.isChecked);
}
}
}
-(void) check {
[_checkBoxImageView setImage:[UIImage imageNamed:#"small-check"]];
}
-(void) uncheck {
_checkBoxImageView.image = nil;
}
-(void) setup {
self.userInteractionEnabled = YES;
self.layer.borderWidth = 0.5f;
self.layer.borderColor = [UIColor lightGrayColor].CGColor;
_checkBoxImageView = [[UIImageView alloc] initWithFrame:CGRectMake(1, 0, 23, 23)];
[self addSubview:_checkBoxImageView];
}
And here is the cellForRowAtIndexPath method:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
SamplesTableViewCell *cell = (SamplesTableViewCell *) [tableView dequeueReusableCellWithIdentifier:#"SamplesTableViewCell" forIndexPath:indexPath];
Item *sample = [_samples objectAtIndex:[indexPath row]];
cell.productNameLabel.text = sample.product.name;
cell.productColorLabel.text = sample.productColor.name;
[cell.productImageView setImage:sample.productColor.image];
cell.checkboxView.checkBoxViewSelectionChanged = ^(CheckBoxView *checkBoxView, BOOL isChecked) {
if(!isChecked) {
[checkBoxView check];
checkBoxView.isChecked = YES;
}
else {
[checkBoxView uncheck];
checkBoxView.isChecked = NO;
}
};
return cell;
}
The CheckBoxView is actually a UIView on the Storyboard prototype cell whose class is set to CheckBoxView so it is not created dynamically in the cellForRowAtIndexPath event. When I run the above code and checkmark the top rows and scroll then the same checkmark appears on the lower rows.
UPDATE:
Here is my updated code but it still checks and unchecks the rows at the bottom.
cell.checkboxView.checkBoxViewSelectionChanged = ^{
if(!sample.isSelected) {
[sample setSelected:YES];
[cell.checkboxView check];
}
else
{
[sample setSelected:NO];
[cell.checkboxView uncheck];
}
};
Your UIView should not be maintaining the isChecked state for your checkbox. As you pointed out, due to cell reuse all the others will then be checked because the same UIView is being used.
Your model objects powering your data need to maintain the state, or your controller.
For example:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
SamplesTableViewCell *cell = (SamplesTableViewCell *) [tableView dequeueReusableCellWithIdentifier:#"SamplesTableViewCell" forIndexPath:indexPath];
// Some code...
if ([self.myDatasource[indexPath.row] isChecked]) {
cell.checkBoxView.isChecked = YES;
}
return cell;
}
Just some rough pseudocode, but hopefully you get the idea. Potential ways to implement this state maintained is either in the form of model objects, or in your UIViewController have an NSArray containing an NSDictionary representing each row in your UITableView, where each key-value pair maintains a particular state for your UITableViewCell.
I have a UIView on every UITableViewCell.. This UIView has a event for touch to make it grow in height.
When user taps on this UIView the grow animation works fine, and then i mark a flag to indicate that this cell have a bigger UIView.
But when i need recycle that cell and make her bigger automatically the UIView won't grows when the UITableView displays the cell again. If i change the backgroundColor it displays the change, but don't work if i change their frame height.
A sample code:
Custom UIViewController - UITableViewDelegate, UITableViewDataSource
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
if([_dataSource count] > 0)
{
Model *model = [_dataSource objectAtIndex:indexPath.row];
ModelCellView *cell = [model getViewFor: tableView];
return cell;
}
return nil;
}
My Model with data
- (ModelCellView*)getViewFor:(UITableView *)tableView
{
NSString *cellIdentifier = #"ModelCell";
ModelCellView *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
if (cell == nil)
{
cell = [[ModelCellView alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:cellIdentifier];
}
//Lots of code
// Reset actionbar (UIView) to specific size
[cell.actionBar resetToState: _actionBarOpened];
//Lots of code
return cell;
}
My custom UIView class (ActionBar) inside custom UITableViewCell
- (void)resetToState:(BOOL)opened;
{
if(opened)
{
_closed = NO;
//[self open]; //tried to change de frame height here, but dont work too.
}
else
{
_closed = YES;
//[self close]; //tried to change de frame height here, but dont work too.
}
[self setNeedsLayout];
}
- (void)layoutSubviews
{
if(!_closed)
{
// This dont work
int sizeToGrow = 60;
[self setFrame: CGRectMake(
_defaultPosition.x,
_defaultPosition.y - sizeToGrow,
_defaultSize.width,
_defaultSize.height + sizeToGrow)];
// This work
self.backgroundColor = [UIColor redColor];
// This work
[_saveButton setFrame: CGRectMake(
_saveButton.frame.origin.x,
INSIDE_VIEW_Y_POSITION,
_saveButton.frame.size.width,
_saveButton.frame.size.height)];
// This work
[_deleteButton setFrame: CGRectMake(
_deleteButton.frame.origin.x,
INSIDE_VIEW_Y_POSITION,
_deleteButton.frame.size.width,
_deleteButton.frame.size.height)];
}
else
{
// This dont work
[self setFrame: CGRectMake(
_defaultPosition.x,
_defaultPosition.y,
_defaultSize.width,
_defaultSize.height)];
// This work
self.backgroundColor = [UIColor greenColor];
// This work
[_saveButton setFrame: CGRectMake(
_saveButton.frame.origin.x,
OUTSIDE_VIEW_Y_POSITION,
_saveButton.frame.size.width,
_saveButton.frame.size.height)];
// This work
[_deleteButton setFrame: CGRectMake(
_deleteButton.frame.origin.x,
OUTSIDE_VIEW_Y_POSITION,
_deleteButton.frame.size.width,
_deleteButton.frame.size.height)];
}
[super layoutSubviews];
}
I am trying to dynamically resize some UITableViewCell's based on the height of UITextView's contained within them.
There's loads of solutions to this by keeping a pointer to the UITextView and getting it's content size in heightForRowAtIndexPath however when the whole table is created dynamically with an unknown number of rows and an unknown number of them rows contain UITextView's this just isn't possible.
It would be easy if I could call the cell in question during heightForRowAtIndexPath but that causes an infinite loop and crash as this method is called before any cell's are even created.
Any other solutions?
I am using a UITableViewCell subclass for my cell like this:
- (void)initalizeInputView {
// Initialization code
self.selectionStyle = UITableViewCellSelectionStyleNone;
self.textView = [[UITextView alloc] initWithFrame:CGRectZero];
self.textView.autocorrectionType = UITextAutocorrectionTypeDefault;
self.textView.autocapitalizationType = UITextAutocapitalizationTypeNone;
self.textView.textAlignment = NSTextAlignmentRight;
self.textView.textColor = [UIColor lightBlueColor];
self.textView.font = [UIFont fontWithName:#"HelveticaNeue-Light" size:17];
self.textView.autoresizingMask = UIViewAutoresizingFlexibleWidth;
self.textView.keyboardType = UIKeyboardTypeDefault;
[self addSubview:self.textView];
self.textView.delegate = self;
}
- (BOOL)resignFirstResponder {
if (_delegate && [_delegate respondsToSelector:#selector(tableViewCell:didEndEditingWithLongString:)]) {
[_delegate tableViewCell:self didEndEditingWithLongString:self.stringValue];
}
return [super resignFirstResponder];
}
- (void)setKeyboardType:(UIKeyboardType)keyboardType
{
self.textView.keyboardType = keyboardType;
}
- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
{
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if (self) {
[self initalizeInputView];
}
return self;
}
- (id)initWithCoder:(NSCoder *)aDecoder {
self = [super initWithCoder:aDecoder];
if (self) {
[self initalizeInputView];
}
return self;
}
- (void)setSelected:(BOOL)selected {
[super setSelected:selected];
if (selected) {
[self.textView becomeFirstResponder];
}
}
- (void)setSelected:(BOOL)selected animated:(BOOL)animated {
[super setSelected:selected animated:animated];
if (selected) {
[self.textView becomeFirstResponder];
}
}
- (void)setStringValue:(NSString *)value {
self.textView.text = value;
}
- (NSString *)stringValue {
return self.textView.text;
}
- (void)textViewDidBeginEditing:(UITextView *)textView
{
// For keyboard scroll
UITableView *tableView = (UITableView *)self.superview;
AppSetupViewController *parent = (AppSetupViewController *)_delegate;
parent.activeCellIndexPath = [tableView indexPathForCell:self];
}
- (void)textViewDidChange:(UITextView *)textView
{
if (textView.contentSize.height > contentRowHeight) {
contentRowHeight = textView.contentSize.height;
UITableView *tableView = (UITableView *)self.superview;
[tableView beginUpdates];
[tableView endUpdates];
[textView setFrame:CGRectMake(0, 0, 300.0, textView.contentSize.height)];
}
}
- (void)textViewDidEndEditing:(UITextView *)textView
{
if (_delegate && [_delegate respondsToSelector:#selector(tableViewCell:didEndEditingWithLongString:)]) {
[_delegate tableViewCell:self didEndEditingWithLongString:self.stringValue];
}
UITableView *tableView = (UITableView *)self.superview;
[tableView deselectRowAtIndexPath:[tableView indexPathForCell:self] animated:YES];
}
- (void)layoutSubviews {
[super layoutSubviews];
CGRect editFrame = CGRectInset(self.contentView.frame, 10, 10);
if (self.textLabel.text && [self.textLabel.text length] != 0) {
CGSize textSize = [self.textLabel sizeThatFits:CGSizeZero];
editFrame.origin.x += textSize.width + 10;
editFrame.size.width -= textSize.width + 10;
self.textView.textAlignment = NSTextAlignmentRight;
} else {
self.textView.textAlignment = NSTextAlignmentLeft;
}
self.textView.frame = editFrame;
}
Which is created in cellForRowAtIndexPath like this:
else if ([paramType isEqualToString:#"longString"]) {
MyIdentifier = #"AppActionLongString";
LongStringInputTableViewCell *cell = (LongStringInputTableViewCell *)[tableView dequeueReusableCellWithIdentifier:MyIdentifier];
cell.textLabel.text = [[[_selectedAction objectForKey:#"parameters"] objectAtIndex:indexPath.row] objectForKey:#"name"];
cell.params = [[_selectedAction objectForKey:#"parameters"] objectAtIndex:indexPath.row];
cell.textView.text = [results objectAtIndex:indexPath.row];
return cell;
}
Simply passing back the height to a variable in my ViewController is no good because like I said, there could be several of these cells within the table.
Thanks
Use this method to dynamically resize your tableviewCell. First store the user input in NSMutable Array and after that reload table. Hope it will help you.
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{
NSString *msg =[self.messages objectAtIndex:indexPath.row];
CGSize textSize = { 120, 10000.0 };
CGSize size = [msg sizeWithFont:[UIFont systemFontOfSize:15]
constrainedToSize:textSize
lineBreakMode:UILineBreakModeWordWrap];
return size.height+20;
}
I needed a dynamic table view cell height based on the amount of text to be displayed in that cell. I solved it in this way:
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
if (!isLoading)
{
if ([self.conditionsDataArray count]>0)
{
Conditions *condition =[self.conditionsDataArray objectAtIndex:indexPath.row];
int height;
UITextView *textview = [[UITextView alloc] initWithFrame:CGRectMake(0, 0, 236, 0)]; //you can set your frame according to your need
textview.text = condition.comment;
textview.autoresizingMask = UIViewAutoresizingFlexibleHeight;
[tableView addSubview:textview];
textview.hidden = YES;
height = textview.contentSize.height;
NSLog(#"TEXT VIEW HEIGHT %f", textview.contentSize.height);
[textview removeFromSuperview];
[textview release];
return height;
}
return 55; //Default height, if data is in loading state
}
Notice that the Text View has been added as Subview and then made hidden, so make sure you add it as SubView otherwise it's height will not be considered.
It would be easy if I could call the cell in question during heightForRowAtIndexPath but that causes an infinite loop and crash as this method is called before any cell's are even created. Any other solutions?
You can. I would guess you're attempting to call cellForRowAtIndexPath, which will cause an infinite loop. But you should rather be dequeuing the cell directly by calling dequeueReusableCellWithIdentifier.
See the table view delegate implementation of TLIndexPathTools. The heightForRowAtIndexPath method looks like this:
(EDIT Initially forgot to include the method prototypeForCellIdentifier that actually dequeues the cell.)
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
id item = [self.dataModel itemAtIndexPath:indexPath];
NSString *cellId = [self cellIdentifierAtIndexPath:indexPath];
if (cellId) {
UITableViewCell *cell = [self prototypeForCellIdentifier:cellId];
if ([cell conformsToProtocol:#protocol(TLDynamicSizeView)]) {
id<TLDynamicSizeView> v = (id<TLDynamicSizeView>)cell;
id data;
if ([item isKindOfClass:[TLIndexPathItem class]]) {
TLIndexPathItem *i = (TLIndexPathItem *)item;
data = i.data;
} else {
data = item;
}
CGSize computedSize = [v sizeWithData:data];
return computedSize.height;
} else {
return cell.bounds.size.height;
}
}
return 44.0;
}
- (UITableViewCell *)tableView:(UITableView *)tableView prototypeForCellIdentifier:(NSString *)cellIdentifier
{
UITableViewCell *cell;
if (cellIdentifier) {
cell = [self.prototypeCells objectForKey:cellIdentifier];
if (!cell) {
if (!self.prototypeCells) {
self.prototypeCells = [[NSMutableDictionary alloc] init];
}
cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
//TODO this will fail if multiple tables are being used and they have
//overlapping identifiers. The key needs to be unique to the table
[self.prototypeCells setObject:cell forKey:cellIdentifier];
}
}
return cell;
}
This uses a protocol TLDynamicSizeView that any cell can implement to have it's height calculated automatically. Here is a working example project. The cell's implementation of the protocol looks like this:
#implementation DynamicHeightCell
- (void)awakeFromNib
{
[super awakeFromNib];
self.originalSize = self.bounds.size;
self.originalLabelSize = self.label.bounds.size;
}
- (void)configureWithText:(NSString *)text
{
self.label.text = text;
[self.label sizeToFit];
}
#pragma mark - TLDynamicSizeView
- (CGSize)sizeWithData:(id)data
{
[self configureWithText:data];
//the dynamic size is calculated by taking the original size and incrementing
//by the change in the label's size after configuring
CGSize labelSize = self.label.bounds.size;
CGSize size = self.originalSize;
size.width += labelSize.width - self.originalLabelSize.width;
size.height += labelSize.height - self.originalLabelSize.height;
return size;
}
#end
just comment
if (cell == nil)
Hope, this will help you.