objective-c: [self.tableview reloadData] reloads data but doesnt clear older cells - objective-c

i'm working on a tableview and i am newbie in dynamically creating cells (i used to create them with IB and then link them to their tableviewcell controller etc..).
Everything works great and recpected arrays are updated properly but when i fire [self.tableview reloadData] the program just redraws new values over old cells. for example if there "TEST CELL" value in a uilabel inside a cell, when i update the data to "CELL TEST" and fire the reloadData, the uilabel looks like there are two labels on top of each other and both values are visible. (its like creating two uilabel with exact same location and same size and setting their values)
and this event happens everytime i fire reloadData, with each reload, the program looks like its adding another uilabel on top of the older one. heres my code:
- (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];
}
UILabel *lblText1 = [[UILabel alloc] initWithFrame:CGRectMake(30, 5, 130, 30)];
lblText1.adjustsFontSizeToFitWidth = YES;
lblText1.backgroundColor = [UIColor clearColor];
lblText1.text = [lblText1Array objectAtIndex:indexPath.row];
[cell addSubview:lblText1];
[lblText1 release];
if(indexPath.row<3)
{
UILabel *lblText2 = [[UILabel alloc] initWithFrame:CGRectMake(170, 5, 130, 30)];
lblText2.adjustsFontSizeToFitWidth = YES;
lblText2.backgroundColor = [UIColor clearColor];
lblText2.text = nil;
lblText2.text = [spParameters objectAtIndex:indexPath.row];
[cell addSubview:lblText2];
[lblText2 release];
}
else if(indexPath.row==3){
UITextField *textField = [[UITextField alloc] initWithFrame:CGRectMake(170, 7, 130, 30)];
textField.adjustsFontSizeToFitWidth = YES;
textField.borderStyle = UITextBorderStyleRoundedRect;
textField.placeholder = #"please insert value";
textField.textAlignment = UITextAlignmentCenter;
textField.delegate = self;
textField.text = [spParameters objectAtIndex:indexPath.row];
[cell addSubview:textField];
[textField release];
}
else if(indexPath.row == 4)
{
UISwitch *gSwitch = [[UISwitch alloc] initWithFrame:CGRectMake(170, 7, 130, 30)];
[gSwitch setOn:FALSE];
[gSwitch addTarget: self action: #selector(switchValueChanged:) forControlEvents:UIControlEventValueChanged];
[cell addSubview:gSwitch];
[gSwitch release];
}
// Configure the cell...
return cell;
}
im releasing the components after i adding them to subviews, and i am thinking about if theres something wrong with the reuseidentifier...
Thanx for helping.

It looks as though you're populating a detail view with a fixed number of cells, so you should consider creating the instances statically, for example in viewDidLoad or in Interface Builder. You could store each cell in a separate instance variable, and just return the one that corresponds the current row each time tableView:cellForRowAtIndexPath: is called.
If you create the cells programmatically, add whatever subviews you need at that time. Otherwise, as I mentioned, you could do that in Interface Builder, which often makes it easier to set up the details of controls such as text fields. Note though that UITableViewCell already contains an instance of UILabel, so adding one yourself is redundant. Instead, just access the cell's textLabel property to get its label.

Related

UITableView overlaps on scrolling

In my Storyboard I've got a table view.
I'm filling that table view using data loaded from a JSON file (loaded in viewDidLoad).
In my UITableView I did set "Prototype Cells" to 1 so I can easily select a Accessory.
My Prototype Cell has a Seque to a new View which needs to show the details of the selected item.
I'm filling my Cells programmatically using this:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *videoTableIdentifier = #"VideoCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:videoTableIdentifier];
if (cell == nil)
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:videoTableIdentifier];
UILabel * title = [[UILabel alloc] initWithFrame:CGRectMake(40,5, 240,20)];
[cell addSubview:title];
[title setText:[[videos objectAtIndex:indexPath.row] objectForKey:#"title"]];
UILabel * detail = [[UILabel alloc] initWithFrame:CGRectMake(40,28, 100,10)];
[cell addSubview:detail];
[detail setText:[NSString stringWithFormat:#"Year: %#", [[videos objectAtIndex:indexPath.row] objectForKey:#"year"]]];
[cell addSubview:[[UIImageView alloc] initWithImage:[UIImage imageNamed:[[videos objectAtIndex:indexPath.row] objectForKey:#"image"]]]];
return cell;
}
When I start scrolling the following happens:
So I started searching for a solution and found that I had to set: dequeueReusableCellWithIdentifier:videoTableIdentifier to "nil".
While I did that, this solved my problem, BUT now me Prototype Cell Accessory and Seque are gone so I can't navigate to the next view anymore.
I couldn't find a solution for this on the internets so I decided to ask this.
Would be awesome if someone could help.
Your problem is that every time you re-use a cell, you are adding new subviews to it, so if you have a cell with some labels, and you reuse it and add more labels, all of them appear overlaped.
I suggest you to make your own custom UITableViewCell with labels on it, and just change the value of the labels every time.
if (!title){
title = [[UILabel alloc] initWithFrame:CGRectMake(40,5, 240,20)];
[cell addSubview:title];
}
[title setText:[[videos objectAtIndex:indexPath.row] objectForKey:#"title"]];
Previous answer author is right. It could be better to use your own subclass of UITableViewCell.
I don't see any memory release. Are you using automatic reference counting?

tableviews cells are changing after scrolling down

I am making a form within a grouped tableview. In this form I have UIswitches and textfields. But after scrolling down, the cells styles are changing.
Here is my cellForRowAtIndex
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = nil;
static NSString *MyIdentifier = #"GenericCell";
cell = [tableView dequeueReusableCellWithIdentifier:MyIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:MyIdentifier] ;
}
NSString *text = nil;
if(indexPath.section == CREDENTIALS_SECTION){
if (indexPath.row == 0) {
NSLog(#"tot hier login");
UITextField *login = [[UITextField alloc] initWithFrame:CGRectMake(110, 10, 185, 30)];
login.adjustsFontSizeToFitWidth = YES;
login.placeholder = #"example#gmail.com";
login.keyboardType = UIKeyboardTypeEmailAddress;
login.returnKeyType = UIReturnKeyNext;
login.backgroundColor = [UIColor clearColor];
login.tag = 0;
login.delegate = self;
[login setEnabled: YES];
[cell addSubview:login];
}else if (indexPath.row == 1){
NSLog(#"tot hier pass");
UITextField *pass = [[UITextField alloc] initWithFrame:CGRectMake(110, 10, 185, 30)];
pass.adjustsFontSizeToFitWidth = YES;
pass.placeholder = #"Required";
pass.keyboardType = UIKeyboardTypeDefault;
pass.returnKeyType = UIReturnKeyDone;
pass.secureTextEntry = YES;
pass.backgroundColor = [UIColor clearColor];
pass.tag = 0;
pass.delegate = self;
[cell addSubview:pass];
}
if (indexPath.row == 0) { // Email
text = #"Email";
}
else if(indexPath.row == 1) {
text = #"Password";
}
}else if(indexPath.section == METHODS_SECTION){
UISwitch *toggleSwitch = [[UISwitch alloc]initWithFrame:CGRectMake(220, 10, 100, 30)];
toggleSwitch.tag = indexPath.row;
[toggleSwitch addTarget:self action:#selector(toggleSwitched:) forControlEvents:UIControlEventValueChanged];
[cell addSubview:toggleSwitch];
if (indexPath.row == 0) { // Web
text = #"Web applicatie";
}
else if(indexPath.row == 1) { //Mobile
text = #"Mobiele applicatie";
}
else if(indexPath.row == 2) { //Mail
text = #"E-mail";
}
}else if(indexPath.section == PHONE_SECTION){
UITextField *phoneText = [[UITextField alloc] initWithFrame:CGRectMake(20, 10, 185, 30)];
phoneText.adjustsFontSizeToFitWidth = YES;
phoneText.font = [UIFont fontWithName:#"Arial-BoldMT" size:18];
phoneText.keyboardType = UIKeyboardTypeNumberPad;
phoneText.delegate = self;
phoneText.textColor = [UIColor blackColor];
phoneText.text = _person.phone;
[cell addSubview:phoneText];
}else if(indexPath.section == REMARK_SECTION){
UITextView *textView = [[UITextView alloc]initWithFrame:CGRectMake(20, 10, 280, 260)];
textView.text = _person.remark;
textView.delegate = self;
textView.font = [UIFont fontWithName:#"Arial" size:15.0];
textView.backgroundColor = [UIColor clearColor];
[cell addSubview:textView];
text = #"";
}else if(indexPath.section == BUTTON_SECTION){
cell.backgroundColor = [UIColor redColor];
text = #"test";
}
cell.textLabel.text = text;
return cell;
}
After some searching I found that more people are having this problem. And that the problem lays in this piece of code.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = nil;
static NSString *MyIdentifier = #"GenericCell";
cell = [tableView dequeueReusableCellWithIdentifier:MyIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:MyIdentifier] ;
}
NSString *text = nil;
But I don't find a solution for it.
Hope anybody can help!
Kind regards!
Clarification
Oké so here you see a screenshot of my form. below I have a red cell (save button) when I scroll down other cells are getting a red background. And some cells, text property's are changing.
That is not gong to work. Aparently you did not yet fully understand how the re-use mechanism works.
What do you do?
First you fetch a cell to be re-used. If you get one -fine so far but the problem comes later. If you don't get one then you create a new one.
When you have created a new one, which is the case at start before the user begins scrolling, then you add some UIItems depending on section and row. I will explain why this is not actually a smart thing to do.
Then the user scrolls. Cells will dissappear from screen and then made available for re-use. Then you will fetch the cells for re-use. But it may well happen that those cells already have additional UI-Items on them because you have used them before in that way. In the following process you will add new UI Items regardless whether there are already additional UI-Items on that very cell.
What can you do:
Create your own custom table cell subclasses. One subclass for each set of additional ui items that you may need. That is probably the neatest way of doing it. For each subclass use a different re-use identifier (!!!)
This is what I would recommend!
However, there are alternatives:
You could still live with your concept but invent an individual type of re-use identfier for each type of cell that has some type of additional ui item on it. If so, then make sure that these UI items are only created and added as sub-views in the if (cell == nil) branch of your code. Only create them once and then re-use them. Cell reuse-IDs could be "email-display", "email-input" , "password-display", "password-input", "switch", ...
A variance of the solution above would be, to calculate row and section
into the reuse-identifier. Such as "cell-id-0.2" for section 0 and
row 2 - or so. But still you will have to make sure that you really
re-use the additional UI views and do not re-create them every time
when the cell is filled with data. Plus, the layout in your first section varies depending on whether you want to input password and e-mail or just display them. You will still have to deal with those variations.
If cell == nil - meaning if a cell is re-used - then first clean it from every UI item that you may have added before. You can do that by tagging your UIViews with - let's say 99 - (anything different from 0 should do) upon creation and when reusing enumerate over all subviews and remove those, which have the tag 99. Despite that you can stick with the code that you have already made.
The easiest fix is:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:#"GenericCell"] ;
//some more code
return cell;
}
This would remove the reusability from the tableview, but since it's a limited settings view, it can be ok. I would still advice taking 1 or 2 from Hermann Klecker's solutions.
If you also need to persist UIControl state then use
static NSString *MyIdentifier = [NSString stringWithFormat:#"GenericCell%d",indexPath.row];
It will always return your unique table row and you can use it as required.
Try to remove all subviews from cell before reusing it. Try the code :
if (cell == nil)
{
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:MyIdentifier] ;
}
else
{
[cell.contentView.subviews makeObjectsPerformSelector:#selector(removeFromSuperview)];
}
Remove all subviews before adding the subviews on cell.
if (cell == nil)
{
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:SimpleTableIdentifier]autorelease];
}
else
{
//To remove the subview of cell.
for (UIView *vwSubviews in [cell.contentView subviews])
{
[vwSubviews removeFromSuperview];
}
}
It may solves your problem.
Actually you have some bad code here.
In the mehthod
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
Unless it is not in if (cell == nil), you should NOT initialize and use any
-(void)addSubview:(UIView*)view
Why?
The cells are views which are reused from tableview. So If you add some subview, next time while reusing the cell, it will be added more subviews on it. Simply they are overlapped and may cause MEMORY LEAK.
Do not forget that cells are reusable. So;
if I have the following code unless I do not set text somewhere else. It is expected to all cells has the text in their text labels "this is a text". Because they are reusable.
if (someChangingBool) {
cell.textLabel.text = #"this is a text";
}
So I need to have an else for that if which sets the text something else.
For more Information.

How to get a UITableViewCell change its background color by tapping a UIBarButtonItem?

I'm trying to make a UIBarButtonItem in the right-corner of my tableView in order to highlight and un-highlight a cell when pressed.
In less words, when users press the button, a range of cells will change their background color from white to yellow.
I'm failing to make that though, because every time i press that button the app crashes.
Here's the code i'm using to create the button:
- (void)viewDidLoad {
[super viewDidLoad];
UIBarButtonItem *barButton;
barButton = [[[UIBarButtonItem alloc] initWithImage:[UIImage imageNamed:#"high.png"] style:UIBarButtonItemStyleBordered target:self action:#selector(colorCells:)] autorelease];
self.navigationItem.rightBarButtonItem = barButton;
}
And here to make it highlight a range of cells:
- (void) colorCells:(id)sender
{
UITableViewCell *cell;
NSString *cellValue = cell.textLabel.text;
if ([cellValue isEqual: #"textTheCellShouldBeEqualTo"]){
cell.backgroundColor = [UIColor colorWithRed:251/255.0f green:255/255.0f blue:192/255.0f alpha:1]; ;
cell.imageView.image = [UIImage imageNamed:#"hot.png"];
}
else {
cell.imageView.image = nil;
cell.backgroundColor = [UIColor whiteColor];
}
}
Where am i failing? It's supposed to work fine. Or? Am i missing something? The view is a UITableViewController.
EDIT
I modified my code like so:
- (void) colorCells:(id)sender{
UITableViewCell *cell;
NSInteger nSections = [self.tableView numberOfSections];
for (int j=0; j<nSections; j++) {
NSInteger nRows = [self.tableView numberOfRowsInSection:j];
for (int i=0; i<nRows; i++) {
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:i inSection:j];
cell = [self.tableView cellForRowAtIndexPath:indexPath];
}
NSString *cellValue = cell.textLabel.text;
if ([cellValue isEqual: #"textTheCellShouldBeEqualTo"] ){
cell.backgroundColor = [UIColor colorWithRed:251/255.0f green:255/255.0f blue:192/255.0f alpha:1]; ;
cell.imageView.image = [UIImage imageNamed:#"hot.png"];
}
else {
cell.imageView.image = nil;
cell.backgroundColor = [UIColor whiteColor];
}
[self.tableView reloadData];
}
}
Now it doesn't crash, but it doesn't color the background too. Any ideas?
Basing the background color off of the text in a cell seems incredibly fragile. What determines that a cell should be highlighted? Does it change? Surely, the color of the background corresponds to a specific property of the objects in your data source. A more robust approach would be to use an NSMutableIndexSet property on your class to track a set of row indexes require highlighting upon tapping the bar button.
Consider this example. I'm assuming that rowsToHighlight is an instance of NSMutableIndexSet declared in your class and that it has been populated with the indexes of rows that require highlighting. We're going to implement -tableView:willDisplayCell:forRowAtIndexPath: to adjust the background color of the cell depending if the provided index path is a member of rowsToHighlight.
- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
{
if ([self.rowsToHighlight containsIndex:indexPath.row])
{
cell.backgroundColor = [UIColor yellowColor];
}
}
Then in the method that is fired by your bar button, just do an empty update block to get the table to reload with animation.
- (void)colorCells:(id)sender
{
[self.tableView beginUpdates];
[self.tableView endUpdates];
}
Your cells won't redraw unless you tell the table view which contains them to reload them. You can do this with one of several methods. You may consider [someTableView reloadData] or one of these methods.
Also, your cell object is not the one in your table view. You might consider this:
- (void) colorCells:(id)sender
{
UITableViewCell *cell;
//grab a cell
cell = [self tableView:self.tableView cellForRowAtIndexPath:[NSIndexPath indexPathForRow:someRow inSection:someSection]];
NSString *cellValue = cell.textLabel.text;
if ([cellValue isEqual: #"textTheCellShouldBeEqualTo"]){
cell.backgroundColor = [UIColor colorWithRed:251/255.0f green:255/255.0f blue:192/255.0f alpha:1]; ;
cell.imageView.image = [UIImage imageNamed:#"hot.png"];
}
else {
cell.imageView.image = nil;
cell.backgroundColor = [UIColor whiteColor];
}
//perhaps you need to reload the table as well
[self.tableView reloadData];
}

Crash after attempting to get a UIImageView reference through viewWithTag

I need to draw an image in a table cell. So far I have not been able to get a proper reference to the UIImageView after I create the view and assign it to the cell. The same procedure does work for a UILabel, for example.
I can't figure out what I'm doing wrong.
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
UIImageView *imageView;
UILabel *title;
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault
reuseIdentifier:CellIdentifier] autorelease];
// Setup title
title = [[[UILabel alloc] initWithFrame:CGRectMake(0, 0, 100, 30)] autorelease];
title.tag = 1;
[cell.contentView addSubview:title];
// Setup image
UIImageView* imageView = [[[ UIImageView alloc] initWithFrame:
CGRectMake(50, 0, 50, 50)] autorelease];
imageView.tag = 2;
[cell.contentView addSubview:imageView];
} else {
// Get references to cell views
title = (UILabel *)[cell.contentView viewWithTag:1];
imageView = (UIImageView *)[cell.contentView viewWithTag:2];
}
NSLog(#"%#", [title class]); // UILabel
NSLog(#"%#", [imageView class]); // CRASH! EXC_BAD_ACCESS
return cell;
}
The problem is the scope of the imageView variables. In the case that the cell doesn't exist yet, you create a new UIImageView that only exists in the if-block. It hides the variable that you declared earlier and disappears after the if-block ends.
Instead of
UIImageView *imageView = ...
you should simply write
imageView = ...
Otherwise you're creating a new object that has nothing to do with the object you declared at the top of the method and the original imageView is still undefined.

How can I present a picker view just like the keyboard does?

I want a UIPickerView to show up when I press a button (just like keyboard) and then go away when user taps anywhere on the screen. How can I do this? Thanks.
A bit more background: I have a UITextField called months in UITableViewCell. Now the best option for me is to put a UIPikcerView there and it will be easier for the user to just chose the month rather than having to type it out (and it's the better way). But UIPickerView looks really ugly in a UITableViewCell, not to mention I can't change it's gigantic size. So what should I do in this case?
This is how you should use UIPickerView for a textField in a UITableView.
- (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];
cell.selectionStyle= UITableViewCellSelectionStyleNone;
UITextField *textField = [[UITextField alloc]initWithFrame:CGRectMake(110, 10, 185, 30)];
textField.clearsOnBeginEditing = NO;
textField.textAlignment = UITextAlignmentRight;
textField.delegate = self;
[cell.contentView addSubview:textField];
//textField.returnKeyType = UIReturnKeyDone;
cell.textLabel.backgroundColor = [UIColor clearColor];
cell.detailTextLabel.backgroundColor = [UIColor clearColor];
// if you want a UIPickerView for first row... then do the following //
// Here.. packSizePickerView is an object of UIPickerView
if (indexPath.row == 1)
{
textField.tag=0;
textField.delegate= self;
packSizePickerView.delegate = self;
packSizePickerView = [[UIPickerView alloc] init];
packSizePickerView.autoresizingMask=UIViewAutoresizingFlexibleWidth;
packSizePickerView.showsSelectionIndicator=YES;
textField.inputView = packSizePickerView;
}
// if you want to select date / months in row 2, go for UIDatePickerView //
if (indexPath.row == 1)
{
textField.tag = 1;
UIDatePicker *datePicker = [[UIDatePicker alloc] init];
datePicker.datePickerMode = UIDatePickerModeDate;
[datePicker addTarget:self action:#selector(datePickerValueChangedd:) forControlEvents:UIControlEventValueChanged];
datePicker.tag = indexPath.row;
textField.delegate= self;
textField.inputView = datePicker;
[datePicker release];
}
}
// Configure the cell...
NSInteger row = [indexPath row];
cell.textLabel.text = [myArray objectAtIndex:row];
return cell;
}
And for datePickerValueChangedd
- (void)datePickerValueChangedd:(UIDatePicker*) datePicker{
UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(100, 50, 68, 68)];
NSDateFormatter *df = [[NSDateFormatter alloc] init];
df.dateStyle = NSDateFormatterMediumStyle;
label.text = [NSString stringWithFormat:#"%#",[df stringFromDate:datePicker.date]];
NSLog(#"I am inside the datePickerValueChanged - - %#", label.text);
[df release];
globalValue1 = label.text;
// use a global variable to copy the value from the pickerView. //
}
Now in the textFieldDidEndEditing:
-(void) textFieldDidEndEditing:(UITextField *)textField {
if (textField.tag ==0)
[textField setText: globalValue0];
if (textField.tag ==1)
[textField setText: globalValue1];
}
And to resign UIDatePicker, set the UIView as a UIControl and
resignFirstResponder
I think that you need to put your UIPickerView "outside" the viewable screen and animate it in when the button is pressed. I'm not really sure what the best way to get rid of it would be but you could probably listen for a tap on the parent view when the picker is visible and then animate it back out.