The UITableViewController in my app pulls data from a json data source. I have also created a custom UITableViewCell background using CG. There is a very interesting bug that happens and I have no idea why. I will walk you through what happens and how I recreate it:
Tap to enter table view.
Without scrolling the table at all I immediately tap on an item in view.
After tapping on that item I press the back button to return to the table view.
If I then scroll down the first cell to appear from off screen will not have my custom back ground. It will just be the default for a cell. Then if I continue to scroll down every 10th cell will have the same issue.
This bug only occurs in this exact process. If I were to scroll the table view at all before tapping on an item it would not happen.
Here is the relevant code for the tableview controller:
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
// Will remove all of the used codes from the table if setting is enabled
if (self.shouldHideCodes) {
NSMutableArray *tempArray = [self.jsonCodeData mutableCopy];
[tempArray removeObjectsInArray:[self.usedCodes usedCodes]];
self.jsonCodeData = tempArray;
}
return [self.jsonCodeData count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell;
if (self.jsonCodeData) {
cell = [tableView dequeueReusableCellWithIdentifier:#"code cell"];
if ([cell isKindOfClass:[CodeCellTVC class]]) {
CodeCellTVC *tvcCell = (CodeCellTVC *)cell;
if (![tvcCell.backgroundView isKindOfClass:[CustomCellBackground class]]) {
tvcCell.backgroundView = [[CustomCellBackground alloc] init];
}
NSDictionary *codeDict = [self.jsonCodeData objectAtIndex:indexPath.row];
// Retrieve code string from dictionary
NSString *codeText = [NSString stringWithFormat:#"%#", [codeDict objectForKey:#"code"]];
tvcCell.codeTableLabel.text = codeText;
}
}
return cell;
}
The thing that confuses me is how it reacts. That when the bug happens every 10th cell has the issue and not every one. I don't have anything outside of these method's that deal with the tableviewcell itself.
I understood your problem, you did a wrong at the time of initializing the cell,Every time your intializing the cell, so that every time memory will allocate for that cell, it will create memory issue.
Edit the code like bellow it will work for you.
cell = [tableView dequeueReusableCellWithIdentifier:#"code cell"];
if(cell == nil)
{
cell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:#"code cell"];
}
Related
I use tableview with reusable cells. On each cell I have a textField with text, which I can modify. If text is empty, I delete that cell.
Lets say that we had 100 rows and we want to modify row number 1: we tap on it, give an empty string #"", scroll down to position number 50 and tap on this cell.
What now is going is that we detect tap gesture on another cell and I call method textFieldDidEndEditing: to see should I remove this cell from tableview. I use cellForRowAtIndexPath: to get the modified cell.
The problem is that there appear other cells with empty textField. I delete modified cell, but only one. I think that this is a problem with reusable cells.
Can anybody can help me with this problem?
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *ImageIdentyfier = #"StandardCellWithImageIdentifier";
StandardCellWithImage *cellImage = (StandardCellWithImage *)[tableView dequeueReusableCellWithIdentifier:ImageIdentyfier];
if(cellImage == nil) {
cellImage = [[StandardCellWithImage alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:ImageIdentyfier];
}
cellImage.nameLabel.delegate = self;
Item *item = [self.mutableFetchResults objectAtIndex:indexPath.row];
cellImage.nameLabel.text = item.itemText;
cellImage.infoLabel.text = item.itemInfo;
cellImage.checkbox.userInteractionEnabled = YES;
cellImage.nameLabel.userInteractionEnabled = NO;
if(item.itemIsChecked.boolValue == YES) {
cellImage.checkbox.tag = indexPath.row;
[cellImage.tapGesture addTarget:self action:#selector(didSelectedImageAtIndexPath:)];
cellImage.checkbox.image = [UIImage imageNamed:#"checkbox-checked.png"];
} else {
cellImage.checkbox.tag = indexPath.row;
[cellImage.tapGesture addTarget:self action:#selector(didSelectedImageAtIndexPath:)];
cellImage.checkbox.image = [UIImage imageNamed:#"open-checkbox.png"];
}
return cellImage;
}
When you scroll from row 1 to row 50, already existing cells are reused - including your cell with empty textField. That is why you see it several times and why your delete routine removed only one instead of all.
Sounds like your cell creation at cellForRowAtIndexPath method needs fixing to make sure empty textfield is not automatically copied to recycled cells. Without seeing any code, this exercise is left to you.
Looked at code, thanx. Could not see any "easy" fix, so proposing that you should avoid the problem. So instead of checking cell taps, maybe you should check list scrolling.
The problem you have exists only because cell, which was being edited, was recycled due user scrolling the list. Therefore remove the problem by a) don't let user to scroll while editing text or b) stop text edit when user starts scrolling.
when your textFieldDidEndEditing is invoked finish , you should check whether the text is "" if it is "" I think you should delete it from the dataSource and then reloadData
your - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath method should write like this :
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
XXXXXXXCell *cell = [tableView dequeueReusableCellWithIdentifier: kIdentifier];
if (cell == nil) {
//Init cell, only init
}
//Setup the cells detail, such as the textField.text and so on
return cell;
}
When I scroll down and up again my text in tableView will disappear.
And my code is:
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return [screenDefBuild.elementsToTableView count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *MyIdentifier = #"MyIdentifier";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:MyIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:MyIdentifier];
}
ScreenListElements *currentScreenElement = [screenDefBuild.elementsToTableView objectAtIndex:indexPath.row];
cell.textLabel.text = currentScreenElement.objectName;
currentRow++;
return cell;
}
- (void)viewDidLoad
{
[super viewDidLoad];
tableView = [[UITableView alloc] initWithFrame:self.view.bounds];
[tableView setDataSource:self];
[self.view addSubview:tableView];
}
I also want to fill my table view to entire screen. (grey strap on the top).
I don't know what you're doing with this variable
currentRow++;
But whatever you use that for, i'd wager its breaking your code.
the UITableView will call cellForRowAtIndexPath every time a cell is about to appear on screen regardless of whether it has been on screen before or not. When you scroll down and then scroll back up this variable will have increased beyond the bounds of your data, hence you get empty cells.
You need to design this method in such a way that it can create any cell in the table view at any time. You can't rely on the order that the cells will be made and with scrolling you will have to make the same cell over and over again. Only use indexPath to figure out which cell you are currently supposed to be making.
http://developer.apple.com/library/ios/#documentation/UIKit/Reference/UITableView_Class/Reference/Reference.html
Answer for the second part of the question- grey strap. You are adding the table view to current view, so you should use the size property of self.view.frame but not the origin. You want this to be set to 0,0.
Change
tableView = [[UITableView alloc] initWithFrame:self.view.bounds];
to
CGRect viewFrame=self.view.frame;
viewFrame.origin=CGPointZero;
tableView = [[UITableView alloc] initWithFrame:viewFrame];
As for the first part of your question- it's strange, as you seem to do everything properly. One thing i may suggest is to add [tableView reloadData]; in the end of viewDidLoad.
I have a table view with a custom cell. My cell has an image on the left, and the image is a black png that I have switch to a white png when it is selected. The cell also has a push segue attached. My problem is when the back button is pressed in the destination view and you return to the table view, the icon is still white, so it looks like it disappeared. I need to load the black icon back in. I can't use didDeselectRowAtIndexPath, because the view is unloaded before it has an opportunity for this to kick in. Is there another method that's out there that I just don't know about? This is my current code:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
NSArray *array = [self.listArray objectAtIndex:indexPath.section];
StartRow *rowobject = [array objectAtIndex:indexPath.row];
StartCell *cell = [tableView cellForRowAtIndexPath:indexPath];
cell.mainIcon.image = rowobject.iconWhite;
}
- (void)tableView:(UITableView *)tableView didDeselectRowAtIndexPath:(NSIndexPath *)indexPath {
NSArray *array = [self.listArray objectAtIndex:indexPath.section];
StartRow *rowobject = [array objectAtIndex:indexPath.row];
StartCell *cell = [tableView cellForRowAtIndexPath:indexPath];
cell.mainIcon.image = rowobject.icon;
}
You actually don't want to reloadData each time the table view about to appear. Instead you want to find the index path for the currently selected cell and then use this knowledge to hunt down said cell.
After you've found the cell that's selected, you simply swap out the image.
in viewwillappear write this
[tableview reloadData];
I have a UITableViewController which has two sections. The first section shows a single cell with centered text, saying Add a new Slide. The second section show the current slides.
When a user taps on the Add a new slide cell, a new UITableVeiwController is pushed onto the stack that shows an editor. If the user saves the new slide (by tapping save), the cell is added to the data source and the editor is popped from the stack.
I have two problems:
When the editor is popped, if a cell was deleted before Add a new slide was tapped, the old cell shows up instead of the new one. Popping the UITableViewController (by tapping the automatically generated back button) fixes this, but I'd like this to not happen at all. (Originally, popping the table did not update after popping the editor, so I added [self.tableView reloadData]; to the viewDidAppear method.)
After a certain number of slides, the last slide on the list becomes the Add a new slide cell. I know that the data is being entered properly because another part of the app, which uses the same data source, updates correctly. The table supports editing in the second section, and when you swap the order of the cells, it behaves correctly behind the scenes, but the wrong cell is still there.
What could be going on?
Here's some of my code:
Note that as I was gearing to post my code, I noticed a mismatch of the braces. The check for cell==nil seems to encompass the second part of the code which determines the content of the cells. This fixes the label of the cells in the second section of the table, but the style is still wrong. I've since fixed the code, but the original is posted here.
// Customize the appearance of table view cells.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
if ([indexPath section] == 0 ) {
cell = [[[MBTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
}else if([indexPath section] == 1){
cell = [[[MBTableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier] autorelease];
}
if ([indexPath section] == 0) {
[cell.textLabel setTextAlignment:UITextAlignmentCenter];
[cell.textLabel setText:#"Add a New Slide"];
}else if([indexPath section] == 1){
NSArray *storedViewsInfo = [[NSArray alloc] initWithArray:[kSettings arrayForKey:#"views"]];
if ([[[storedViewsInfo objectAtIndex:[indexPath row]] valueForKey:#"type"] isEqualToString:#"announcement"]) {
[cell.detailTextLabel setText:#"Custom Announcement"];
[cell.textLabel setText:[[[storedViewsInfo objectAtIndex:[indexPath row]] valueForKey:#"info"] valueForKey:#"text"]];
}
[storedViewsInfo release];
[cell setAccessoryType:UITableViewCellAccessoryDisclosureIndicator];
}
}
return cell;
}
Without seeing the code, first thing that comes to mind is checking if you've given your custom cells different identifiers in your - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath; method?
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier1 = #"CellIdentifier1";
static NSString *Cellidentifier2 = #"CellIdentifier2";
if (indexPath.section == kAddSlideSection) {
CustomCell *cellType1 = (CustomCell*) [tableView dequeueReusableCellWithIdentifier:CellIdentifier1];
...
} else {
CustomCell *cellType2 = (CustomCell*)[tableView dequeueReusableCellWithIdentifier:CellIdentifier2];
...
}
}
Also it might be worth considering implementing a delegate method that gets called when your user finishes adding the new slide - i.e. if successful call [self.tableview reloadData] from that method instead of in viewWillAppear.
In a lot of iPhone apps, I see a UITableViewController being used as a checkbox list. (See, for an example of what I mean, Auto-Lock under Settings)
While trying to implement this myself, I had to jump through a lot of hoops in order to have an item selected programmatically by default (ie., the current value for what the list represents). The best I've been able to come up with is by overriding the viewDidAppear method in my view controller class:
- (void)viewDidAppear:(BOOL)animated {
NSInteger row = 0;
// loop through my list of items to determine the row matching the current setting
for (NSString *item in statusItems) {
if ([item isEqualToString:currentStatus]) {
break;
}
++row;
}
// fetch the array of visible cells, get cell matching my row and set the
// accessory type
NSArray *arr = [self.tableView visibleCells];
NSIndexPath *ip = [self.tableView indexPathForCell:[arr objectAtIndex:row]];
UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:ip];
cell.accessoryType = UITableViewCellAccessoryCheckmark;
self.lastIndexPath = ip;
[super viewDidAppear:animated];
}
Is this the best/only/easiest way to get a reference to a particular cell and indexPath if I want to mark a row by default?
In order to display the status items, you have to implement tableView:cellForRowAtIndexPath: anyway, don't you? So, why not just set the accessory type of the cell before returning the cell, like this:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
// dequeue or create cell as usual
// get the status item (assuming you have a statusItems array, which appears in your sample code)
NSString* statusItem = [statusItems objectAtIndex:indexPath.row];
cell.text = statusItem;
// set the appropriate accessory type
if([statusItem isEqualToString:currentStatus]) {
cell.accessoryType = UITableViewCellAccessoryCheckmark;
}
else {
cell.accessoryType = UITableViewCellAccessoryNone;
}
return cell;
}
Your code is extremely fragile, especially because you use [self.tableView visibleCells]. What if there are more status items than rows fitting on the screen (as the name suggests, visibleCells only returns the currently visible cells of the table view)?