I have a UITableView with a dozen rows, each containing a UITextField.
By default the UITextField contains a placeholder value "Add Value" if the user hasn't previously edited the text field:
UITextField *textField = [[UITextField alloc] initWithFrame:CGRectMake(158, 6, 148, 24)];
NSString *strReplacement = [valueArray objectAtIndex:indexPath.row];
if (([strReplacement length] != 0) {
textField.text = strReplacement;
} else {
textField.placeholder = #"Add Value";
}
textField.delegate = self;
[cell addSubview:textField];
[textField release];
So far so good.
I've also added a UIButton to the footer of the UITableView.
What I want is to clear all the edited values and refresh all the UITextFields in the UITableView when the user clicks the UIButton.
I can easily enough remove all objects from the valueArray but I can't figure out how to refresh all the UITableView cells to reflect the changes.
Any help is appreciated.
lq
I believe what you're looking for is
[tableView reloadData];
Your solution feels weird. Filipe's right that the correct way to do it is with [wordsTableView reloadData], which will cause tableView:cellForRowAtIndexPath: to be called for each visible cell. That method is also called as you scroll through the table, so if reloadData isn't working, you're probably also going to end up with bugs with data not updating correctly as you change it and scroll. In your clearValues method, you're doing the same thing by calling tableView:cellForRowAtIndexPath:.
I think the real problem is in your tableView:cellForRowAtIndexPath: implementation. That method generally has 2 sections. First, you create or recycle a cell to get a reference with something like:
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier] autorelease];
}
Inside that if statement is generally the only place you should be adding subviews to your cell. If dequeueReusableCellWithIdentifier: returns a cell, it should already have the subview.
Then, after that if statement, you populate or update the contents of the subviews. The problem with your original code is that it's populating the text field and adding it as a subview, assuming there isn't already a text field in the cell. So your tableView:cellForRowAtIndexPath: should look something more like this:
int textFieldTag = 100;
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier] autorelease];
UITextField *textField = [[UITextField alloc] initWithFrame:CGRectMake(158, 6, 148, 24)];
[textField setTag:textFieldTag];
[textField setDelegate:self];
[cell addSubview:textField];
[textField release];
}
UITextField *textField = [cell viewWithTag:textFieldTag];
NSString *strReplacement = [valueArray objectAtIndex:indexPath.row];
if (([strReplacement length] != 0) {
textField.text = strReplacement;
} else {
textField.placeholder = #"Add Value";
}
It looks like you may be setting the textField's tag value to the row number, presumably so you can use it in the UITextFieldDelegate. That could also lead to bugs, as if the cell from row 1 is recycled by dequeueReusableCellWithIdentifier: and becomes row 12, it's going to have an unexpected tag value. Even if it doesn't happen now, it's a bug waiting to happen, and will be tricky to troubleshoot.
Filipe's solution should also work, however, calling reloadData should be avoided wherever possible as calling this method has a high performance overhead.
You need some class that has a reference to both the UIButton instance as well as all the instances of the UITextField that you have on the screen/in the table. Sounds like the perfect job for your UITableView controller subclass!
In your code above, why don't you also add each UITextField that you create to an NSArray of text fields that lives in your UITableView controller? Then when the user presses the UIButton, the action can call some method in your controller class, which loops through all the UITextField elements in the NSArray setting the text property of each instance to #"".
Warning: If you're reusing cells then you may have to ensure that the controller's NSArray of UITextFields is being updated properly.
After a few hours of trial and error, I came up with this solution. The UIButton "Clear All" invokes the following method:
- (IBAction)clearValues:(id)sender {
// count the number of values in the array (this is the same as the number of rows in the table)
int count = [valueArray count];
// remove all values from the array (deletes any user added values):
[self.valueArray removeAllObjects];
UITableViewCell *cell;
UITextField *textField;
// loop through each row in the table and put nil in each UITextField:
for (NSUInteger i = 0; i < count; ++i) {
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:i inSection:0];
cell = [self.wordsTableView cellForRowAtIndexPath:indexPath];
// NOTE: Make sure none of your tags are set to 0 since all non-tagged objects are zero.
// In table construction, your textFieldTags should be: textField.tag=indexPath.row+1;
textField = (UITextField*)[cell viewWithTag:i+1];
textField.text = nil;
}
}
Related
I have a problem, setting a button to a UITableviewCell.
After viewDidLoad, the button is on the right place. But when I am scrolling down, the button is anyplace else.
Here is my code, I hope you can help me.
Thanks In Advance.
- (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];
}
if (indexPath.section == 0 && indexPath.row == 0 && _isAddImageViewLoad == NO) {
// Add Image Button
UIButton *addImage = [UIButton buttonWithType:UIButtonTypeCustom];
UIImage* image = [UIImage imageNamed:#"AddImage#2x"];
addImage.frame = CGRectMake(110.0f, 10.0f, 110.0f, 110.0f);
[addImage setImage:image forState:UIControlStateNormal];
[cell.contentView addSubview:addImage];
_isAddImageViewLoad = YES;
} else {
NSDictionary *dictionary = [_items objectAtIndex:indexPath.section];
NSArray *array = [dictionary objectForKey:#"data"];
NSString *cellValue = [array objectAtIndex:indexPath.row];
cell.textLabel.text = cellValue;
}
return cell;
}
It is because you are reusing the cells, and the button is getting placed when it shouldn't an easy solution in your else section. Write addImage.hidden = YES; and in your if statement put addImage.hidden = NO;
Just a couple things. If you use "AddImage" it will use the "AddImage#2x" automatically if it's a retina display. I don't think that will solve your issue but it could be causing weirdness.
When a table view cell is scrolled off the view it is "recycled" in a sense. It appears like you are using a bool to exclude the original cell from being loaded again with a button. You may want to use a header to hold your button if you always want it at the "top". You may also want to verify that the button is being removed when the cell is reused. if its not it will show up in the next row that reuses that cell.
On a side note... Buttons don't usually work very well in table view cells because they handle touches in very different ways. It's quite a bit of modification to get them to feel natural but that's another matter.
Hope that helps!
The problem is because of cell reuse. You need to put some code in the else clause to delete the button if it exits. One way to do this, would be to give your button a tag, like:
addImage.tag = 10;
Then in your else clause:
}else{
if (cell viewWithTag:10) [[cell viewWithTag: 10] removeFromSuperview];
...
The problem is because of the dequeue for the cells. The first time the tableview creates the cells, all the cells run through the
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier];
code. But when the section 0 row 0 is moved off the screen, that cell is pushed into the cell reusable queue.
Now when your tableview needs to display section 0 row 0, it will get a cell from the reuse queue. you will not get the same cell as the first time. So now you might have 2 cells with the button.
What you should do is have different CellIdentifier for section 0 row 0 , and all other sections and rows. Also create the button when creating the cell. So after the first time the tableView creates the cell, you will not be creating the the button everything.
Look at this line of code:
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
This line of code means the UITableViewCells are not created every time. They are re-used as you scroll up and down. Using the code you have above, the UIButton will be created in the correct spot, but then as the cells are re-used, it will create the button in random spots.
One quick way to solve the problem, change the above line of code to simply
UITableViewCell *cell;
I have created the cells with labels and using checkaMarksAccessory. The few last cells have UITextFields which can user modifi, and those have selector on UIControlEventEditingDidEnd where i want change the state of the cell to checked.
How can i get the cell in the selector? Doesn't have the object some parentView?
The way i inserting the object to cell.
UITextField *textfield = [[UITextField alloc] initWithFrame:CGRectMake(10, 25, 200, 30)];
[textfield setBorderStyle:UITextBorderStyleRoundedRect];
[textfield addTarget:self action:#selector(vybavaDidFinishEdit:) forControlEvents:UIControlEventEditingDidEnd];
[cell.contentView addSubview:textfield];
I'm not sure if it's safe to assume cell.contentView.superview == cell. Might Apple change this? I doubt it. But, I don't see anywhere in the documentation that says a cell's content view is a direct subview of the cell.
If you've added a UIGestureRecognizer to one of your subviews of the cell's content view, then you can get a reference to the cell with:
NSIndexPath *indexPath = [self.tableView indexPathForRowAtPoint:[gestureRecognizer locationInView:self.tableView]];
UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:indexPath];
Table View Animations and Gestures sample code uses indexPathForRowAtPoint: this way.
If you must traverse superviews, I think using a function like the one below is a bit safer.
UITableViewCell *ACMContentViewGetCell(UIView *view)
{
while ((view = view.superview)) {
if ([view isKindOfClass:[UITableViewCell class]]) {
return (UITableViewCell *)view;
}
}
return nil;
}
But that function still assumes contentView is within its cell, which I also didn't see anywhere in the documentation.
So perhaps, the best solution is to rearchitect your code so that you don't need to get cell from contentView, or if you must, then add an instance variable from the subview of contentView to cell.
ok so the way is to use superview. The superview is component which own the object. If i want get the UITableViewCell from UITextField i used [[UITextField superview] superview].
Below is code for UITableView, But when i scroll its behaves weirdly (too annoying)... This problem is due to reuseIdentifier.... but dont know how to solve..
- (UITableViewCell *)tableView:(UITableView *)tableView1 cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView1 dequeueReusableCellWithIdentifier:CellIdentifier];
NSInteger imgTag = 1;
NSInteger lblTag = 2;
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
cell.selectionStyle = UITableViewCellSelectionStyleNone;
UIImageView *imgView = [[UIImageView alloc] initWithFrame:CGRectMake(2, 2, 52, 52)];
// Image:[UIImage imageNamed:[self.glassType objectAtIndex:indexPath.row]]];
imgView.tag = imgTag;
[cell.contentView addSubview:imgView];
[imgView release];
UILabel *lblName = [[UILabel alloc] initWithFrame:CGRectMake(60, cell.frame.size.height/4, 200, 21)];
// lblName.text = [self.glassName objectAtIndex:indexPath.row];
lblName.tag = lblTag;
[cell addSubview:lblName];
[lblName release];
}
NSInteger imgIndex = 2;
NSInteger lblIndex = 3;
((UIImageView *)[cell viewWithTag:imgTag]).image = [[self.glassType objectAtIndex:indexPath.row] objectAtIndex:imgIndex];
((UILabel *)[cell viewWithTag:lblTag]).text = [[self.glassName objectAtIndex:indexPath.row] objectAtIndex:lblIndex];
return cell;
}
- (void)tableView:(UITableView *)tableView1 didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [tableView1 cellForRowAtIndexPath:indexPath];
cell.accessoryType = UITableViewCellAccessoryCheckmark;
}
How to make Cell for row at index so that it remains constant even when scrolled??? Also how to make single selection in UITableView??
The answer is that you should not add subviews to your table cells outside of the "if (cell == nil) { ..." clause or they get added over and over again to the same cell when it gets re-used.
See my answer to this question for a more detailed explanation, including code for how to fix it:
cellForRowAtIndexPath memory management
You also cannot store state in table cells because as soon as they scroll offscreen they are recycled and re-appear at a different index in your table. You need to set up an array of model objects to store state for your table cells (such as what their accessory type should be). A more detailed explanation can be found in my answer to this question:
Looping through UITableViewCells of a UITableView
If you fix how you are adding subviews to the cells, and store your "ticked" state in an array of model objects as well as setting the cell.accessoryType (so that it can be restored when the cell is dequeued), then your approach to row selection is otherwise correct.
So put this in your tableView:cellForRowAtIndexPath: method, just before the return cell;:
MyModelObject *object = [self.arrayOfModelObjects objectAtIndex:indexPath.row];
BOOL isChecked = object.checked;
cell.accessoryType = isChecked? UITableViewCellAccessoryCheckmark: UITableViewCellAccessoryNone;
And in your tableView: didSelectRowAtIndexPath: method, get rid of the current logic and replace it with:
- (void)tableView:(UITableView *)tableView1 didSelectRowAtIndexPath:(NSIndexPath *)indexPath
for (int i = 0; i < [self.arrayOfModelObjects count]; i++)
{
MyModelObject *object = [self.arrayOfModelObjects objectAtIndex:i];
object.checked = (i == indexPath.row); // only check the one we just tapped
}
//refresh table to update the accessory views for all rows
[tableView1 reloadData];
}
Obviously replace the arrayOfModelObjects with your own model implementation. You could just use an array of NSNumber objects containing bools if you don't want to create a custom class for this purpose.
The recycling queue is like a pool where previously created Cells are stored before to reuse them. For example when you scrolls up, at the moment the cell disappears above, it is stored in the queue and becomes available for the cell that will appear at the bottom. Ok ?
Actually the number of cells really created is exactly the max simultaneous cell you can display in your table (in most cases from 3 to 8). In other words your if (cell == nil) code is executed (more or less from 3 to 8 times) at the first reloadData to create the pool of cells your table needs.
Then all you make on a cell is kept as it and appears again when you dequeue it. It's now easy to understand that, in your code, you have to make all strictly row-dependant settings outside the if (cell == nil) block. The same way, do not add subViews outside the if (cell == nil) block, you can imagine the thousands of subview you will add each time you reset a dequeued cell !
Tip: if you need some custom cleanup before reusing a cell (like to set an image to blank), you can create a custom UITableviewCell class and implements the prepareForReuse method.
Is it clear ?
Always reload your tableView in viewWillAppear method instead of viewDidLoad.
(void) viewWillAppear:(BOOL)animated{
[self.tableView reloadData];
}
This avoids most of all unexpected and annoying problems. :)
i am a newbie to iphone development.
I am working on a project where i am having a UItableview , i placed a button in each row .
Now my UItableView have a cell.textlabel, detailLabel, and a button placed programatically.
In cell.textlabel i have some values from json(each label have different values) and just at the right corner of each row , i placed a button.
Now i want the cell.textlable.text value of that row only whose button is pressed.
I am getting the same value for each button.
So , my question is , how to get labeltext value of the specific row whose button is pressed ?
attach a tag with your button during its creation e.g. button1.tag = 9999;
in your button click action grab the button as sender
-(void)buttonaction:(UIButton*)sender
{
UITableViewCell * selectedCell = nil;
for(UITableViewCell *cell in [tableView visibleCells])
{
if([cell viewWithTag:9999] == sender)
{
selectedCell = cell;
break;
}
}
//do whatever you like with selected cell now.
}
I hope it helps!!!
Solved like this:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath: (NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"Cell";
cell = [self.tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier] autorelease];
UIButton *tableCellButton = [[UIButton buttonWithType:UIButtonTypeRoundedRect]retain];
tableCellButton.frame = CGRectMake(200, 1, 80, 20);
[tableCellButton setTitle:#"showing" forState:UIControlStateNormal];
[tableCellButton addTarget:self action:#selector(shortcutBottonClicked:) forControlEvents:UIControlEventTouchUpInside];
[cell addSubview:tableCellButton];
} NSDictionary *listing = [ self.temp objectAtIndex:indexPath.row];
cell.textLabel.text = [listing objectForKey:#"0"];
return cell;
}
-(IBAction) shortcutBottonClicked:(id)sender{
//this code solved the problem
indexPath = [self.tableView indexPathForCell:(UITableViewCell*)[sender superview]];
mlsid2 = [self.tableView cellForRowAtIndexPath:indexPath].textLabel.text;
}
I think you mean that each row in the table has a button, and any number of them can be pressed. After the user is done pressing buttons for rows, you need to get which rows of data have been pressed.
If I read you correclty then unless your table is static, it will work something like the following:
You need to have a datastructure like the one I'm assuming you have for your UITableview datasource. For example, an NSArray full of NSNumber objects, one corresponding to each row of data. When the button in a row is pressed, the corresponding NSNumber is set 1. When you need it, just iterate through the NSArray of NSNumbers to know the corresponding rows in your table's datasource that had it's button pressed.
One thing to be aware of is that UItableview cells that you see in the UI are normally reused as the user scrolls - at least this is the case if you have a variable amount of data and rows for your table (as opposed to static), so you can't rely on the UI elements, like the button, for remembering the state
in first view .h file
#
class viewController;
#import <UIKit/UIKit.h>
#interface firstViewController : UITableViewController<UITableViewDelegate, UITableViewDataSource> {
NSString *_cellTitle;
}
#end
first view .m file in didSelectRowAtIndex method add this code like
viewController *detailViewController = [[viewController alloc] initWithNibName:#"viewController" bundle:nil];
NSInteger row = [indexPath row];
_rowTitle = [_detailList objectAtIndex:row];
detailViewController._barTitle = _rowTitle ;
or you can do this in buttonPressed method
then in the second view .h section -
#interface viewController : UIViewController {
NSString *_barTitle;
}
#property (nonatomic, retain) NSString *_barTitle;
#end
synthesize this property in implementation section
in .m and under viewDidLoad method
self.navigationItem.title = _barTitle;
don't forget to import second view .h file
hope this helps
I have a UITableView where I have the backgroud color set via
UIView *myView = [[UIView alloc] init];
if ((indexPath.row % 2) == 0)
myView.backgroundColor = [UIColor greenColor];
else
myView.backgroundColor = [UIColor whiteColor];
cell.backgroundView = myView;
[myView release];
The problem I find is that when I edit a table (via setEditing:YES...) some cells of the same color invariable are next to each other. How do I force UITableView to fully redraw. reloadData is not doing a great job.
Is there are deep-cleaning redraw?
I had this issue before so I'll share with you how I solved it:
You can use a boolean flag (say it's called needsRefresh) to control the behavior of cell creation in -cellForRowAtIndexPath:
An example:
- (UITableViewCell*) tableView:(UITableView *) tableView cellForRowAtIndexPath:(NSIndexPath*) indexPath {
UITableViewCell *cell = [tableView dequeueResuableCellWithIdentifier:SOME_ID];
if(!cell || needsRefresh) {
cell = [[[UITableViewCell alloc] init....] autorelease];
}
//.....
return cell;
}
So, when you need a hard reload, set the needsRefresh flag to YES. Simple as a pimple.
For me the accepted answer didn't really work since I had no idea when to set the needsRefresh back to YES.
What worked for me was:
- (UITableViewCell*) tableView:(UITableView *) tableView cellForRowAtIndexPath:(NSIndexPath*) indexPath {
UITableViewCell *cell = [tableView dequeueResuableCellWithIdentifier:customCellIdentifier];
if(nil == cell) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault
reuseIdentifier:customCellIdentifier];
}
//.....
return cell;
}
And then you change the customCellIdentifier value whenever you need to. This way the cells are also still reusable if you switch back to the original cell identifier.
The accepted method seems dirty, it just makes a bunch of new cells that are stored along with the bad ones. Here are a couple of solutions depending on your situation:
1.
first, for the situation described in the question you should not dump your cells and create new views on every cycle. You need to tag your view and then get it back when from the cell when you get a reuse cell:
- (UITableViewCell*) tableView:(UITableView *) tableView cellForRowAtIndexPath:(NSIndexPath*) indexPath {
UITableViewCell *cell = [tableView dequeueResuableCellWithIdentifier:SOME_ID];
if(!cell) {
cell = [[UITableViewCell alloc] init];
UIView *myView = [[UIView alloc] init];
cell.backgroundView = myView;
[myView setTag:5]; //<------
}
UIView *myView = [cell viewWithTag:5]; //<------
if ((indexPath.row % 2) == 0)
myView.backgroundColor = [UIColor greenColor];
else
myView.backgroundColor = [UIColor whiteColor];
return cell;
}
//then just reload the tableview.
2.
...or even better, why not just use the cell backgrouncolor and update that without creating a view.
3.
A sure way to really clear out old cached cells it to simply recreate the UITableView object.
4.
In most cases you dont need to destroy these cells, just keep track of your elements and update them after getting the reusable cell.You can tag all your elements, keep a array reference to them, find them thought the view hierarchy... Im sure theres a bunch of other ways.
5.
heres a one liner to directly purge all cells, although not best practice to mess with the internals of objects like this as they might change in future versions:
[(NSMutableDictionary*)[tableview valueForKey:#"_reusableTableCells" ] removeAllObjects];
I was able to solve this by adding a refresh variable to the table datasource. I used a dictionary for each cell, but there's an extra key called #"refresh":#"1", indicating the cell needs refreshing. Once it's updated, I set that key's value to #"0". So whenever the table is reloaded, make sure the key goes back to #"0" again.
#define TABLE_VIEW_CELL_DEFAULT_ID #"cellIdentifier"
#property (nonatomic, strong) NSString *tableViewCellIdentifier;
#property (nonatomic) NSUInteger tableViewCellIdentifierCount;
// By using a different cell identifier, this effectively flushes the cell
// cache because the old cells will no longer be used.
- (void) flushTableViewCellCache
{
self.tableViewCellIdentifierCount++;
self.tableViewCellIdentifier = [NSString stringWithFormat:#"%#%i", TABLE_VIEW_CELL_DEFAULT_ID, self.tableViewCellIdentifierCount];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
MyTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:self.tableViewCellIdentifier];
if (cell == nil) {
cell = [[MyTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:self.tableViewCellIdentifier];
}
// rest of method...
}