I have a UITableView in an iPhone application which I am refreshing (by calling [self.tableView reloadData] in the action method for a UISegmentedControl dynamically embedded in one of the UITableView cells. The table view is refreshed to update a text value for one of the cells.
However, the following code seems to produce an unwanted side-effect. It appears that each time the UITableView refreshes it creates a new instance of the UISegmentedControl (and possibly the images - I'm not sure) over the existing one(s).
The only reason I notice this is that with each refresh a barely perceptible border starts to form around the UISegmentedControl and the application slows noticeably. I would be extremely grateful for any suggestions/code-solutions to my current predicament.
// Customize the appearance of table view cells.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier] autorelease];
}
NSUInteger section = indexPath.section;
NSUInteger row = indexPath.row;
// Set up the cell...
//populates the personal info section
if (section == kPersonalInfoAddSection) {
if (row == kNameRow) {
//Other code irrelevant to this question was removed for the sake of clarity
}
else if(row == kHeightRow) {
cell.imageView.image = [UIImage imageNamed:#"tableview_height_label.png"];
//THIS IS THE TEXT I'M TRYING TO UPDATE
cell.textLabel.text = [Formatter formatHeightValue:mainUser.heightInMM forZone:self.heightZone];
cell.detailTextLabel.text = REQUIRED_STRING;
}
}
//populates the units section
if (section == kUnitsSection) {
if (row == kHeightUnitsRow) {
NSArray *heightUnitsSegments = [[NSArray alloc] initWithObjects:FT_AND_IN_STRING, M_AND_CM_STRING, nil];
UISegmentedControl *heightUnitControl = [[UISegmentedControl alloc] initWithItems:heightUnitsSegments];
CGRect segmentRect = CGRectMake(90, 7, 200, 30);
[heightUnitControl setFrame:segmentRect];
//[heightUnitControl setSelectedSegmentIndex:0];
[heightUnitControl addTarget:self action:#selector(heightSegmentClicked:) forControlEvents:UIControlEventValueChanged];
heightUnitControl.tag = kHeightSegmentedControlTag;
cell.textLabel.text = #"Height:";
cell.detailTextLabel.text = #"(units)";
[cell.contentView addSubview:heightUnitControl];
[heightUnitsSegments release];
[heightUnitControl release];
}
else if(row == kWeightUnitsRow) {
//Other code irrelevant to this question was removed for the sake of clarity
}
}
return cell;
}
Thank you all in advance!
You're right, it is creating a new instance of the UISegmentedControl. It's because you are using a generic cell identifier, #"Cell", then adding the UISegmentedControl each time, never removing it. The cells get cached containing the UISegmentedControl, you retrieve the cached cell and add the control again.
You could use a more specific cell identifier and if cell != nil you know it contains the UISegmentedControl already. Or create a new cell each time that way you're not using a cached cell that already contains the control.
With the image view you just set the cells image view property without adding a new view to the cell so that one is ok, it gets replaced each time.
Since the text you are trying to update doesn't have to do with the UISegmentedControl I think you should be able to use a more specific cell identifier and add the control only on cell creation.
- (void)viewDidLoad {
[super viewDidLoad];
NSArray *arr1=[NSArray arrayWithObjects:#"img1.jpg",#"img2.jpg",nil];
NSArray *arr2=[NSArray arrayWithObjects:#"img1.jpg",#"img2.jpg",#"img3.jpg",#"img4.jpg",#"img5.jpg",#"img6.jpg",nil];
NSArray *arr3=[NSArray arrayWithObjects:#"img6.jpg",#"img5.jpg",#"img2.jpg",#"img1.jpg",nil];
Imgs = [[NSArray alloc] initWithArray:[NSArray arrayWithObjects:arr1,arr2,arr3,nil]];
NSDictionary *dic1=[NSDictionary dictionaryWithObjectsAndKeys:#"Ahmedabad",#"Name",#"Picture 5.png",#"Rating",#"Picture 1.png",#"Photo",arr1,#"img",nil];
NSDictionary *dic2=[NSDictionary dictionaryWithObjectsAndKeys:#"Rajkot",#"Name",#"Picture 5.png",#"Rating",#"Picture 2.png",#"Photo",nil];
NSDictionary *dic3=[NSDictionary dictionaryWithObjectsAndKeys:#"Baroda",#"Name",#"Picture 5.png",#"Rating",#"Picture 7.png",#"Photo",nil];
tblArray=[[NSArray alloc] initWithObjects:dic1,dic2,dic3,nil];
[myTbl reloadData];
}
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
self.navigationController.navigationBarHidden=NO;
[self.navigationController.navigationBar setUserInteractionEnabled:YES];
}
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
self.navigationController.navigationBarHidden=YES;
}
-(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView{
return 1;
}
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
return [tblArray count];
}
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
NSString *CellIdentifer=[NSString stringWithFormat:#"%i",indexPath.row];
UITableViewCell *cell=[tableView dequeueReusableCellWithIdentifier:CellIdentifer];
if(cell==nil){
cell=[self myCustomCell:CellIdentifer dicToSet:[tblArray objectAtIndex:indexPath.row]];
[cell setAccessoryType:UITableViewCellAccessoryDisclosureIndicator];
}
return cell;
}
-(UITableViewCell*)myCustomCell:(NSString*)CellIdentifer dicToSet:(NSDictionary*)dicToSet{
UITableViewCell *cell=[[[UITableViewCell alloc] initWithFrame:CGRectMake(0, 0, 320, 44) reuseIdentifier:CellIdentifer] autorelease];
UIImageView *imgV=[[UIImageView alloc] initWithFrame:CGRectMake(2, 2, 40, 40)];
[imgV setImage:[UIImage imageNamed:[dicToSet valueForKey:#"Photo"]]];
[cell addSubview:imgV];
[imgV release];
UILabel *lbl=[[UILabel alloc] initWithFrame:CGRectMake(44, 2, 276, 20)];
[lbl setText:[dicToSet valueForKey:#"Name"]];
[cell addSubview:lbl];
[lbl setBackgroundColor:[UIColor clearColor]];
[lbl setFont:[UIFont fontWithName:#"Helvetica-Bold" size:18]];
[lbl release];
UIImageView *imgV1=[[UIImageView alloc] initWithFrame:CGRectMake(44, 24, 70, 20)];
[imgV1 setImage:[UIImage imageNamed:[dicToSet valueForKey:#"Rating"]]];
[cell addSubview:imgV1];
[imgV1 release];
return cell;
}
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
nxtPlcDtl=[[plcFullDtl alloc] initWithNibName:#"plcFullDtl" bundle:nil];
nxtPlcDtl.dict=[[NSDictionary alloc] initWithDictionary:[tblArray objectAtIndex:indexPath.row]];
nxtPlcDtl.Imgs = [Imgs objectAtIndex:indexPath.row];
nxtPlcDtl.comment1 = [comment1 objectAtIndex:indexPath.row];
nxtPlcDtl.vedio = [vedio objectAtIndex:indexPath.row];
[self.navigationController pushViewController:nxtPlcDtl animated:YES];
}
Related
I have a table view that has many cells. Each cell has its own UITextField. I added the text fields programmatically. I want each and every textField to appear when the edit button is hit. (now table is in edit mode) and when pressed again, i want all of the textFields to vanish(leaving the edit mode). I know that i can accomplish this using the hidden property, but i tried doing this in this method:
- (IBAction)editButton:(id)sender
{
if (self.editing)
{
[self setEditing:NO animated:YES];
[self.myTableView setEditing:NO animated:YES];
EditButton.title = #"Edit";
cellText.hidden = YES; //<-- THIS IS THE CODE
}
else
{
[self setEditing:YES animated:YES];
[self.myTableView setEditing:YES animated:YES];
EditButton.title = #"Done";
cellText.hidden = NO; //<-- THIS IS THE CODE
}
}
but it only shows and hides the VERY LAST cell's textField. How can i get it to where it shows and then doesnt show EVERY cell's textFIeld? Many thanks in advance!!!
CELL FOR ROW
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath: (NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil)
{
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
cellText = [[UITextField alloc]init];
[cellText setFrame:CGRectMake(190, 15, 55, 30)];
cellText.text = #"1";
cellText.borderStyle = UITextBorderStyleRoundedRect;
cellText.hidden = YES;
cellText.userInteractionEnabled = NO;
[cell addSubview:cellText];
}
return cell;
}
Thanks in advance!! :D
You can get rid of this issue, using this trick, I am not sure as if it would create the memory leak within your code.Since, it create new cell each time.But surely you can use it,if you don't get some proper way of doing it. ;)
- (IBAction)editButton:(id)sender
{
if (self.editing)
{
[self setEditing:NO animated:YES];
[self.myTableView setEditing:NO animated:YES];
EditButton.title = #"Edit";
}
else
{
[self setEditing:YES animated:YES];
[self.myTableView setEditing:YES animated:YES];
EditButton.title = #"Done";
}
[self.myTableView reloadData];
}
After reloading the TableView, check the condition in cellForRowAtIndexPath, either pass the value of self.editing to TextField which makes it hide/show.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath: (NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
cellText = [[UITextField alloc]init];
[cellText setFrame:CGRectMake(190, 15, 55, 30)];
cellText.text = #"1";
cellText.borderStyle = UITextBorderStyleRoundedRect;
cellText.hidden = YES;
cellText.backgroundColor = [UIColor redColor];
cellText.userInteractionEnabled = NO;
[cell addSubview:cellText];
cellText.hidden=!self.editing;
return cell;
}
Although you create a text field for each cell, you keep a reference only to the last one in the ivar named cellText. That's why you show/hide the only text field.
I suggest you reload the table when toggling the editing mode, and set the text fields visibility in tableView:cellForRowAtIndexPath:.
Oh, and you should release cellText after adding it as a subview. Otherwise you are leaking memory. And it is highly recommended that you add subviews to UITableViewCell content view, rather than directly to the cell.
Try this code
- (void)viewDidLoad {
[super viewDidLoad];
self.navigationItem.rightBarButtonItem = self.editButtonItem;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
}
UITextField * cellText = [[UITextField alloc] initWithFrame:CGRectMake(1, 1, 100, 30)];
cellText.tag = 1;
cellText.textColor = [UIColor darkTextColor];
//cellText.numberOfLines = 0;
cellText.font = [ UIFont fontWithName: #"Helvetica-Bold" size: 12.0 ] ;
cellText.backgroundColor = [ UIColor clearColor ] ;
cellText.text = #"123";
cellText.hidden = YES;
[cell.contentView addSubview:cellText];
[cellText release];
cellText =nil;
// Set up the cell...
return cell;
}
- (UITableViewCellEditingStyle)tableView:(UITableView *)aTableView editingStyleForRowAtIndexPath:(NSIndexPath *)indexPath {
// Detemine if it's in editing mode
UITextField *cellText = (UITextField *)[[aTableView cellForRowAtIndexPath:indexPath] viewWithTag:1];
if (!self.editing)
{
[self setEditing:NO animated:YES];
[self.tableView setEditing:NO animated:YES];
// EditButton.title = #"Edit";
cellText.hidden = YES; //<-- THIS IS THE CODE
}
else
{
[self setEditing:YES animated:YES];
[self.tableView setEditing:YES animated:YES];
// EditButton.title = #"Done";
cellText.hidden = NO; //<-- THIS IS THE CODE
}
return UITableViewCellEditingStyleNone;
}
hi friend this code is working fine for me, believe you too have a grate day
This is actually normal. Per the Apple documentation under addSubview:
Views can have only one superview. If view already has a superview and
that view is not the receiver, this method removes the previous
superview before making the receiver its new superview.
So it will keep removing it adding and removing from cells until it gets to the last one.
i am new programmer of iphone App... i have 7 labels and 1 imageView in first cell of table view......
i write this code for that..... this work satisfactory...(may be it take times when scroll)
please tell me ...this is the right way to do this task or not....?
if not please...tell me ..the right way...
thanks in advance
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:[NSString stringWithFormat:#"Cell %i",indexPath.section]];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:[NSString stringWithFormat:#"Cell %i",indexPath.section]] autorelease];
}
if(indexPath.row==0)
{
CGRect frame=CGRectMake(120,10, 80, 40);
UILabel *label1=[[UILabel alloc]init];
label1.frame=frame;
label1.text=#"first label";
[cell.contentView addSubview:label1];
[label1 release];
CGRect frame2=CGRectMake(200,10, 80, 40);
UILabel *label2=[[UILabel alloc]init];
label2.frame=frame2;
label2.text=#"second label";
[cell.contentView addSubview:label2];
[label2 release];
and so on.......
}
else if(indexPath.row==1)
{
//add four labels for this cell here......
}
return cell;
}
When you are reusing cell you don't need to create labels second time:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:[NSString stringWithFormat:#"Cell %i",indexPath.section]];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:[NSString stringWithFormat:#"Cell %i",indexPath.section]] autorelease];
if(indexPath.row==0)
{
CGRect frame=CGRectMake(120,10, 80, 40);
UILabel *label1=[[UILabel alloc]init];
label1.frame=frame;
label1.text=#"first label";
label1.tag = 1001;
[cell.contentView addSubview:label1];
[label1 release];
CGRect frame2=CGRectMake(200,10, 80, 40);
UILabel *label2=[[UILabel alloc]init];
label2.frame=frame2;
label2.text=#"second label";
label2.tag = 1002;
[cell.contentView addSubview:label2];
[label2 release];
and so on.......
}
}
if(indexPath.row==0)
{
UILabel *label1=[cell viewWithTag:1001];
label1.text=#"first label";
UILabel *label2=[cell viewWithTag:1002];
label2.text=#"second label";
and so on.......
}
return cell;
}
I'm accessing previously created labels using there tag value.
I think, it will be much easier to design such a complicated cell in InterfaceBuilder. If you're using Storyboards, you can design custom cells right away in your table view. If you're using xibs, you can create a nib that will have a custom UITableViewCell instead of the table view, UIViewController as owner and declare subclass of UITableViewCell in your project. That should make your sufferings much easier =)
I am using MGTwitterEngine and have almost EVERYTHING figured out, but for some reason I cannot figure out how to return more tweets in my timeline! It receives around 20 tweets by the method I am using and if I add a integer for example: return count = 100;, I can tell I now have 100 cells when it loads and by the scroll indicator but when I scroll to that 18- 19 cell I get this error:
SIGABRT
-[NSMutableArray objectAtIndex:]: index 20 beyond bounds [0 .. 19]'
* Call stack at first throw:
I know what it means: theres no more information to receive at cell 20. I'm baffled and went in and out of MGTwitterEngine looking for a default tweet count but I can't find it. I am using the dump to create my NSStrings and the dump only seems to give around 20 tweets per login. Please help, any suggestions are good in my book! Thank you!
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
int count = [tweets count];
// Return just enough cells to fill the screen during loading ....
if (count == 0)
count = MyCustomRowCount;
else {
//Here is where I think I need to add a else return integer but dont know how
}
return count;
return [tweets count];
return [authors count];
return [avatarsURL count];
}
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
NSString *identifier = #"cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:identifier];
if (cell == nil) {
[cell autorelease];
}
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:#"cell"]autorelease];
/*
//Here it adds a nice shadow to the table view but will crash on rotation and send a
wird dump !!!????
tableView.layer.shadowColor = [[UIColor blackColor] CGColor];
tableView.layer.shadowOffset = CGSizeMake(1.0f, 1.0f);
tableView.layer.shadowRadius = 8.0f;
tableView.layer.shadowOpacity = 1.0f;
*/
[cell.textLabel setNumberOfLines:1];
[cell.textLabel setText:[(Tweet*)[authors objectAtIndex:indexPath.row] author]];
[cell.detailTextLabel setText:[(Tweet*)[tweets objectAtIndex:indexPath.row] tweet]];
[cell.detailTextLabel setNumberOfLines:10];
[cell.textLabel setTextColor:[UIColor darkGrayColor]];
[cell.textLabel setShadowColor:[UIColor whiteColor]];
[cell.textLabel setShadowOffset:CGSizeMake(0.5, 0.5)];
[cell.detailTextLabel setTextColor:[UIColor blackColor]];
//[cell.detailTextLabel setText:[(Tweet*)[retweetCount objectAtIndex:indexPath.row]
reTweetCount]];
[cell.textLabel setUserInteractionEnabled:YES];
[cell.contentView setMultipleTouchEnabled:YES];
// cell.text = [[NSString alloc] initWithFormat:#"Cell :%i", indexPath.row];
// Here we use the new provided setImageWithURL: method to load the web image with
SDWebImageManager
[cell.imageView setImageWithURL:[NSURL URLWithString:[(Tweet*)[avatarsURL
objectAtIndex:indexPath.row]avatarURL]]
placeholderImage:[UIImage imageNamed:#"avatar.png"]];
//add gradient to cell
UIImage *gradient = [UIImage imageNamed:#"gradientcell2.png"];
UIImageView *cellimage = [[UIImageView alloc] initWithImage:gradient];
cellimage.contentMode = UIViewContentModeScaleToFill;
cell.backgroundView = cellimage;
[cellimage release];
UIImage *selectedGradient = [UIImage imageNamed:#"selectedcell.png"];
UIImageView *selectedCell = [[UIImageView alloc] initWithImage:selectedGradient];
selectedCell.contentMode = UIViewContentModeScaleToFill;
cell.selectedBackgroundView = selectedCell;
[tableView setBackgroundColor:[UIColor clearColor]];
return cell;
}
//get cell accessory
-(UITableViewCellAccessoryType)tableView:(UITableView *)tableView
accessoryTypeForRowWithIndexPath:(NSIndexPath *)indexPath {
if(indexPath.row < [tweets count]){
}
return UITableViewCellAccessoryDisclosureIndicator;
}
// custom hieght
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath*)indexPath {
return 80;
}
//select tweet bring to detail view ..... Also bring in the Users information who made the tweet!
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath*)indexPath {
//Get the selected tweets
NSString *selectedTweet = [(Tweet*)[tweets objectAtIndex:indexPath.row] tweet];
NSString *selectedUser = [(Tweet*)[tweets objectAtIndex:indexPath.row] author];
NSString *selectedUserInfo = [(Tweet*)[tweets objectAtIndex:indexPath.row] user];
NSString *retweetCount = [(Tweet*)[tweets objectAtIndex:indexPath.row] reTweetCount ];
// NSString *selectedUserFriendsCount = [(Tweet*)[tweets objectAtIndex:indexPath.row] userFriendsCount];------HUH? NO soup for ME!
NSString *selectedUserFollowersCount = [(Tweet*)[tweets objectAtIndex:indexPath.row] userFollowersCount];
//Initialize the detail view controller and display it.
TwitterDetailViewController*dvController = [[TwitterDetailViewController alloc]
initWithNibName:#"TwitterDetailViewController" bundle:[NSBundle mainBundle]];
dvController.selectedTweet = selectedTweet;
dvController.selectedUser = selectedUser;
dvController.selectedUserInfo = selectedUserInfo;
// dvController.selectedUserFriendsCount = selectedUserFriendsCount;------Doesnt reconize the call for some odd reason!
dvController.selectedUserFollowersCount = selectedUserFollowersCount;
dvController.retweetCount = retweetCount;
dvController.modalTransitionStyle = UIModalTransitionStyleCrossDissolve;
[self presentModalViewController:dvController animated:YES];
//[self.navigationController pushViewController:webController animated:YES];-------would rather use navagation controller for several obvious reasons
[dvController release];
dvController = nil;
}
UITableView doesn't it like it when you gave it something inconsistant. If you change the model, you need to call [tableView reloadData] or if you want to animate the changes call [tableView beingUpdate][tableView endUpdate] with all the insert/remove cell operations in the middle.
On another note, I'm not sure why you doing here:
NSString *identifier = #"cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:identifier];
if (cell == nil) {
[cell autorelease];
}
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle
reuseIdentifier:#"cell"];
[cell autorelease];
First if (cell == nil), calling auto release on it won't do anything.
The reuse identifier thing is a flyweight pattern. If dequeueReusableCellWithIdentifier: returns nil, it means it doesn't have a cell object in the pool for you to use and you should alloc a new one for it. dequeueReusableCellWithIdentifier also already returns an autorelease object.
Instead try this:
NSString *identifier = #"mytweetcell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:identifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:identifier] autorelease];
}
i am using the code below to implement a volume view into a cell.
[[cell detailTextLabel] setText: #""];
MPVolumeView *systemVolumeSlider = [[MPVolumeView alloc] initWithFrame: CGRectMake(100, 10, 200, 100)];
[cell addSubview: systemVolumeSlider];
[self.view addSubview:cell];
[systemVolumeSlider release];
//[MPVolumeView release];
However I have a problem with it. Whenever i scroll up or down in the tableview the MPVolumeView will be added to some other cells aswell. How could I fix this?
As mentioned in the comments, the cell with the Volume control may get re-used for non-Volume cells so it needs to be removed if it already exists. An example of how this can be done:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:CellIdentifier] autorelease];
}
//remove the volume control (which we tagged as 10) if it already exists...
UIView *v = [cell.contentView viewWithTag:10];
[v removeFromSuperview];
cell.textLabel.text = #"some text";
if (indexPath.section == 7)
{
if (indexPath.row == 1)
{
cell.detailTextLabel.text = #"";
MPVolumeView *systemVolumeSlider = [[MPVolumeView alloc] initWithFrame:CGRectMake(100, 10, 200, 100)];
//set a tag so we can easily find it (to remove it)...
systemVolumeSlider.tag = 10;
[cell.contentView addSubview:systemVolumeSlider];
[systemVolumeSlider release];
return cell;
}
}
cell.detailTextLabel.text = #"detail";
return cell;
}
In your comments, it seems the volume control should only be on the 2nd row of the 8th section so the example is written that way. Modify as needed.
I am trying create an accordion type of uitableviewcell that, when the user selects the cell, it expands to display a detailed info view inline similar to how the digg app works. I initially tried replacing the current tablecell with a customcell in cellForRowAtIndex, however the animation looks a bit choppy as you can see the cell being replaced and overall the effect doesn't work too well.
If you look at the digg app and others who have done this it seems that they aren't replacing the current cell but instead perhaps adding a subview to the cell? The original cell however doesn't seem to animate at all and only the new view accordions into the table.
Does anyone have any ideas how to accomplish a similar effect?
I have made some progress using neha's method below and while the cell is animating the correct way it is wreaking havoc with the other cells in the table. What I have done is subclassed UITableViewCell with a custom class which contains an instance of a UIView which actually draws the view which I then add to the table cell's contentview.
- (void)setSelected:(BOOL)selected animated:(BOOL)animated {
if (selected) {
[self expandCell];
}
}
-(void)expandCell {
self.contentView.frame = CGRectMake(0.0, 0.0, self.contentView.bounds.size.width, 110);
}
Here are all the table delegate methods I am using:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
if (isSearching && indexPath.row == selectedIndex) {
static NSString *CellIdentifier = #"SearchCell";
CustomTableCell *cell = (CustomTableCell*)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[CustomTableCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
}
[cell setCustomTitle:[timeZoneNames objectAtIndex:indexPath.row] detail:[timeZoneNames objectAtIndex:indexPath.row]];
UILabel *theText = [[UILabel alloc] initWithFrame:CGRectMake(10.0, 10.0, cell.contentView.bounds.size.width -20, 22.0)];
theText.text = #"Title Text";
[cell.contentView addSubview:theText];
UITextField *textField = [[UITextField alloc] initWithFrame:CGRectMake(10.0, 10 + 46.0, cell.contentView.bounds.size.width - 20, 40.0)];
textField.borderStyle = UITextBorderStyleLine;
[cell.contentView addSubview:textField];
UILabel *testLabel = [[UILabel alloc] initWithFrame:CGRectMake(5.0, 88.0, cell.contentView.bounds.size.width - 20, 22.0)];
testLabel.text = [NSString stringWithFormat:#"Some text here"];
[cell.contentView addSubview:testLabel];
[theText release];
[textField release];
[testLabel release];
return cell;
} else {
static NSString *CellIdentifier = #"Cell";
CustomTableCell *cell = (CustomTableCell*)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[CustomTableCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
}
[cell setCustomTitle:[timeZoneNames objectAtIndex:indexPath.row] detail:[timeZoneNames objectAtIndex:indexPath.row]];
return cell;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
[tableView deselectRowAtIndexPath:indexPath animated:NO];
selectedIndex = indexPath.row;
isSearching = YES;
[tableView beginUpdates];
[tableView endUpdates];
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
if (isSearching && indexPath.row == selectedIndex) {
return 110;
}
return rowHeight;
}
It seems now that the cell is expanding but not actually being refreshed so the labels, and textfield aren't being shown. They do however show up when I scroll the cell off and on the screen.
Any ideas?
The Apple way to do is quite simple.
First, you'll need to save the selected indexPath row:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
self.selectedRowIndex = [indexPath retain];
[tableView beginUpdates];
[tableView endUpdates];
}
I'll explain the begin/end updated part later.
Then, when you have the currently selected index, you can tell the tableView that it should give that row more space.
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
//check if the index actually exists
if(selectedRowIndex && indexPath.row == selectedRowIndex.row) {
return 100;
}
return 44;
}
This will return height 100 for the selected cell.
Now we can go back to the begin/end updates. That block triggers the reload of all tableView geometry. Moreover, that block is animated, which eventually gives the impressions of the row expanding.
Pawel's beginUpdates/endUpdates trick is good, and I often use it. But in this case you simply need to reload the rows that are changing state, ensuring that you correctly reload them with the desired cell type, and that you return the correct new cell height.
Here is a complete working implementation of what I think you're trying to accomplish:
.h:
#import <UIKit/UIKit.h>
#interface ExpandingTableViewController : UITableViewController
{
}
#property (retain) NSIndexPath* selectedIndexPath;
#end
.m:
#implementation ExpandingTableViewController
#synthesize selectedIndexPath;
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
// Return the number of sections.
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
// Return the number of rows in the section.
return 10;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier1 = #"Cell1";
static NSString *CellIdentifier2 = #"Cell2";
UITableViewCell *cell;
NSIndexPath* indexPathSelected = self.selectedIndexPath;
if ( nil == indexPathSelected || [indexPathSelected compare: indexPath] != NSOrderedSame )
{
cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier1];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier1] autorelease];
}
cell.textLabel.text = [NSString stringWithFormat: #"cell %d", indexPath.row];
}
else
{
cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier2];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier2] autorelease];
}
cell.textLabel.text = [NSString stringWithFormat: #"cell %d", indexPath.row];
cell.detailTextLabel.text = [NSString stringWithFormat: #"(expanded!)", indexPath.row];
}
return cell;
}
#pragma mark -
#pragma mark Table view delegate
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
if ( self.selectedIndexPath != nil && [self.selectedIndexPath compare: indexPath] == NSOrderedSame )
{
return tableView.rowHeight * 2;
}
return tableView.rowHeight;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
NSArray* toReload = [NSArray arrayWithObjects: indexPath, self.selectedIndexPath, nil];
self.selectedIndexPath = indexPath;
[tableView reloadRowsAtIndexPaths: toReload withRowAnimation: UITableViewRowAnimationMiddle];
}
#pragma mark -
#pragma mark Memory management
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
}
- (void)viewDidUnload {
}
- (void)dealloc {
[super dealloc];
}
#end
If you don't want to reload the cell (you want to keep your existing cell and just change the size, and likely add/remove some subviews), then simply do the beginUpdates/endUpdates trick in didSelectRowAtIndexPath:, and call some method on your cell to incite the layout change. beginUpdates/endUpdates will prompt the tableView to re-query the heights for each cell - so be sure to return the correct value.
Create a class that subclasses UITableviewcell in your project. Create this class' nib and set its parent to be the class in your project with tableview and override its -
(void)setSelected:(BOOL)selected animated:(BOOL)animated
Write methods contractCell() and expandCell() in this class, and provide the height of the cells you want in expandCell method. Call this methods appropriately based on some flags set to identify wheather the cell is in expanded state or contracted state. Use your tableview's
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
method to handle selection of cells.
Replace your cellForRowAtIndexPath function with this one.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath
*)indexPath {
if (isSearching && indexPath.row == selectedIndex) {
static NSString *CellIdentifier = #"SearchCell";
CustomTableCell *cell = [[[CustomTableCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
[cell setCustomTitle:[timeZoneNames objectAtIndex:indexPath.row] detail:[timeZoneNames objectAtIndex:indexPath.row]];
UILabel *theText = [[UILabel alloc] initWithFrame:CGRectMake(10.0,
10.0, cell.contentView.bounds.size.width
-20, 22.0)];
theText.text = #"Title Text";
[cell.contentView addSubview:theText];
UITextField *textField = [[UITextField alloc] initWithFrame:CGRectMake(10.0, 10 +
46.0, cell.contentView.bounds.size.width - 20, 40.0)];
textField.borderStyle = UITextBorderStyleLine;
[cell.contentView addSubview:textField];
UILabel *testLabel = [[UILabel alloc] initWithFrame:CGRectMake(5.0,
88.0, cell.contentView.bounds.size.width - 20, 22.0)];
testLabel.text = [NSString stringWithFormat:#"Some text here"];
[cell.contentView addSubview:testLabel];
[theText release];
[textField release];
[testLabel release];
return cell;
} else {
static NSString *CellIdentifier = #"Cell";
CustomTableCell *cell = [[[CustomTableCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
[cell setCustomTitle:[timeZoneNames objectAtIndex:indexPath.row] detail:[timeZoneNames objectAtIndex:indexPath.row]];
return cell;
}
}
create array wof dictionary which have a key Select_sts which is 0 in start when click its change 1
accourding u change table
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section{
customView = [[UIView alloc] initWithFrame:CGRectMake(0.0, 0.0, 320.0, 40.0)];
UILabel * headerLabel = [[UILabel alloc] initWithFrame:CGRectZero];
headerLabel.backgroundColor = [UIColor clearColor];
headerLabel.opaque = NO;
headerLabel.textColor = [UIColor blackColor];
headerLabel.highlightedTextColor = [UIColor whiteColor];
headerLabel.font = [UIFont boldSystemFontOfSize:16];
headerLabel.frame = CGRectMake(5.0, 10.0, 300.0, 20.0);
headerLabel.text=[NSString stringWithFormat: #"PNR %#",[[record objectAtIndex:section] objectForKey:#"number"]];
customView.backgroundColor=[UIColor whiteColor];
btn_openClose.tag=section+10000;
btn_openClose.backgroundColor=[UIColor clearColor];
// [btn_openClose setImage:[UIImage imageNamed:#"down_arrow.png"] forState:UIControlStateNormal];
[btn_openClose addTarget:self action:#selector(collapseExpandButtonTap:) forControlEvents:UIControlEventTouchUpInside];
[customView addSubview:btn_openClose];
}
- (void) collapseExpandButtonTap:(id) sender{
int indexNo=[sender tag]-10000;
// NSLog(#"total_record %#",[total_record objectAtIndex:indexNo]);
NSMutableDictionary *mutDictionary = [[total_record objectAtIndex:indexNo] mutableCopy];
if([[mutDictionary objectForKey:#"Select_sts"] integerValue]==0)
[mutDictionary setObject:[NSNumber numberWithInt:1] forKey:#"√"];
else
[mutDictionary setObject:[NSNumber numberWithInt:0] forKey:#"Select_sts"];
[total_record replaceObjectAtIndex:indexNo withObject:mutDictionary];
// [table_view beginUpdates];
// [table_view reloadData];
// [table_view endUpdates];
NSMutableIndexSet *indetsetToUpdate = [[NSMutableIndexSet alloc]init];
[indetsetToUpdate addIndex:indexNo]; // [indetsetToUpdate addIndex:<#(NSUInteger)#>]
// You can add multiple indexes(sections) here.
[table_view reloadSections:indetsetToUpdate withRowAnimation:UITableViewRowAnimationFade];
}