I have created cell that can be expanded and collapsed, when the cell is expanded I add 2 subviews and remove those 2 subviews when cell is collapsed. Look at the code:
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
if(selectedIndex == indexPath.row){
selectedIndex = -1;
UITableViewCell *cell = [self.tblView cellForRowAtIndexPath:indexPath];
[[cell viewWithTag:TAG_KHMER] removeFromSuperview];
[[cell viewWithTag:TAG_KOREAN] removeFromSuperview];
//[tableView reloadRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
[self.tblView beginUpdates];
[self.tblView endUpdates];
return;
}
if(selectedIndex >= 0){
NSIndexPath *previousPath = [NSIndexPath indexPathForRow:selectedIndex inSection:0];
selectedIndex = indexPath.row;
UITableViewCell *cell = [self.tblView cellForRowAtIndexPath:previousPath];
[[cell viewWithTag:TAG_KHMER] removeFromSuperview];
[[cell viewWithTag:TAG_KOREAN] removeFromSuperview];
//[tableView reloadRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
}
VocabularyController *vc = [self.vocabularyInfo objectAtIndex:indexPath.row];
UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:indexPath];
UILabel *khmerLabel = [[UILabel alloc] init];
khmerLabel.text = vc.khmer;
khmerLabel.font = [UIFont fontWithName:#"Hanuman" size:17];
[khmerLabel setNumberOfLines:0];
khmerLabel.tag = TAG_KHMER;
khmerLabel.frame = CGRectMake(20, 45, 300, 300);
UILabel *koreanPro = [[UILabel alloc] init];
koreanPro.text = vc.korean;
[koreanPro setNumberOfLines: 0];
koreanPro.tag = TAG_KOREAN;
koreanPro.frame = CGRectMake(20, 315, 300, 300);
[cell addSubview:khmerLabel];
[cell addSubview:koreanPro];
selectedIndex = indexPath.row;
//[tableView reloadRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
[self.tblView beginUpdates];
[self.tblView endUpdates];
}
What happened is the cell seem not remove the previous one. it displays new text on the old text but when I click on the same cell twice again and then the cell can render the text good.
Can anyone help me how to display it properly.
After click twice times on the cell.
Don't try and add subviews like that - it's going to lead to confusion, because as you've found out UITableView recycles the cells.
Instead, create your own custom UITableViewCell subclass that can be switched between the various states you require, and has all the subviews already set up. You can do this in a number of ways - if you're using storyboards you can use prototype cells, or you can use a NIB, or you can create your custom subclass entirely in code (whichever you are most comfortable with).
Basically, don't add subviews to your cells in your table view delegate/datasource calls. Create a custom subclass, and you'll find everything much, much easier.
Related
Say i’m loading UITableview with each UITextView inside each cell as subview.And i’ve assigned indexPath.row as tags for each textview.
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"userDetails";
UITableViewCell *cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
UITextView *textView=[[UITextView alloc]initWithFrame:CGRectMake(0, 0, self.frame.size.width, 60)];
NSString * myString = [contentArray1 objectAtIndex:indexPath.row];
textView.text= myString;
UITapGestureRecognizer *tapRecognizer = [[UITapGestureRecognizer alloc]
[tapRecognizer setNumberOfTouchesRequired:1];
[tapRecognizer setDelegate:self];
textView.userInteractionEnabled = YES;
textView.tag = indexPath.row;//assign tags to textview
[textView addGestureRecognizer:tapRecognizer];
[cell addSubview:textView];
return cell;
}
Below method gets called once the user taps on any textview.I’m seeing proper tag values printed when i tap on any textviews.
-(void) action:(id)sender
{
//NSLog(#"TESTING TAP");
UITapGestureRecognizer *tapRecognizer = (UITapGestureRecognizer *)sender;
NSLog (#"%d",[tapRecognizer.view tag]);
}
Now i would like to insert row in my tableview,say at index 3.
What i did is simple,
[contentArray1 insertObject:[NSString stringWithFormat:#"added cell”] atIndex:3];
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:3 inSection:0];
[self.tableView insertRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
Now when i try to tap on any textviews after the inserted cell,i’m able to see the old tag values.Meaning,after row gets inserted to tableview at index=3,when i tap on textview i can see tag=2,then again when i tap on next cell’s textview i can see tag=2,it should be 3.
My question is,once we insert any row/cell in tableview,the tableview will not refresh other cell tags/index?….
I can fix it by calling reloadVisibleCells method.But i’m looking out for better solution.I don’t want to refresh whole screen just for inserting a row.Any solutions would be greatly appreciated.
Try to do it like this:
[self.tableView beginUpdates];
[self.tableView insertRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
[self.tableView endUpdates];
Your problem is that inserting/deleting rows doesn't cause reloading of other rows, and this is the expected and correct behavior. However, since -tableView:cellForRowAtIndexPath: is not called for those other rows, they are still configured with old (now obsolete) tags.
You can fix it in a number of ways (off the top of my head):
subclass UITableViewCell and store the represented object itself as its property (instead of the object's index)
associate the object with the UITableViewCell with objc_setAssociatedObject()
use -[UITableView indexPathForCell:] instead of tags to figure out a cell's real index path.
I have an UITableView with custom cells and custom headers. When I move one cell upon editing, it pops up on to of the header view.
How can I keep the header view on top of all the cells?
The app uses storyboard, in case that makes a difference.
This is how it looks? https://www.dropbox.com/s/wg8oiar0d9oytux/iOS%20SimulatorScreenSnapz003.mov
This is my code:
[...]
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"ListCell";
ListCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
int handleSection = [self sectionToHandle:indexPath.section];
switch (handleSection)
{
case PrivateLists:
{
if (tableView.isEditing && (indexPath.row == self.privateLists.count))
{
cell.textField.text = NSLocalizedString(#"Lägg till ny lista", nil);
cell.textField.enabled = NO;
cell.textField.userInteractionEnabled = NO;
cell.accessoryType = UITableViewCellAccessoryNone;
cell.editingAccessoryView.hidden = YES;
}
else
{
List *list = [self.privateLists objectAtIndex:indexPath.row];
cell.textField.text = list.name;
cell.textField.enabled = YES;
cell.textField.userInteractionEnabled =YES;
cell.backgroundColor = [UIColor clearColor];
cell.onTextEntered = ^(NSString* enteredString){
list.name = enteredString;
UpdateListService *service = [[UpdateListService alloc]initServiceWithList:list];
[service updatelistOnCompletion:
^(BOOL success){
DLog(#"Updated list");
NSIndexPath *newPath = [NSIndexPath indexPathForRow:0 inSection:indexPath.section];
[self.tableView moveRowAtIndexPath:indexPath toIndexPath:newPath];
[self moveListToTop:list.ListId newIndexPath:newPath];
justMovedWithoutSectionUpdate = YES;
}
onError:
^(NSError *error){
[[ActivityIndicator sharedInstance] hide];
[[ErrorHandler sharedInstance]handleError:error fromSender:self];
}];
};
}
}
break;
default:
return 0;
break;
}
return cell;
}
-(UIView *)tableView:(UITableView *)aTableView viewForHeaderInSection:(NSInteger)section
{
UIView *view = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 320, 22)];
UILabel *textLabel = [[UILabel alloc] initWithFrame:CGRectMake(10, 0, 300, 21)];
[textLabel setFont:[[AXThemeManager sharedTheme]headerFontWithSize:15.0]];
[textLabel setTextColor:[[AXThemeManager sharedTheme]highlightColor]];
[textLabel setText:#"SECTION TITLE"];
[textLabel setBackgroundColor:[UIColor clearColor]];
UIImageView *backgroundView = [[UIImageView alloc]initWithImage:[AXThemeManager sharedTheme].tableviewSectionHeaderBackgroundImage];
[backgroundView setFrame:view.frame];
[view addSubview:backgroundView];
[view sendSubviewToBack:backgroundView];
[view addSubview:textLabel];
return view;
}
- (float)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section
{
return 22;
}
- (float)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
return 44;
}
[...]
Good news! I was able to fix/workaround your problem in two different ways (see below).
I would say this is certainly an OS bug. What you are doing causes the cell you have moved (using moveRowAtIndexPath:) to be placed above (in front of) the header cell in the z-order.
I was able to repro the problem in OS 5 and 6, with cells that did and didn't have UITextFields, and with the tableView in and out of edit mode (in your video it is in edit mode, I noticed). It also happens even if you are using standard section headers.
Paul, you say in one of your comments:
I solved it badly using a loader and "locking" the table while
preforming a reloadData
I am not sure what you mean by "using a loader and locking the table", but I did determine that calling reloadData after moveRowAtIndexPath: does fix the problem. Is that not something you want to do?
[self.tableView moveRowAtIndexPath:indexPath toIndexPath:newPath];
//[self.tableView reloadData];
// per reply by Umka, below, reloadSections works and is lighter than reloadData:
[self reloadSections:[NSIndexSet indexSetWithIndex:indexPath.section] withRowAnimation:UITableViewRowAnimationNone];
If you dont want to do that, here is another solution that feels a little hacky to me, but seems to work well (iOS 5+):
__weak UITableViewCell* blockCell = cell; // so we can refer to cell in the block below without a retain loop warning.
...
cell.onTextEntered = ^(NSString* sText)
{
// move item in my model
NSIndexPath *newPath = [NSIndexPath indexPathForRow:0 inSection:indexPath.section];
[self.itemNames removeObjectAtIndex:indexPath.row];
[self.itemNames insertObject:sText atIndex:0];
// Then you can move cell to back
[self.tableView moveRowAtIndexPath:indexPath toIndexPath:newPath];
[self.tableView sendSubviewToBack:blockCell]; // a little hacky
// OR per #Lombax, move header to front
UIView *sectionView = [self.tableView headerViewForSection:indexPath.section];
[self.tableView bringSubviewToFront:sectionView];
It's a bug.
You can quickly solve it by adding, after the line:
[self.tableView moveRowAtIndexPath:indexPath toIndexPath:newPath];
this lines:
UIView *sectionView = [self.tableView headerViewForSection:indexPath.section];
[self.tableView bringSubviewToFront:sectionView];
Not a solution but your code has number of issues. Who knows what happens if you fix them ;)
(1) Your cell may be nil after this line:
ListCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
It should look like this:
ListCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[ListCell alloc] initWithStyle:style
reuseIdentifier:cellIdentifier] autorelease];
}
(2) Two memory leaks in
-(UIView *)tableView:(UITableView *)aTableView viewForHeaderInSection:(NSInteger)section
->>Fix (When you add the label as subview it gets +1 ref).
UILabel *textLabel = [[[UILabel alloc] initWithFrame:CGRectMake(10, 0, 300, 21)] autorelease];
->>Fix (When you add the view as subview it gets +1 ref).
UIImageView *backgroundView = [[[UIImageView alloc]initWithImage:[AXThemeManager sharedTheme].tableviewSectionHeaderBackgroundImage] autorelease];
(3) Not a defect but this may help you. Try using this instead of [table reloadData]. It allows to animate things nicely and is not such a hardcore way to update the table. I'm sure it is much more lightweight. Alternatively try to look for other "update" methods. Given you don't delete rows in your example, something like [updateRowsFrom:idxFrom to:idxTo] would help.
[self.tableView reloadSections:[NSIndexSet indexSetWithIndex:0]
withRowAnimation:UITableViewRowAnimationAutomatic];
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
float heightForHeader = 40.0;
if (scrollView.contentOffset.y<=heightForHeader&&scrollView.contentOffset.y>=0) {
scrollView.contentInset = UIEdgeInsetsMake(-scrollView.contentOffset.y, 0, 0, 0);
} else if (scrollView.contentOffset.y>=heightForHeader) {
scrollView.contentInset = UIEdgeInsetsMake(-heightForHeader, 0, 0, 0);
}
}
What I did to solve this problem was to set the zPosition of the view in the section header.
headerView.layer.zPosition = 1000 //just set a bigger number, it will show on top of all other cells.
I have a table view in which each cell contains a button. I dont use the didSelectRowAtIndexPath delegate but my custom method for the button action. I want that on clicking the button inside each cell, that cell should be highlighted or change colour and return to its normal state(colour) when the button in some other cell is clicked(making this cell highlighted).My button action method is:
- (void)SelectButtonTapped:(UIButton *)button
{
self.tabBarController.tabBar.userInteractionEnabled = YES;
AppDelegate *proDel = [[UIApplication sharedApplication]delegate];
UITableViewCell *cell = (UITableViewCell *)button.superview.superview;
//MyCustomCell *cell = (MyCustomCell *)button.superview.superview;
NSIndexPath *indexPath = [homeTable indexPathForCell:cell];
//cell.backgroundColor = [UIColor lightGrayColor];
//[homeTable setNeedsDisplayInRect:[homeTable rectForRowAtIndexPath:indexPath]];
DetailViewController *objDetail=[[DetailViewController alloc] init];
Home *tempSearchObj=(Home *)[profileArray objectAtIndex:indexPath.row];
objDetail.firstName=tempSearchObj.userName;
objDetail.userImageUrl=tempSearchObj.imageUrl;
objDetail.passedProfileID = tempSearchObj.profileID;
plsSelectLabel.text = [NSString stringWithFormat:#"%#",objDetail.firstName];
proDel.globalName = plsSelectLabel.text;
proDel.globalProfileId = objDetail.passedProfileID;
}
But this doesn't seem to work. Any help would be appreciated!!
Set the cell selection style in cellForRowAtIndexPath as :
if (cell == nil)
{
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
[cell setSelectionStyle:UITableViewCellSelectionStyleBlue];
}
And :
yourButton.tag = indexPath.row;
Add the following line in your method to highlight the cell :
UIButton *clickButton = (UIButton *)sender;
int addButtonIndex = clickButton.tag;
NSIndexPath *indexPathHighlight = [NSIndexPath indexPathForRow:addButtonIndex inSection:0];
UITableViewCell *newCell = [yourTable cellForRowAtIndexPath:indexPathHighlight];
[newCell setSelected:YES animated:YES];
Unselect previous cell :
NSIndexPath *previousIndexPathHighlighted = [NSIndexPath indexPathForRow:previousTag inSection:0];
UITableViewCell *previousCell = [yourTable cellForRowAtIndexPath:previousIndexPathHighlighted];
[previousCell setSelected:NO animated:YES];
previousTag = clickButton.tag;
The proper way to go about this is that on your cellForRowAtIndex: method you have a way of knowing if your cell needs to look highlighted or not. Then, after clicking on a button and configuring whatever you need to do, you should simply do
NSArray *indexes = [NSArray arrayWithObject:[NSIndexPath indexPathForRow:0 inSection:1]];
[self.tableView reloadRowsAtIndexPaths:indexes withRowAnimation:UITableViewRowAnimationNone];
The other way to go about this, is to access the cell and change it directly. But not with button.superview.superview, but rather
UITableViewCell *cell = [self.tableView cellForRowAtIndex:[NSIndexPath indexPathForRow:0 inSection:1]];
I think the best way to do it is to make a
NSInteger selectedCellIndex;
Set the tags of your buttons in the cellForRowAtIndex method
button.tag = indexPath.row
Set the selectedCellIndex when you click the button and reload the table
selectedCellIndex = button.tag;
[self.tableView reloadData];
Then in the cellForRowAtIndex method check if the index is the same as the selectedCellIndex and add a different color to the cell.
if( indexPath.row == selectedCellIndex ){
//set different colors
}
Remember to do this outside the if( cell == nil ) check, otherwise it wouldn't work.
I'm trying to add a button when a row is selected in UITableView and also remove it when row is tapped second time, i don't want custom UITableViewCell.
Any suggestion or sample code will be appreciated.
code i've tried:
in cellForRowAtIndexPath method
if(cell.selected == YES){
UITextField* numOfBottles =[[UITextField alloc] initWithFrame:CGRectMake(240,9.0f,50, 25)];
numOfBottles.tag = indexPath.row;
[numOfBottles setBorderStyle:UITextBorderStyleNone];
[numOfBottles setBackgroundColor:[UIColor clearColor]];
[numOfBottles setTextColor:[UIColor whiteColor]];
[numOfBottles setTextAlignment:UITextAlignmentLeft];
[numOfBottles setBackground:[UIImage imageNamed:#"blue_dropdown_normal.png"]];
[numOfBottles setFont:[UIFont systemFontOfSize:16.0f]];
[numOfBottles setDelegate:self];
NSString* quantity = [[NSString alloc] initWithString:[subtotalObj.qtyArray objectAtIndex:(indexPath.row - 1)]];
[numOfBottles setText:quantity];
[numOfBottles setTextAlignment:UITextAlignmentCenter];
[numOfBottles setBackgroundColor:[UIColor whiteColor]];
numOfBottles.keyboardType = UIKeyboardTypeDefault;
// numOfBottles.tag = indexPath.row;
[cell.contentView addSubview:numOfBottles];
[numOfBottles release];
}
and in didSelectRowAtIndexPath method
[tableView reloadData];
But still button(textfield with background image) is not rendered.
I think the best way is to use a UITableViewCell subclass.
But you can use my answer on another question to make your TableView rerender and place a button in your cell.
- (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];
}
if(indexPath.row == selectedRow){ // if your criteria where met
//add your button
}
return cell;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
//do your select things here
selectedRow = indexPath.row; //where selectedRow is a class variable
[self.tableView reloadData]; // rerenders the tableview
}
There's UITableView delegate, - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath implement it, where you'll get the row which user tapped, get the current cellusing cellForRowAtIndexPath: you need to pass NSIndexPath using the section and row. Now check if cell.contentView subviews has UIButton which you want? If yes, perform removeFromSuperView else addSubView. If you cell have already filled with UIButtons you can give unique tag to distinguish them.
Use these delegates
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
UITableViewCell *cell = (UITableViewCell*)[tableView cellForRowAtIndexPath:indexPath];
//Create reference of button with tag 999
cell.contentView addSubView:yourButtonReference]
[tableView reloadRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationNone];
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = (UITableViewCell*)[tableView cellForRowAtIndexPath:indexPath];
UIButton *btnAdded = (UIButton *)[cell viewWithTag:999];
if(btnAdded){
[btnAdded removeFromSuperView];
[tableView reloadRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationNone];
}
}
This is the current working code.
But this should be simpler, I guess.
If I use the commented code I get a wrong height size.
Anyone some suggestions?
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
TableViewCell *cell = (TableViewCell *)[[self tableView]dequeueReusableCellWithIdentifier:CellClassName];
// TableViewCell *cell = [[[TableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:CellClassName] autorelease];
if (!cell)
{
NSArray *topLevelItems = [cellLoader instantiateWithOwner:self options:nil];
cell = [topLevelItems objectAtIndex:0];
// cell = [[[TableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:CellClassName] autorelease];
}
NSLog(#"cell height %f", cell.bounds.size.height);
[[self tableView] setRowHeight:cell.bounds.size.height] ;
}
Yes, you can definitely simplify it. Just call your own implementation of -tableView:cellForRowAtIndexPath: and use the cell that you get back from that, i.e.
UITableView *table = self.tableView;
NSIndexPath *index = [NSIndexPath indexPathForRow:0 inSection:0];
UITableViewCell *cell = [self tableView:table cellForRowAtIndexPath:index];
[table setRowHeight:cell.bounds.size.height]
Voilà: no more duplicated code.
On another note, you might want to do this in -viewDidLoad or -viewWillAppear: if your view controller is going to appear with animation. Doing it in -viewDidAppear: will cause the table’s row height to visibly jump when the view-presentation animation finishes.