How to redraw UITableViewCells automatically given different sorting parameters - ios

I have a UITableViewController called HighScoreViewController that possesses UITableViewCells that are each individual HighScore objects.
When I press the segmented control at the top I want to resort the TableCells immediately. However, currently my sort only works by dragging around so that the cells are off-screen causing the (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath to be called again (which resorts the individual cell according to the score, duration, or date).
I tried to create a method that would be called in my segmented control action that would remove and add the View to redraw the cells. When that didn't work I tried redrawing each cell manually but I realized that it would be a extremely complicated as I would have to monitor which scores had already been sorted in the View and what scores were sorted in the Data Source. I also tried setNeedsDisplay.
How can I resort the UITableViewCells immediately after pressing the segmented control?
This is my action outlet for the segmented Control
- (IBAction)changeSort:(id)sender {
NSLog(#"changing Sort");
// Sorted by score
if (self.sortingButton.selectedSegmentIndex == SORT_BY_SCORE){
[self sortHighScoresByScore];
}else if (self.sortingButton.selectedSegmentIndex == SORT_BY_DURATION){// sorted by duration
[self sortHighScoresByDuration];
}else if (self.sortingButton.selectedSegmentIndex == SORT_BY_DATE){// sorted by last time played
[self sortHighScoresByDate];
}else{
NSLog(#"HighScoreViewController, changeSort else != 0,1,2");
}
}
And this is my cell method:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
//UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"HighScoreCell" forIndexPath:indexPath];
NSLog(#"inside TableView");
UITableViewCell *cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:#"HighScoreCell"];
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSMutableArray *playingCardHighScores = [defaults objectForKey:#"playingCardHighScores"];
NSMutableArray *setCardHighScores = [defaults objectForKey:#"setCardHighScores"];
// Decode High Scores arrays
playingCardHighScores = [self decodeEncodedArray:playingCardHighScores];
setCardHighScores = [self decodeEncodedArray:setCardHighScores];
/* Will need to set the left and right side to two different scores or add some kind of slider*/
HighScore *hs = playingCardHighScores[indexPath.row];
UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(55, 4, 260, 20)];
label.font = [UIFont boldSystemFontOfSize:15.0];
label.tag=25;
UILabel *detailLabel = [[UILabel alloc] initWithFrame:CGRectMake(25, 25, 260, 15)];
detailLabel.adjustsFontSizeToFitWidth = YES;
detailLabel.tag=30;
NSString *score = [NSString stringWithFormat:#"Score:%ld",(long)hs.score];
NSString *duration = [NSString stringWithFormat:#",Duration:%.2f",hs.secondsPlayed];
NSString *datePlayed = [NSString stringWithFormat:#",datePlayed:%#",hs.datePlayed];
NSString *combination = [score stringByAppendingString:[duration stringByAppendingString:datePlayed]];
detailLabel.text = combination;
[cell.contentView addSubview:label];
[cell.contentView addSubview:detailLabel];
// Should change depending on what selector is picked
NSString *strScore;
NSDate *date;
switch (self.sortingButton.selectedSegmentIndex) {
case SORT_BY_SCORE:
strScore = [NSString stringWithFormat:#"Score: %ld",(long)hs.score];
break;
case SORT_BY_DURATION:
strScore = [NSString stringWithFormat:#"Duration: %.2f seconds",hs.secondsPlayed];
break;
case SORT_BY_DATE:
date = hs.datePlayed;
break;
default:
break;
}
// if the string exists
if (strScore){
cell.textLabel.text = strScore;
}else{
cell.textLabel.text = [date description];
}
return cell;
}

You'll need to call [self.tableView reloadData] in your segmented control IBAction to get all the cells on screen to be recreated.
Alternatively if you want to be able to specify stock animation, you can call reloadSections:withRowAnimation: on your table view

Related

When user adds row to UITableView, the new row does not "stay" when returning to the app

I am working on an app where the user can estimate various length and width dimensions (area) for better determining required quantities.
I am struggling with a few items, but will address them one at a time...
I would like the user to add as many fields as they like to input the required areas they need calculated... for this I am using a UITableView and a reuseable cell... I have coded to allow the user to add a new row (or as many rows as they like). Currently the app starts with 3 rows, but if they want to add more they can.
So that "part" is working... the user can add and delete rows at will, however, if a user adds 3 or 4 more rows in the app and then closes the app, the app on return only displays the default number of rows.
My question is... how can the app "remember" those additional rows...so the next time the user enters the app, the new number of rows are always shown and not my default. I am not currently asking how to save the data or information inside the row, simply how to save the new number of rows for use later on...
I have some screen caps for reference...
You can see I start with the default 3 rows, and allow the user to add more... that does function... however, when I close the app, the default 3 rows show again, forgetting the rows added by the user... any help much appreciated...
UPDATE
So now I have the following additional code based on the suggestions posted below...
In controller
- (UITableViewCell *) tableView:(UITableView *) tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
SodTableCell *cell = [tableView dequeueReusableCellWithIdentifier:#"Cell"];
if(cell == nil){
cell = [[SodTableCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:#"Cell"];
}
//if number of rows is greater than the total number of rows in the data set
//and this view is in editing mode.
//Initialize the cell for "Add Row"
//there will be an extra row once SetEditing: is called
if(indexPath.row >= tabledata.count && [self isEditing]){
cell.areaNumber.text = #"";
NSUserDefaults *area = [NSUserDefaults standardUserDefaults];
[area setObject:self.area_Input forKey:#"sod_area_input_by_user"];
[area synchronize];
NSLog(#"%#",[area valueForKey:#"sod_area_input_by_user"]);
}else{ // this tableview is showing table (not in edit mode)
/*cell.areaNumber.text = [tabledata objectAtIndex:indexPath.row];*/
cell.areaNumber.text = tabledata[indexPath.row];
// Configure the cell...
UILabel*label = [[UILabel alloc] initWithFrame:CGRectMake(self.view.bounds.size.width/2.5, 0, self.view.bounds.size.width, 44)];
NSString*text = [NSString stringWithFormat:#"Area %ld",(long)indexPath.row+1];
[label setText:text];
[label setTextColor: [UIColor colorWithRed:0.41 green:0.33 blue:0.25 alpha:1.0]];
label.font = [UIFont fontWithName:#"HelveticaNeue-Thin" size:14.0f];
[cell.contentView addSubview: label];
}
return cell;
}
Where area_Input is a NSString...
I am trying to then show the result here in viewDidLoad
tabledata = [[NSMutableArray alloc] initWithObjects:self.area_Input, nil];
But it appears as nil when viewing the NSLog... any ideas?
You can save these cells info in NSUserDefaults then in your viewWillAppear retrieve these cells and save them to an array that you use to load the cells.
Solved...
In case this helps anyone else...
- (UITableViewCell *) tableView:(UITableView *) tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
SodTableCell *cell = [tableView dequeueReusableCellWithIdentifier:#"Cell"];
if(cell == nil){
cell = [[SodTableCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:#"Cell"];
}
//if number of rows is greater than the total number of rows in the data set and this view is in editing mode.
//Initialize the cell for "Add Row"
//there will be an extra row once SetEditing: is called
if(indexPath.row >= tabledata.count && [self isEditing]){
cell.areaNumber.text = #"";
}else{ // this tableview is showing table (not in edit mode)
cell.areaNumber.text = [tabledata objectAtIndex:indexPath.row];
//cell.areaNumber.text = tabledata[indexPath.row];
//[cell addSubview:cell.areaNumber];
NSUserDefaults *save = [NSUserDefaults standardUserDefaults];
[save setObject:tabledata forKey:#"sod_area_input_by_user"];
[save synchronize];
NSLog(#"This number is from cell creation code %#",[save valueForKey:#"sod_area_input_by_user"]);
// Configure the cell...
UILabel*label = [[UILabel alloc] initWithFrame:CGRectMake(self.view.bounds.size.width/2.5, 0, self.view.bounds.size.width, 44)];
NSString*text = [NSString stringWithFormat:#"Area %ld",(long)indexPath.row+1];
[label setText:text];
[label setTextColor: [UIColor colorWithRed:0.41 green:0.33 blue:0.25 alpha:1.0]];
label.font = [UIFont fontWithName:#"HelveticaNeue-Thin" size:14.0f];
[cell.contentView addSubview: label];
}
return cell;
}
Then in viewDidLoad...
- (void)viewDidLoad
{
[super viewDidLoad];
UIBarButtonItem *addButton = [[UIBarButtonItem alloc] initWithTitle:#"+ / -" style: UIBarButtonItemStylePlain target:self action:#selector(addORDeleteRows)];[self.navigationItem setRightBarButtonItem:addButton];
// Code to adjust the background image used in app based on whether it is an ipad or iphone 4 or iphone 5
if(UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
// iPad code
self.view.backgroundColor = [UIColor clearColor];
[ADVThemeManager customizeView:self.view];
}
tabledata = [[NSMutableArray alloc] init];
if([tabledata count]==0){
//Your array is empty so you have to call it with #"yourKey"
tabledata=[[NSMutableArray alloc]initWithArray:[[NSUserDefaults standardUserDefaults]objectForKey:#"sod_area_input_by_user"]];
}
NSUserDefaults *save = [NSUserDefaults standardUserDefaults];
[save setObject:tabledata forKey:#"sod_area_input_by_user"];
numberOfSection = 1;
[myTable reloadData];
Now the app will allow me to insert and delete rows as needed...
I think it should be noted that "tabledata" is a NSMutableArray
Thanks everyone for your input... much appreciated...

tableview update with new row keeping previous rows at first

After searching a lot did not found a proper solution to update a tableview
i want to update my tableview like a new coming record should place just below the previously updated records.
this is my code
if (sqlite3_open(myDatabase, &myConnection) == SQLITE_OK)
{
sqliteQuery = [NSString stringWithFormat: #"SELECT Description, SalePrice FROM ProductDetails WHERE Barcode = \'%#\'", barcode];
if (sqlite3_prepare_v2(myConnection, [sqliteQuery UTF8String], -1, &sQLStatement, NULL) == SQLITE_OK)
{
if(sqlite3_step(sQLStatement) == SQLITE_ROW)
{
temp = [[NSString alloc] initWithUTF8String:(const char *) sqlite3_column_text(sQLStatement, 0)];
}
temp1 = [NSString stringWithFormat:#"%d", value];
if ([temp1 isEqualToString:#"0"])
{temp1 = #"1";}
temp2 = [[NSString alloc] initWithUTF8String:(const char *) sqlite3_column_text(sQLStatement, 1)] }
}
[description addObject: temp];
[qty addObject: temp1];
[price addObject:temp2];
}
sqlite3_finalize(sQLStatement);
}
sqlite3_close(myConnection);
}
myTable.hidden = NO;
myTable.delegate = self;
myTable.dataSource = self;
[myTable reloadData];
[self.view endEditing:YES];
and the tableview data source delegate methods
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return [description count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath: (NSIndexPath *)indexPath
{
// Configure the cell in each row
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell;
cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
cell = [self getCellContentView:CellIdentifier];
UILabel *lbl11 = (UILabel *)[cell viewWithTag:1];
[lbl11 setText:#"Label1"];
UILabel *lbl21 = (UILabel *)[cell viewWithTag:2];
[lbl21 setText:#"Label2"];
UILabel *lbl31 = (UILabel *)[cell viewWithTag:3];
[lbl31 setText:#"Label3"];
lbl11.text = [description objectAtIndex:indexPath.row];
lbl21.text = [qty objectAtIndex:indexPath.row];
lbl31.text = [price objectAtIndex:indexPath.row];
return cell;
}
- (UITableViewCell *)getCellContentView:(NSString *)cellIdentifier
{
UITableViewCell *cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:cellIdentifier];
cell.backgroundColor=[UIColor clearColor];
CGRect lbl11Rect = CGRectMake(20, 5, 450, 30);
CGRect lbl21Rect = CGRectMake(545, 5, 150, 30);
CGRect lbl31Rect = CGRectMake(795, 5, 150, 30);
UILabel *lbl11 = [[UILabel alloc] initWithFrame:lbl11Rect];
lbl11.tag=1;
lbl11.font=[UIFont fontWithName:#"Superclarendon" size:15];
lbl11.backgroundColor=[UIColor clearColor];
//lbl11.layer.borderWidth = 1.0f;
[cell.contentView addSubview:lbl11];
UILabel *lbl21 = [[UILabel alloc] initWithFrame:lbl21Rect];
lbl21.tag=2;
lbl21.font=[UIFont fontWithName:#"Superclarendon" size:15];
lbl21.backgroundColor=[UIColor clearColor];
//lbl21.layer.borderWidth = 1.0f;
[cell.contentView addSubview:lbl21];
UILabel *lbl31 = [[UILabel alloc] initWithFrame:lbl31Rect];
lbl31.tag=3;
lbl31.font=[UIFont fontWithName:#"Superclarendon" size:15];
lbl31.backgroundColor=[UIColor clearColor];
//lbl31.layer.borderWidth = 1.0f;
[cell.contentView addSubview:lbl31];
cell.selectionStyle = UITableViewCellSelectionStyleBlue;
return cell;
}
for first value search it showed the data in tableview…. how can i update tableview properly
i have found that for update
[myTable beginUpdate]
function will be used… and there is also
insertRowsAtIndexPaths: withRowAnimation
but unfortunately didn't find any good help that how to use this.
any help will be appreciated ….
First update your data source so numberOfRowsInSection and cellForRowAtIndexPath will return the correct values for your post-insert data. You must do this before you insert or delete rows.
Then insert your row:
// First figure out how many sections there are
NSInteger lastSectionIndex = [tableView numberOfSections] - 1;
// Then grab the number of rows in the last section
NSInteger lastRowIndex = [tableView numberOfRowsInSection:lastSectionIndex];
// Now just construct the index path
NSIndexPath *pathToLastRow = [NSIndexPath indexPathForRow:lastRowIndex inSection:lastSectionIndex];
//Adding new data row to last index position:
[myTable beginUpdates];
[myTable insertRowsAtIndexPaths:[NSArray arrayWithObject: pathToLastRow] withRowAnimation:UITableViewRowAnimationNone];
[myTable endUpdates];
There are different variants for animation:
UITableViewRowAnimationBottom
UITableViewRowAnimationFade
UITableViewRowAnimationMiddle
UITableViewRowAnimationNone
UITableViewRowAnimationRight
UITableViewRowAnimationTop
From iOs Developer Library:
beginUpdates
Begin a series of method calls that insert, delete, or select rows and sections of the receiver.
Discussion
Call this method if you want subsequent insertions, deletion, and selection operations (for example, cellForRowAtIndexPath: and indexPathsForVisibleRows) to be animated simultaneously. This group of methods must conclude with an invocation of endUpdates. These method pairs can be nested. If you do not make the insertion, deletion, and selection calls inside this block, table attributes such as row count might become invalid. You should not call reloadData within the group; if you call this method within the group, you will need to perform any animations yourself.
The example of using this method you can find in Related Sample Code: iPhoneCoreDataRecipes

UITableViewCell: dequeueReusableCellWithIdentifier: Change button background per cell on a certain action

I have table view that rendering cells. I created the cell once and reuse it. In each cell there is a button to do vote action for a place. On the vote action I want to change the background image of the button. When I click on the button in a certain cell the background image changes, but when I scroll down I find another button with the changed background image. I believe that the problem is because I'm using dequeueReusableCellWithIdentifier but I can't find a way to solve this problem.
Here is my code:
// .h file
#interface VotePlaceView : UIView < UITableViewDataSource, UITableViewDelegate>
{
VotePlaceView *votePlaceCell;
}
and in the implementation file
// .m File
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"votePlaceCell"];
if (cell)
{
votePlaceCell = (VotePlaceView*)[cell viewWithTag:10];
}
if (!cell)
{
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:#"votePlaceCell"];
//GlobalObjects is a customised thing for me to load the cell in different languages
votePlaceCell = [[GlobalObjects loadNibNamed:#"votePlaceCell" owner:self options:nil] objectAtIndex:0];
votePlaceCell.tag = 10;
[cell addSubview:votePlaceCell];
}
.
. //Fill data in the cell
.
//This gives different IDs, no ID like the others (unique ID)
NSLog(#"ID %d", [[[self.votePlacesResults objectAtIndex:indexPath.row] valueForKey:#"id"] intValue]);
UIButton* voteButton = (UIButton*)[votePlaceCell viewWithTag:20];
voteButton.tag = [[[self.votePlacesResults objectAtIndex:indexPath.row] valueForKey:#"id"] intValue];
return cell;
}
-(IBAction)votePlace:(UIButton *)sender
{
// I tried two ways to achieve my point with no luck
//Selected case give the new background image
//THE FIRST WAY
sender.selected = YES;
//OR THE SECOND WAY
UIButton* newVotePlace;
newVotePlace = (UIButton*)[[sender superview] viewWithTag:sender.tag];
newVotePlace.selected = YES;
}
Any idea?
The Solution
#Akhilrajtr's answer solved my problem, after I made some changes to his answer. Here is the solution, in case if anyone faced the same problem.
Instead of using a UIButton, I used UIButton and UIImageView. I put the UIImageView behind the UIButton and I give the UIImageView two images in the default case and in the highlighted case. I cleared the background image of the UIButton and then followed the accepted answer as follows:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
.
.
int placeId = [[[self.votePlacesResults objectAtIndex:indexPath.row] valueForKey:#"id"] intValue];
UIButton* voteButton = (UIButton*)[votePlaceCell viewWithTag:20];
if (!voteButton) {
voteButton = (UIButton*)[votePlaceCell viewWithTag:placeId];
}
voteButton.tag = [[[self.votePlacesResults objectAtIndex:indexPath.row] valueForKey:#"id"] intValue];
if ([selectedIdArray containsObject:[[self.votePlacesResults objectAtIndex:indexPath.row] valueForKey:#"id"]]) {
UIImageView* voteImage;
voteImage = (UIImageView*)[votePlaceCell viewWithTag:11];
voteImage.highlighted = YES;
} else {
UIImageView* voteImage;
voteImage = (UIImageView*)[votePlaceCell viewWithTag:11];
voteImage.highlighted = NO;
voteButton.selected = NO;
}
return cell;
}
-(IBAction)votePlace:(UIButton *)sender
{
[selectedIdArray addObject:[NSNumber numberWithInt:sender.tag]];
UIImageView* voteImage;
voteImage = (UIImageView*)[[sender superview] viewWithTag:11];
voteImage.highlighted = YES;
}
Try this,
Create a NSMutableArray *selectedIdArray (don't forget to instatiate selectedIdArray in viewDidLoad:) to store selected ID's , and in cellForRowAtIndexPath:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
.
.
//Other codes
.
.
NSLog(#"ID %d", [[[self.votePlacesResults objectAtIndex:indexPath.row] valueForKey:#"id"] intValue]);
int placeId = [[[self.votePlacesResults objectAtIndex:indexPath.row] valueForKey:#"id"] intValue];
UIButton* voteButton = (UIButton*)[votePlaceCell viewWithTag:20];
if (!voteButton) {
voteButton = (UIButton*)[votePlaceCell viewWithTag:placeId];
}
voteButton.tag = [[[self.votePlacesResults objectAtIndex:indexPath.row] valueForKey:#"id"] intValue];
if ([selectedIdArray containsObject:[NSNumber numberWithInt:placeId]) {
voteButton.selected = YES;
} else {
voteButton.selected = NO;
}
return cell;
}
and in
-(IBAction)votePlace:(UIButton *)sender
{
[selectedIdArray addObject:[NSNumber numberWithInt:sender.tag]];
sender.selected = YES;
}
You need to store an array containing information about the background for each cell, and set the background for each cell in cellForRowAtIndexPath.
As you're using selected on the button, create an NSMutableArray with as many entries as the cells, and instead of setting the button itself to selected, change the bool on the selected index of the array, then you can set selected dependent on the value of the desired index of NSMutableArray in cellForRowAtIndexPath.

Saving UITextField text in cell into NSUserDefaults?

I have seen every post that is close to this question, and still not finding something useful. I have textFields in every cell that is being used as a form for the user to fill out. Everything with the cells works fine except when scrolling, the input in the textFields disappears when the cell scrolls off screen. I know this is because of dequeue. But there should be a way to save the data entered so that it doesn't disappear when scrolling or exiting the app. I also want to be able to take this info and email it as a PDF, or document. What is the best way to achieve this? The code below is an example of how I am generating my cells etc.
.h file
#interface MasterViewController : UITableViewController <UITextFieldDelegate, UITextFieldDelegate, UITableViewDataSource, UINavigationBarDelegate>{
NSString* name_;
UITextField* nameFieldTextField;
}
// Creates a textfield with the specified text and placeholder text
-(UITextField*) makeTextField: (NSString*)text
placeholder: (NSString*)placeholder;
// Handles UIControlEventEditingDidEndOnExit
- (IBAction)textFieldFinished:(id)sender;
#property (nonatomic,copy) NSString* name;
.m file
#synthesize name = name_;
- (void)viewDidLoad{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
self.detailViewController = (DetailViewController *)[[self.splitViewController.viewControllers lastObject] topViewController];
self.name = #"";
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:nil];
// Make cell unselectable and set font.
cell.selectionStyle = UITableViewCellSelectionStyleNone;
cell.textLabel.font = [UIFont fontWithName:#"ArialMT" size:13];
if (indexPath.section == 0) {
UITextField* tf = nil;
switch ( indexPath.row ) {
case 0: {
cell.textLabel.text = #"Name" ;
tf = nameFieldTextField = [self makeTextField:self.name placeholder:#"John Appleseed"];
nameFieldTextField.tag = 1;
[cell addSubview:nameFieldTextField];
break ;
}
// Textfield dimensions
tf.frame = CGRectMake(120, 12, 170, 30);
// Workaround to dismiss keyboard when Done/Return is tapped
[tf addTarget:self action:#selector(textFieldFinished:) forControlEvents:UIControlEventEditingDidEndOnExit];
}
return cell;
}
// Textfield value changed, store the new value.
- (void)textFieldDidEndEditing:(UITextField *)textField {
//Section 1.
if ( textField == nameFieldTextField ) {
self.name = textField.text ;
}
}
- (void)viewWillAppear:(BOOL)animated{
NSString *nameCellString = [[NSUserDefaults standardUserDefaults] stringForKey:#"nameCellString"];
nameFieldTextField.text = nameCellString;
}
- (void)viewWillDisappear:(BOOL)animated{
NSString *nameCellString = self.name;
[[NSUserDefaults standardUserDefaults] setObject:nameCellString forKey:#"nameCellString"];
}
There are actually two problems here, both of them being in your cellForRowAtIndexPath: implementation.
You are putting the text field into the cell, even if this cell is reused and already has a text field. Thus you are actually piling text field over text field, covering up the previously existing text field.
You are not putting the text back into the text field if there was already text in the text field for that row (index path).
In other words, the cells are (as you rightly say) reused, so it is up to you to take that fact into account. You must look at the state of the incoming cell, and reconfigure the cell accordingly.
First off, I urge you to consider creating a custom cell in a storyboard, and grabbing that. It's a lot easier than coding one, and I think it's the future. That said, look into populating your tableViews with NSArrays, instead of hard-coding strings into the cellForRowAtIndexPath method. I've taken the liberty of giving you an example of this.
The following is based on your code, and should be a copy/paste solution. Look it over, and see how it operates.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSArray *titlesArray = #[#"Name", #"Birthday", #"Favorite Food"];
UITableViewCell *cell;
cell = [tableView dequeueReusableCellWithIdentifier:[NSString stringWithFormat:#"%i%i", indexPath.section, indexPath.row]];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:nil];
// Make cell unselectable and set font.
cell.selectionStyle = UITableViewCellSelectionStyleNone;
cell.textLabel.font = [UIFont fontWithName:#"ArialMT" size:13];
// Populate label from array
cell.textLabel.text = titlesArray[indexPath.row];
if (indexPath.section == 0) {
UITextField* tf = nil;
switch ( indexPath.row ) {
case 0: {
tf = nameFieldTextField = [self makeTextField:self.name placeholder:#"John Appleseed"];
nameFieldTextField.tag = 1;
[cell addSubview:nameFieldTextField];
break ;
}
// Textfield dimensions
tf.frame = CGRectMake(120, 12, 170, 30);
// Workaround to dismiss keyboard when Done/Return is tapped
[tf addTarget:self action:#selector(textFieldFinished:) forControlEvents:UIControlEventEditingDidEndOnExit];
}
// Set the reuse identifier to a unique string, based on placement in table
// This ensures that the textField will retain its text
cell.reuseIdentifier = [NSString stringWithFormat:#"%i%i", indexPath.section, indexPath.row];
}
return cell;
}
// Textfield value changed, store the new value.
- (void)textFieldDidEndEditing:(UITextField *)textField {
//Section 1.
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
switch (textField.tag) {
case 1:
[defaults setObject:textField.text forKey:#"nameCellString"];
self.name = textField.text;
break;
default:
break;
}
[defaults synchronize];
}
EDIT: Changed to accommodate more cells.
You should use array of UIDictionary for your tableDataSourceArray Like:
step1)
NSArray *tableDataSourceArray = [[NSArray alloc]init];
NSMutableDictionary *cellData = [[NSMutableDictionary alloc]init];
[cellData setValue:#"" forKey:#"Name"];
...//so on
[tableDataSourceArray addObject:cellData];
cellData = nil;
repeat step1 as number of records you have.
Now in cellForRowAtIndexPath:
nameFieldTextField.tag = indexPath.row; //To store index of dataSourceArray
nameFieldTextField.text = [[tableDataSourceArray objectAtIndex:indexPath.row] valueForKey:#"Name"];
And at last in textFieldDidEndEditing:
NSMutableDictionary *cellDataDic = tableDataSourceArray objectAtIndex:textField.tag];
[cellDataDic setValue:textField.text forKey:#"Name"];
hope it will help you.
I think the easiest way to fix your problem is to create a new class for your cell (inherit from UITableViewCell) and add new property like customerTextField (UITextField). In constructor add new textfield but with CGRectZero. In method layoutSubviews you will assign CGRect for your textfield.
Generally speaking this approach will make your UIViewController cleaner (you will reduce number of checks for textfield state).

Uiswitch in uitableviewcell reset when scroll up or down?

I have a problem with my UISwitch inside a UITableViewCell. When I change the value of one switch then scroll up or down all switches are messed up. I use an array to store state for each switch due to reusability they are still messed up every time.
Here is cellForRowAtIndexPath method:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"Cell"];
if (cell == nil)
{
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault
reuseIdentifier:#"Cell"];
}
UISwitch *switchController = [[UISwitch alloc] initWithFrame:CGRectZero];
CGRect switchFrame = switchController.frame;
[switchController setOn:YES animated:NO];
//set its x and y value, this you will have to determine how to space it on the left side
switchFrame.origin.x = 50.0f;
switchFrame.origin.y = 10.0f;
switchController.frame = switchFrame;
[switchController addTarget:self action:#selector(switchChanged:) forControlEvents:UIControlEventValueChanged];
[cell addSubview:switchController ];
UILabel *label ;
label=(UILabel *)[cell viewWithTag:1];
NSString *value = [[mainArray objectAtIndex:indexPath.section] objectAtIndex:indexPath.row];
label.text = value;
label.textAlignment = NSTextAlignmentRight;
label.autoresizingMask = UIViewAutoresizingFlexibleRightMargin;
//This for persist switch state when scroll up or down
if ([[[self.SwitchArray objectAtIndex:indexPath.section] objectAtIndex:indexPath.row ]isEqualToString:#"ON"])
{
switchController.on=YES;
}
else
{
switchController.on=NO;
}
return cell;
}
Here is SwitchChanged event :
-(void)switchChanged:(UISwitch *)sender
{
UITableViewCell *cell = (UITableViewCell *)[[sender superview] superview];
NSIndexPath *index=[mainTableView indexPathForCell:cell];
if (sender.on)
{
[[self.SwitchArray objectAtIndex:index.section] replaceObjectAtIndex:index.row withObject:#"ON"];
NSString *word= [[self.mainArray objectAtIndex:index.section ] objectAtIndex:index.row];
}
else
{
//call the first array by section
[[self.SwitchArray objectAtIndex:index.section] replaceObjectAtIndex:index.row withObject:#"OFF"];
NSString *word= [[self.mainArray objectAtIndex:index.section ] objectAtIndex:index.row];
}
[padFactoids setObject:[NSKeyedArchiver archivedDataWithRootObject:SwitchArray] forKey:#"savedArray"];
[padFactoids synchronize];
}
I will appreciate your help so much.
In your header file declare an NSMutableArray, let's name it switchStates.
In your viewDidLoad, allocate memory and add object with the string "OFF" according to number of switches:
switchStates = [[NSMutableArray alloc] init];
for (int i; i <= switchesArray.count; i++) {
[switchStates addObject:#"OFF"];
}
In your method which runs when the switch is triggered:
NSString *theSwitchPosition = [NSString stringWithFormat:#"%#", switchControl.on ? #"ON" : #"OFF"];
[switchStates replaceObjectAtIndex:aPath.row withObject:theSwitchPosition];
After that, in the method where you create your switches:
if ([[switchStates objectAtIndex:indexPath.row] isEqualToString:#"ON"]) {
mySwitch.on = YES;
} else {
mySwitch.on = NO;
}
This worked for me, good luck..
I am not sure whether this causes your problem, but it will certainly cause other related problems.
Each time when a cell was moved off screen and a next one appears, the one that just moved off screen will be reused.
But you add a new switch object every time to the cell. You are far better off creating those only within the cell==nil block. Give it a tag and use the tag to fetch the object upon reusage as you do with the lable object.
You're creating a new switch every time the tableView is asking for a cell. You only want to create the switch once for each cell:
UISwitch *switchController;
if (cell == nil)
{
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault
reuseIdentifier:#"Cell"];
switchController = [[UISwitch alloc] initWithFrame:CGRectZero];
CGRect switchFrame = switchController.frame;
[switchController setOn:YES animated:NO];
//set its x and y value, this you will have to determine how to space it on the left side
switchFrame.origin.x = 50.0f;
switchFrame.origin.y = 10.0f;
switchController.frame = switchFrame;
[switchController addTarget:self action:#selector(switchChanged:) forControlEvents:UIControlEventValueChanged];
[cell.contentView addSubview:switchController ];
switchController.tag = 123; //Arbitrary number...can be anything
}
else {
switchController = (UISwitch *)[cell.contentView viewWithTag:123];
}
//Now set the switch state according to your data model array
It's also generally a better practice to add subviews to the cell's contentView rather than the cell itself.

Resources