Cell is not being rendered in UITableView after insert/delete cells - objective-c

I have a tableview which can work in 2 modes: read-only and read-write. In read-only mode table contains only 4 cells, in read-write mode - 8 cells. To transit between these 2 modes I have button with implemented action:
...
NSArray* cellIndexesInCurrentMode = [self cellIndexesInMode:Mode1];
NSArray* cellIndexInNewMode = [self cellIndexesInMode:Mode2];
[self.tableView beginUpdates];
[self.tableView insertRowsAtIndexPaths: cellIndexInNewMode withRowAnimation:UITableViewRowAnimationFade];
[self.tableView deleteRowsAtIndexPaths:cellIndexesInCurrentMode withRowAnimation:UITableViewRowAnimationFade];
[self.tableView endUpdates];
Everything works fine, until I scroll table down in read-write (8-cells), so it bounced up. After that, tapping on the button to transit to mode2 leads to situation when one of the cells are not renderred:
before transition:
after transition:
My investigation showed that for some reason this cell has alpha = 0 after the last layoutSubviews call on UITableView.
I am stuck, I have no idea what is going on, so any help will be much appreciated.
UPDATE:
Here is my cellForRowAtIndexPath method:
- (UITableViewCell*) tableView :(UITableView*)tableView cellForRowAtIndexPath:(NSIndexPath*)cellForRowAtIndexPath {
IDataSourceElement item = [[self collection] getDataSourceElementAtIndex:cellForRowAtIndexPath.row];
MyTableCell* cell = nil;
if ([item conformsToProtocol:#protocol(MyTableCellBuilder)]) {
cell = [(MyTableCellBuilder)item tableView:tableView buildCellForIndexPath:cellForRowAtIndexPath];
}
if (cell == nil) {
Class cellClass = [self getCellClassForElement: item];
NSString* reuseId = [cellClass description];
cell = [tableView dequeueReusableCellWithIdentifier:reuseId];
if (!cell) {
cell = [[[cellClass alloc] initWithReuseIdentifier :reuseId] autorelease];
}
}
[self fillCell:cell forTableView:tableView forIndexPath:cellForRowAtIndexPath];
return cell;
}
And MyTableCellBuilder buildCellForIndexPath: method implementation
- (MyTableCell*) tableView :(UITableView*)tableView buildCellForIndexPath:(NSIndexPath*)buildCellForIndexPath {
MyTableCell* cell = myTableCell;
if ([cell isKindOfClass:[TextPropertyCell class]] == NO) {
cell = (MyTableCell*)[tableView dequeueReusableCellWithIdentifier :[self cellReuseIdentifier]];
if ([cell isKindOfClass:[MyTableCell class]] == NO) {
cell = [[[MyTableCell alloc] initWithReuseIdentifier :[self cellReuseIdentifier]] autorelease];
}
[self prepareCell:cell];
}
//cell setup
...
...
return cell;
}
My idea was that the problem with caching instance of UITableViewCell, I know that it is not best way to do, but anyway I had it. I tried to remove caching, so all I did I set myTableCell to nil, so it is never returned from this method. And... it helped! I don't know why and would really like to know the answer?

show your cellForRowAtIndexPath method implementation, i'm sure that's where you should look for mistake.

Related

Multiple checkbox in tableview iOS Objective-C

Im working on Multiple checkbox Functionality in tableview, I have to search in google finally i got it one solution.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [self.tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithFrame:CGRectZero reuseIdentifier:CellIdentifier] autorelease];
}
if ([selectedRowsArray containsObject:[contentArray objectAtIndex:indexPath.row]]) {
cell.imageView.image = [UIImage imageNamed:#"checked.png"];
}
else {
cell.imageView.image = [UIImage imageNamed:#"unchecked.png"];
}
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(handleChecking:)];
[cell.imageView addGestureRecognizer:tap];
cell.imageView.userInteractionEnabled = YES; //added based on #John 's comment
//[tap release];
cell.textLabel.text = [contentArray objectAtIndex:indexPath.row];
return cell;
}
- (void) handleChecking:(UITapGestureRecognizer *)tapRecognizer {
CGPoint tapLocation = [tapRecognizer locationInView:self.tableView];
NSIndexPath *tappedIndexPath = [self.tableView indexPathForRowAtPoint:tapLocation];
if ([selectedRowsArray containsObject:[contentArray objectAtIndex:tappedIndexPath.row]]) {
[selectedRowsArray removeObject:[contentArray objectAtIndex:tappedIndexPath.row]];
}
else {
[selectedRowsArray addObject:[contentArray objectAtIndex:tappedIndexPath.row]];
}
[self.tableView reloadRowsAtIndexPaths:[NSArray arrayWithObject:tappedIndexPath] withRowAnimation: UITableViewRowAnimationFade];
}
In that code i have one doubt. what is selectedRowsArray and what is ContentArray Please explain anyone. Thanks in advance.
The selectedRowsArray is another array maintained beside your contentArray which is used to hold the content for of the cells that are currently selected in the UITableView. The contentArray holds the actual content which is populated the cells.
Make note of the following lines:
if ([selectedRowsArray containsObject:[contentArray objectAtIndex:indexPath.row]])
{
cell.imageView.image = [UIImage imageNamed:#"checked.png"];
}
else
{
cell.imageView.image = [UIImage imageNamed:#"unchecked.png"];
}
The cell contents are checked for their presence in the selectedRowsArray which means that the cell is selected.
The selection and deselection is handled by the handleChecking: method which adds or removes the object from the same index in the contentArray into the selectedRowsArray.
You must have understood by now that cells in a UITableView are reused, so the actual number of UITableViewCells in the memory are most probably less than the number of cells to be shown in the UITableView. Hence the need for a system which can actually determine whether the cell is currently selected so this status can be populated when the cell is rendered again in the view.
Alternative methods to achieve the same involve using Booleans in an array representing the status of each cell, or simply the indexes of each selected cell in an array.

UITableView - inserting rows and scrolling to a specific section

I have a tableview like..
The cells xxxx, yyyy & zzzz are fixed, so that, there is no click action to them. But the cell "Show" is clickable. I want to show some six cells under the "Show" cell when it is clicked.
So, after clicking the "Show" cell, the table will look like..
Here, what I done is,
Changed cell.textLabel.text to "Show" to "Hide"
Used [tableView reloadData]; in tableView:didSelectRowAtIndexPath: method.
My code is:
- (void)viewDidLoad
{
[super viewDidLoad];
self.title = #"My title";
// This Mutable array contains the hided cell objects
hidedCellsArray = [[NSMutableArray alloc] initWithObjects:#"Cell 1", #"Cell 2", #"Cell 3", #"Cell 4", #"Cell 5", #"Cell 6", nil];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
...
else if(indexPath.section == 1)
{
if (isShowClicked) //isShowClicked is a boolean value that determines whether the show cell was clicked or not
{
if (indexPath.row == 0)
{
cell.textLabel.text = #"Hide";
cell.selectionStyle = UITableViewCellSelectionStyleBlue;
cell.textLabel.textAlignment=UITextAlignmentCenter;
}
else
{
cell.textLabel.text = [hidedCellsArray objectAtIndex:indexPath.row-1];
cell.selectionStyle = UITableViewCellSelectionStyleBlue;
cell.textLabel.textAlignment=UITextAlignmentCenter;
}
}
else
{
cell.textLabel.text = #"Show";
cell.selectionStyle = UITableViewCellSelectionStyleBlue;
cell.textLabel.textAlignment=UITextAlignmentCenter;
}
}
...
return cell;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
if (indexPath.section == 1)
{
if (isShowClicked)
{
isShowClicked = NO;
}
else
{
isShowClicked = YES;
}
[tableView reloadData];
}
}
What I need:
I want to animate the cells when I click on the show/hide button. I come to know that, the method insertRowsAtIndexPaths: withRowAnimation: should be used to achieve the insertion effect. But I really don't see any simple example for this. Should I include any other methods like setEditing:animated: or tableView: commitEditingStyle: forRowAtIndexPath: methods to do this?
The second thing is, before the animation (cell insertion) happen, I want to move the tableview to section 2 (That is, the "show" cell) to the top. Then the animation of inserting cells should be happen. So, the final appearance of the table after clicking the show cell should like..
Help needed.. I just confused!!
For the first part, you can check "Batch Insertion, Deletion, and Reloading of Rows and Sections" section under Table View Programming Guide for iOS. The most important thing is you need to make sure your data source is matched with the change.
For the second problem, you can set the contentOffset of the table view to the point of the origin of second section title. Something like:
self.tableView.contentOffset = CGPointMake(0, [self.tableView rectForSection:1].origin.y);
If you want to use animation for better UE, just do
[UIView animateWithDuration:duration animations:^{
//Move table view to where you want
} completion:^(BOOL finished){
//insert rows when table view movement animation finish
}];
Try with UIView CommitAnimation
Not to nitpick but the "hidedArray" should really be "hiddenArray", for grammatical correctness.
Anyway, I don't think you need a separate array for the hidden items. For the sake of example let's say you are using "dataArray" to populate the tableview.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
...
if ((indexpath.section == 1) && (indexpath.row == 0)) {
if (isShowClicked) {
cell.textLabel.text = #"Hide";
cell.selectionStyle = UITableViewCellSelectionStyleBlue;
cell.textLabel.textAlignment=UITextAlignmentCenter;
} else {
cell.textLabel.text = #"Show";
cell.selectionStyle = UITableViewCellSelectionStyleBlue;
cell.textLabel.textAlignment=UITextAlignmentCenter;
}
}
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
if ((indexpath.section == 1) && (indexpath.row == 0) && (isShowClicked == NO)) {
isShowClicked = YES;
[self.tableView beginUpdates];
[dataArray addObjectsFromArray:[NSArray arrayWithObjects:#"cell1",#"cell2",#"cell3",#"cell4",#"cell5",#"cell6", nil]];
NSArray *paths = [NSArray arrayWithObject:[NSIndexPath indexPathForRow:[dataArray count]-1 inSection:1]];
//the first section is section 0, so this is actually the second one
[[self tableView] insertRowsAtIndexPaths:paths withRowAnimation:UITableViewRowAnimationFade];
[tableView endUpdates];
showIsClicked = YES;
self.tableview.contentOffset = xy;
//the value of the tableView's contentOffset when it is scrolled to the right position. You can get it by NSLog-ing the contentOffset property and scrolling to the desired position
} else if ((indexpath.section == 1) && (indexpath.row == 0) && (isShowClicked == YES)) {
isShowClicked = NO;
[self.tableView beginUpdates];
[array removeObjectsAtIndexes:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(indexpath.row + 1, 6)]];
[tableView endUpdates];
self.tableview.contentOffset = xy;
//the value of the tableView's contentOffset when it is scrolled to the right position. You can get it by NSLog-ing the contentOffset property and scrolling to the desired position
}
}
There might be a few mistakes but i think it's mostly correct.

Reloading searchResultsTableView

I have, in my main tableView, some custom cells (cells with an imageView, basically).
The imageView is set only if a value on my plist is false. Then, when it's true, the imageView is nil.
Basically when the user enters the detailView, the value is set to YES and the cell.imageView is nil.
And it's okay, it works
I'm using a searchDisplayController, when i search for something that has a cell.imageView, going into the detailView and then coming back to the searchResultsTable, the cell has still the image, while it shouldn't, but the main tableView has the correct cell (so, with no image).
I thought that it could depend on searchResultsTableView, but i'm not sure.
I tried with
[self.searchDisplayController.searchResultsTableView reloadData];
with no effect.
How could i reload the searchResultsTableView so that it shows the right cells, those with the image and those that don't have the image anymore?
Any help appreciated!
EDIT
This is my cellForRowAtIndexPath method:
- (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];
}
NSArray *rows;
if (tableView == self.searchDisplayController.searchResultsTableView) {
rows = filteredList; //for search
} else {
NSDictionary *section = [localSortedTips objectAtIndex:indexPath.section];
rows = [section objectForKey:#"Rows"];
}
NSDictionary *item = [rows objectAtIndex:indexPath.row];
cell.textLabel.text = [item objectForKey:#"name"];
if ([[item valueForKey:#"isRead"] boolValue] == NO) {
cell.imageView.image = [UIImage imageNamed:#"unread.png"];
} else {
cell.imageView.image = nil;
}
cell.textLabel.font = [UIFont boldSystemFontOfSize:15.0];
cell.textLabel.adjustsFontSizeToFitWidth = YES;
return cell;
}
If I understood you right, then you can have a workaround but searching again with the same search string:
if (self.searchDisplayController.active) {
self.searchDisplayController.searchBar.text = self.searchDisplayController.searchBar.text;
}
put it in viewWillAppear: or viewDidAppear: which will be called each time the view is shown up (eg. you go back from the detail view to your search view). And reloading the data in this place would be nice too, to get the right data (for example if you marked the cell as read like in your sample code)
Just [self.tableView reloadData]; and not the searchResultsTableView (it will be automatically use the updated data after the new search)
It sounds as if perhaps your cells are being recycled and not reset properly. UITableView uses view recycling, so it's important that if you do something such as set an image you make sure it is explicitly set even when their isn't an image to display.
If you share your cellsForRowAtIndexPath code you might be able to get some more help.

Can't reach the right text of a UITextField object in a modal UITableViewController subclass

I've got a sublass of UITableViewController which I use to add a new data for a new cell within another Table. That means, I've got a "Cancel" and a "Save" button at the top to save the data and get back to the table where to add the new object into. I show the adding controller modally.
My problem is though that I don't know how to get the data from the user input cells of the tableView in my UITableViewController subclass to save them in my CoreData file.
Here's the code how I try it - but at the console I just get (nil) for the persons name:
- (void)save
{
NSLog(#"Saving ...");
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:0 inSection:0];
UITableViewCell *cell = [self tableView:[self tableView] cellForRowAtIndexPath:indexPath];
NSString *personName = [[cell textField] text];
NSLog(#"New Person: %#", personName);
// Do saving with Core Data and updating here.
[self dismissModalViewControllerAnimated:YES];
}
I guess you will also need the following method to understand the problem:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
EditableDetailCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[EditableDetailCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
cell.accessoryType = UITableViewCellAccessoryNone;
if (indexPath.section == 0) {
[cell.textField setPlaceholder:#"Name"];
} else {
switch (indexPath.row) {
case 0:
[cell.textField setPlaceholder:#"Birth Year"];
break;
case 1:
[cell.textField setPlaceholder:#"Weight in kg"];
break;
case 2:
[cell.textField setPlaceholder:#"Height in cm"];
break;
case 3:
[cell.textField setPlaceholder:#"Sex"];
break;
default:
break;
}
}
return cell;
}
I really can't find the failure. In my understanding it should work this way ... but it doesn't. Could anyone please help?
Don't call your table view datasource method to get the value in the cell. Get it directly from the table view instead.
Instead of this:
UITableViewCell *cell = [self tableView:[self tableView] cellForRowAtIndexPath:indexPath];
Do this:
UITableViewCell *cell = [[self tableView] cellForRowAtIndexPath:indexPath];
Doing it the way you are doing is essentially recreating the cell and so losing the value you have entered. The datasource method is only meant to be called by the table to work out what to display.
In your method to set the cell text, you alter the textField of the cell, but the string you grab to log/save to Core Data is from the textLabel. Is this an intentional discrepancy?

2 NSArrays in one UITableView with Label as subView?

I'm having a problem with my current App. It has one UITableView in the UIViewController. I have one UIButton at the bottom (out of the UITableView). It works in that way:
if ([[NSUserDefaults standardUserDefaults] boolForKey:#"bla"]) {
[[NSUserDefaults standardUserDefaults] setBool:FALSE forKey:#"bla"];
[tableView reloadData];
} else {
[[NSUserDefaults standardUserDefaults] setBool:TRUE forKey:#"tasks2do"];
[tableView reloadData]; }
This worked when I had the cell.textLabel.text Method in this way:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *ident = #"indet";
cell = [tableView dequeueReusableCellWithIdentifier:ident];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithFrame:CGRectZero reuseIdentifier:ident] autorelease];
}
if (![[NSUserDefaults standardUserDefaults] boolForKey:#"bla"]) {
cell.textLabel.text = [firstArray objectAtIndex:indexPath.row];
} else {
cell.textLabel.text = [secondArray objectAtIndex:indexPath.row];
}
return cell; }
Now I want to use an UILabel instead of cell.textLabel, because I need it for some reasons (eg. setting the labels frame)
For that I used the following code:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *ident = #"indet";
cell = [tableView dequeueReusableCellWithIdentifier:ident];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithFrame:CGRectZero reuseIdentifier:ident] autorelease];
}
UILabel *thislabel = [[[UILabel alloc] initWithFrame:CGRectMake(10, 10, 250, 44)] autorelease];
if (![[NSUserDefaults standardUserDefaults] boolForKey:#"bla"]) {
[thislabel setText:[firstArray objectAtIndex:indexPath.row]];
} else {
[thislabel setText:[secondArray objectAtIndex:indexPath.row]];
}
[cell.contentView addSubview:thislabel];
return cell; }
That works fine, until I push the UIButton for switching. It switches, the cell shows the new text but behind the new text is still the old text as you can see here:
http://d.pr/Rqx2
(the firstArray contains the letter L, the secondArray contains the Letter J, it mixes up both up)
Do you have any idea for solving this problem since I tried some stuff (for example using 2 UILabels for the arrays and hide one)? Would be cool. :)
I hope my English is not too bad to understand, my English skills for writing aren't the best, I'm sorry for that.
If you need further information / code just post it, shouldn't be a problem.
I recommend you create a UITableViewCell subclass in which you configure the label (set frame and add it as subview in UITableViewCell's initializer). Add a property for setting the text in the label and write a setter like this for the property:
- (void)setLabelText:(NSString *)newLabelText
{
if ([self.labelText isEqualToString:newLabelText]) return;
[labelText autorelease];
labelText = [newLabelText copy];
self.label.text = labelText;
[self.label setNeedsDisplay]; // or perhaps [self setNeedsDisplay];
}
Edit: by the way, the issue you're dealing with is the caching. You recreate a new label every time a cell comes in view, even if the cell already had a label before. This happens because you initialize an UILabel outside the UITableViewCell initializer (which only should be called once for every cached cell, afterwards it can be retrieved from cache, including all it's subviews).