I'm working on an iPhone app that parses an XML file into a UITableView. It lists all of the titles of items within the XML file, and I'm trying to have the cell expand and add the body content of the selected item when a specified cell is selected.
I can get the cell to expand correctly, but when I have had no luck adding the body content as a subview. When populating the cell at cellForRowAtIndexPath I can add the body content and it displays fine.
My question is, how do I add a subview to the selected cell after it has been selected?
My cellForRowAtIndexPath function, with the 'functioning' bodyLabel commented out:
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *cellID = #"Cell";
issue *curIssue = [[parser issues] objectAtIndex:indexPath.row];
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellID];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:cellID];
CGRect nameFrame = CGRectMake(0,2,300,15);
UILabel *nameLabel = [[UILabel alloc] initWithFrame:nameFrame];
nameLabel.tag = 1;
nameLabel.font = [UIFont boldSystemFontOfSize:12];
[cell.contentView addSubview:nameLabel];
CGRect bodyFrame = CGRectMake(0,16,300,60);
UILabel *bodyLabel = [[UILabel alloc] initWithFrame:bodyFrame];
bodyLabel.tag = 2;
bodyLabel.numberOfLines = 10;
bodyLabel.font = [UIFont systemFontOfSize:10];
bodyLabel.hidden = YES;
[cell.contentView addSubview:bodyLabel];
}
UILabel *nameLabel = (UILabel *)[cell.contentView viewWithTag:1];
nameLabel.text = [curIssue name];
UILabel *bodyLabel = (UILabel *)[cell.contentView viewWithTag:2];
bodyLabel.text = [curIssue body];
return cell;
}
Here is my heightForRowAtIndexPath function, where I'm trying to add the subview. When trying to execute, I receive a EXC_BAD_ACESS exception when trying to alloc the *bodyLabel element.
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSInteger defCellHeight = 15;
NSInteger heightModifier = 10;
if([self cellIsSelected:indexPath]){
return defCellHeight * heightModifier;
}
return defCellHeight;
}
My didSelectRowAtIndexPath function, which allows the cell to grow/shrink:
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
BOOL isSeld = ![self cellIsSelected:indexPath];
NSNumber *seldIndex = [NSNumber numberWithBool:isSeld];
[selectedIndexes setObject:seldIndex forKey:indexPath];
UILabel *label = (UILabel *)[tableView viewWithTag:2];
label.hidden = NO;
[curIssView beginUpdates];
[curIssView endUpdates];
}
And finally, the cellIsSelected helper function which returns true if the cell is selected:
-(bool) cellIsSelected:(NSIndexPath *)indexPath
{
NSNumber *selIndex = [selectedIndexes objectForKey:indexPath];
return selIndex == nil ? FALSE : [selIndex boolValue];
}
You can find the full source file here.
Seems like you're allocating and adding the same UILabel's multiple times. You should only have to do it once within the cellForRowAtIndexPath method. You might also want to set the bodyLabel to hidden in the cellForRowAtIndexPath method, then set it to not hidden when the cell has been selected. With something like:
bodyLabel.hidden = YES;
Another thing. Why are you deselecting the row within the didSelectRowAtIndexPath?
Why are you creating a UILabel in heightForRowAtIndexPath?
If you're trying to add it to the row, use the cellForRowAtIndexPath method you used above.
Simply adding a subview to the UITableViewCell or anywhere else in view with UIView's addSubview method inside of didSelectRowAtIndexPath will work for displaying a subview when a UITableView row is selected.
To add a subview to the cell when you select it, it's pretty straightforward:
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
/*
.. your other setup
*/
UITableViewCell* cell = [tableView cellForRowAtIndexPath:indexPath];
[cell.contentView addSubview:<your subview>];
}
Related
Edit:
Thanks everyone, but this has gotten to be too general as it is clear there are deeper issues at hand. I'm going to try to delete this question. I appreciate all of your help!
We have a large UITableViewCell with a UILabel inside and we want to detect the user's single tap or touch on that label. We're adding a UITapGestureRecognizer inside of our subclassed UITableViewCell:
CGRect frame = CGRectMake(0, 10, 150, 20);
self.titleLabel = [[UILabel alloc] initWithFrame:frame];
self.titleLabel.text = self.title;
self.titleLabel.userInteractionEnabled = YES;
UITapGestureRecognizer *singleTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(expandButtonTapped:)];
singleTap.numberOfTapsRequired = 1;
singleTap.numberOfTouchesRequired = 1;
[self.titleLabel addGestureRecognizer:singleTap];
[cell.contentView addSubview:self.titleLabel];
We've also tried setting the target to the cell's UITableViewController, but same result, the action doesn't get performed. When checking the debugger, the gesture is indeed there and attached to the label.
Edit: After more investigating, if we add a normal UIButton to the cell, it cannot be clicked. Doing more investigating, but here is the cellForRowAtIndexPath method:
- (UITableViewCell*)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
if (![self.metaDataSections count]) {
return nil;
}
ACMTableCellMetaData *metaData = [self metaDataForIndexPath:indexPath];
UITableViewCell *cell = [self.tableView dequeueReusableCellWithIdentifier:metaData.reuseIdentifier];
if (cell == nil) {
cell = [metaData createCell];
cell.selectionStyle = UITableViewCellSelectionStyleNone;
}
cell = [metaData updateCellWithCellForReuse:cell];
return cell;
}
The createCell method:
- (UITableViewCell *)createCell
{
UITableViewCell *cell = [super createCell];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:self.reuseIdentifier];
cell.frame = CGRectMake(CGRectGetMinX(cell.contentView.frame),
CGRectGetMinY(cell.contentView.frame),
CGRectGetWidth(cell.contentView.frame),
ACM_TABLE_CELL_HEIGHT);
[self setupExpandButtonInCell:cell];
}
return cell;
}
updateCell method:
- (UITableViewCell *)updateCellWithCellForReuse:(UITableViewCell *)cell {
UILabel * titleLabel = (UILabel *)[cell.contentView viewWithTag:TITLE_TAG];
titleLabel = self.titleLabel;
self.cell = [super updateCellWithCellForReuse:cell];
return self.cell;
}
I clipped out some code that I don't believe affects anything. The didSelectRowAtIndexPath that is being over ridden in the the subclassed tableview doesn't have anything that would prevent user taps. But strangely, if I put a break point there, it never gets hit when tapping the cells. So I believe there are other issues at play here. We can't see why this is the case however.
If you are writing this code in class, which inherits UITableViewCell, then instead of
[cell.contentView addSubview:self.titleLabel];
use
[self addSubView:self.titleLabel];
make sure to implement
-(void)expandButtonTapped:(parameter type)parameter{
}
in the same class.
I forgot to mention about
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
TableCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if(!cell)
{
cell = [[TableCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:#"Cell"];
}
// Configure the cell...
return cell;
}
I'm trying to add a UITextField as a subview to my table cells. The content of the text fields is fine until I start scrolling and the cells start to be reused. The images illustrate the problem.
At first, the blue values on the right in the UITextField are correct, i.e. the value corresponds to the row number. The second and third images, scrolled down and back up, show that the values are being reused in odd ways.
How do I avoid this? Using unique values for reuseIdentifier solves this problem, but obviously it's not very efficient.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
UITextField *numberTextField;
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
numberTextField = [[UITextField alloc] initWithFrame:CGRectMake(200, 10, 95, 30)];
numberTextField.adjustsFontSizeToFitWidth = YES;
numberTextField.textColor = [UIColor blueColor];
numberTextField.placeholder = #"Enter value";
numberTextField.keyboardType = UIKeyboardTypeDecimalPad;
numberTextField.tag = ([indexPath row]+1);
numberTextField.backgroundColor = [cell backgroundColor];
numberTextField.textAlignment = NSTextAlignmentRight;
numberTextField.clearButtonMode = UITextFieldViewModeNever;
numberTextField.clearsOnBeginEditing = YES;
[numberTextField setEnabled:YES];
[cell addSubview:numberTextField];
} else {
numberTextField = (UITextField *)[cell.contentView viewWithTag:([indexPath row]+1)];
}
cell.textLabel.text = [NSString stringWithFormat:#"Row %i",[indexPath row]+1];
numberTextField.text = [NSString stringWithFormat:#"Value: %i",[indexPath row]+1];
return cell;
}
The problem is you only assign the tag to the numberTextField when it is created. If it gets reused, it doesn't get its tag reassigned.
You should use a constant tag number for the UITextField instead of using row+1.
I am attempting to update a label inside a cell(note, this is NOT the cell's label text. Its another custom label inside of the cell) after the user selects a value from a previous screen and the nav controller popping them back.
However, when I call reloadData, instead of the label in the cell being cleaned and the new value being placed, its actually stacking on top of what was there already. Like if you took the number 200 and placed a 50 on top of it. You get a weird mesh of the 0 and 5 on top of each other.
Any ideas on how to adjust this? Do I have to reset the label's text to "" every view did appear? and if so, what's the best way to do this, I've tried in the cellForRowAtIndexPath method, but no change.
cellforRowAtIndexPath code
// Set up the cell...
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier: CellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
// get the dictionary object
NSDictionary *dictionary = [_groups objectAtIndex:indexPath.section];
NSArray *array = [dictionary objectForKey:#"key"];
NSString *cellValue = [array objectAtIndex:indexPath.row];
cell.textLabel.text = cellValue;
//label for currently selected/saved object
_currentSetting = [[UILabel alloc] initWithFrame:CGRectMake(160, 8, 115, 25)];
[_currentSetting setFont:[UIFont systemFontOfSize:14]];
_currentSetting.backgroundColor = [UIColor clearColor];
_currentSetting.textColor = [UIColor blueColor];
_currentSetting.textAlignment = NSTextAlignmentRight;
_currentSetting.text = [NSString stringWithFormat:#""];
_currentSetting.text = [NSString stringWithFormat:#"%# mi",[setting.val stringValue]];
[cell.contentView addSubview:_currentSetting];
return cell
You are recreating the label and re-adding it every time the cell gets refreshed. All of your cell subviews should only be added when you create the cell the first time.
So in your code you create a cell and all subviews the first time. Then if you need a new cell for scrolling or any other reason you get a reusable cell that has already had all the subviews added to it (re-usable...). Then you go through the process of adding the subviews (again) so now that cell contains the subviews from the previous owner (data) of that cell and the new owner (data) of that cell. That is why they appear stacked on top of eachother when you reload the data.
seudo code:
(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier: CellIdentifier];
if (cell == nil) {
//Add all subviews here
}
//Modify (only modify!!) all cell subviews here
return cell;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"Cell";
UILabel *customLabel;
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
customLabel = [[UILabel alloc] initWithFrame:CGRectMake(0,0,320,44)];
customLabel.tag = 123;
[cell addSubview:customLabel];
} else {
customLabel = (UILabel *)[cell viewWithTag:123];
}
customLabel.text = #"Some nice text";
return cell;
}
I am new to iphone.I have a small doubt (i.e),I have create a table view in that i am placing all my book names and download option to that particular book in each cell like as below
Genesis Download
Exodus Download
Leviticus Download
Here is the above Genesis,Exodus,Leviticus are booknames and download is the button for download that book like this i have 66 different books in table view.Here my question is when we click on download button i want to get the corresponding bookname of that tableview cell.
My code is like below
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
// Return the number of sections.
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return 66;
}
- (UITableViewCell *)tableView:(UITableView *)_tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
UIButton *downloadButton = nil;
//this is the custom cell i have created one class for this in that i am place the string titlelabel.
CustomCell *cell = [_tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil)
{
cell = [[CustomCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier];
downloadButton = [UIButton buttonWithType:UIButtonTypeCustom];
downloadButton.frame = CGRectMake(220,10,50,30);
[downloadButton setImage:[UIImage imageNamed:#"download.png"] forState:UIControlStateNormal];
[downloadButton addTarget:self action:#selector(downloadButtonClicked:) forControlEvents:UIControlEventTouchUpInside];
downloadButton.backgroundColor = [UIColor clearColor];
downloadButton.userInteractionEnabled = YES;
downloadButton.highlighted = YES;
[cell.contentView addSubview:downloadButton];
}
NSString *titleLabel = [[appDelegate getBookNames]objectAtIndex:indexPath.row];
cell.TitleLabel.text = titleLabel;
return cell;
}
-(void)downloadButtonClicked:(id)sender{
}
In the button action you can get the cell by accessing the superView
-(void)downloadButtonClicked:(id)sender{
UIButton *button = (UIButton*)sender;
UIView *view = button.superview; //Cell contentView
UITableViewCell *cell = (UITableViewCell *)view.superview;
cell.textLabel.text; //Cell Text
}
first set the tag of your label say it is 100.
//in the button click method...
UITableViewCell *cell = (UITableViewCell *)[[button superview] superview];
UILabel *lbl = (UILabel *)[cell viewWithTag:100];//this is for custom label
NSLog(#"label text =%#",lbl.text);
else NSLog(#"label text =%#",cell.titleLabel.text);
When you tap on the table view cell the Delegate function will be called and it will give you the index path section and row value . When you putting button on table cells just allocate the tag - indexpath.row to the button tag. Find this tag when you call the button function and find the aray index to get the value for that index on the array.
I am new to iphone.I am struck in my project at some task (i.e),I have a table view with 66 rows.In that i am placed different book names for each cell and place a download button to each book.My requirement is when we click on download button it shows the progress view in that particular cell only but i am getting in that particular cell but when i am drag the tableview it will shows the progress views in some that cells also.It is because of dequeue reusability concept but i dont know how to avoid this problem.I want even after drag the tableview it shows the progress view on the cell which i am click the download button (cell)
here is my code below..
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
// Return the number of sections.
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return 66;
}
- (UITableViewCell *)tableView:(UITableView *)_tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
UIButton *downloadButton = nil;
CustomCell *cell = [_tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil)
{
//here custom cell is another class in that we have the title label declaration
cell = [[CustomCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier];
downloadButton = [UIButton buttonWithType:UIButtonTypeCustom];
downloadButton.frame = CGRectMake(220,10,50,30);
[downloadButton setImage:[UIImage imageNamed:#"download.png"] forState:UIControlStateNormal];
[downloadButton addTarget:self action:#selector(downloadButtonClicked:) forControlEvents:UIControlEventTouchUpInside];
downloadButton.backgroundColor = [UIColor clearColor];
downloadButton.userInteractionEnabled = YES;
downloadButton.highlighted = YES;
downloadButton.tag = indexPath.row;
NSLog(#"tag is %d",indexPath.row);
[cell.contentView addSubview:downloadButton];
}
NSString *titleLabel = [[appDelegate getBookNames]objectAtIndex:indexPath.row];
cell.TitleLabel.text = titleLabel;
return cell;
}
-(void)downloadButtonClicked:(id)sender{
int index = [sender tag];
NSLog(#"index of the cell is %d",index);
UIButton *button = (UIButton*)sender;
UITableViewCell *cell = (UITableViewCell *)[[button superview] superview];
UILabel *titleLabel = (UILabel *)[cell viewWithTag:100];
NSLog(#"label text =%#",titleLabel.text);
selectedBookTitle = titleLabel.text;
NSString* documentsPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
NSMutableArray *allDownloadLinks;
biblePlayerViewController = [[BiblePlayerViewController alloc]init];
allDownloadLinks = [biblePlayerViewController allDownloadLinks];
NSLog(#"all Download Links are %#",allDownloadLinks);
biblePlayerViewController.indexOfSelectedBookTitle = [[appDelegate getBookNames]indexOfObject:selectedBookTitle];
Download* download = [Download downloadWithTitle:selectedBookTitle url:[NSURL URLWithString:[NSString stringWithFormat:#"http://www.audiotreasure.com/%#.zip",[allDownloadLinks objectAtIndex:(biblePlayerViewController.indexOfSelectedBookTitle)]]]PathtoSave:documentsPath];
[[DownloadManager sharedDownloadManager] queueDownload: download];
UITableViewCell *tableViewCell = [tableView cellForRowAtIndexPath:indexPath];
progressView.frame = CGRectMake(10, 40, 300, 20);
[tableViewCell.contentView addSubview:progressView];
}
screen shot of my project is [output of my above code which is in simulator]
you should nil the cell every time in cellForRow. This way it will not be re-used and allocated every time. It should work pretty fine in your case as your tableview is not very large. Just add the following line before cell == nil check:
cell = nil;
It should work now.
I have the same problem, one way to avoid it is to just lock the scrolling ability of the table during the download.