Reloading searchResultsTableView - objective-c

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.

Related

How to indicate new message in UITableViewCell

I have a problem which should be a trivial one but I still can't find a good solution to it.
I have a tableView in which I store cells representing different threads/chatrooms. I have an NSArray with data for cells/chats.
I want to display a badge (with a custom badgeCell) indicating a new message on a corresponding cell.
When I receive a push notification my method searches through the cells and finds indexPath for a cell connected with that new message and adds it to NSArray. Then I reload the tableView.
In tableView:willDisplayCell:forRowAtIndexPath: I check if the cells' indexPath is inside that NSArray of indices and if so I display a badge.
Everything works just fine until I scroll tableView so the cell is not visible. When it comes back again it doesn't have any badge.
Also if my cell is not visible when the Push Notification comes it also doesn't display the badge.
I know it has something to do with ReuseIdentifiers but I can't think of any good solution. Please help me !
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier1 = #"MyCell";
ProblemsTableViewCell *cell;
cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier1];
if(!cell) {
cell = [[ProblemsTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier1];
}
if ([cell respondsToSelector:#selector(setSeparatorInset:)]) {
cell.separatorInset = UIEdgeInsetsZero;
}
[cell setSelectionStyle:UITableViewCellSelectionStyleGray];
cell.delegate = self;
cell.selectionStyle = UITableViewCellSelectionStyleNone;
[cell.contentView.superview setClipsToBounds:NO];
[cell.contentView setClipsToBounds:NO];
cell.clipsToBounds = NO;
return cell;
}
-(void)tableView:(UITableView *)tableView willDisplayCell:(nonnull UITableViewCell *)cell forRowAtIndexPath:(nonnull NSIndexPath *)indexPath{
ProblemsTableViewCell * Cell = (ProblemsTableViewCell*)cell;
/* … some customization - > backgroundColor/images etc. … */
for(NSIndexPath* index in self.newMessages){
if(indexPath.row == index.row){
Cell.badgeString = #"new message"; // the _badge property is a built in view of a custom Cell class with a badge.
Cell.badgeColor = [UIColor whiteColor];
Cell.badgeColorHighlighted = [UIColor whiteColor];
Cell.badge.radius = 0;
Cell.badge.fontSize = 15;
Cell.badge.layer.opacity = 0.55f;
NSLog(#"update 0");
break;
}
}
}
Instead of trying to maintain an array of indices, use the array with cell data. Add an indicator for each element in there to say whether a badge is needed or not and use it to turn the badge on or off during cellForRowAtIndexPath.

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.

Cocoa-Touch – UITableView dequeueReusableCellWithIdentifier gives me wrong cell data

I'm trying to use a UITextField inside a UITableViewCell as you can see in the code below. It seems that when the tableview goes off screen some data that are supposed to be in the cells are mixed up. I would think that there is some problem going on with the method [tableView dequeueReusableCellWithIdentifier:addGroupContactCellIdentifier]; not being able to give me a "proper" cell after the tableview has gone off screen. What is the reason for this?
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *addGroupContactCellIdentifier = #"AddGroupContactCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:addGroupContactCellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault
reuseIdentifier:addGroupContactCellIdentifier];
if ([indexPath section] == 0) { // Group Name Section
cell.textLabel.text = #"Name";
UITextField *groupNameTextField = [[UITextField alloc]initWithFrame:CGRectMake(80, 10, 210, 22)];
groupNameTextField.textAlignment = UITextAlignmentLeft;
groupNameTextField.backgroundColor = [UIColor clearColor];
groupNameTextField.placeholder = #"Type Group Name";
//groupNameTextField.borderStyle = UITextBorderStyleLine;
groupNameTextField.clearButtonMode = UITextFieldViewModeWhileEditing;
groupNameTextField.returnKeyType = UIReturnKeyDone;
groupNameTextField.autocapitalizationType = UITextAutocapitalizationTypeSentences;
groupNameTextField.delegate = self;
[cell.contentView addSubview:groupNameTextField];
}
}
if ([indexPath section] == 1) { // Contacts Section
cell.textLabel.text = [[self.selectedPeoplePickerContacts objectAtIndex:[indexPath row]] objectForKey:#"name"];
cell.detailTextLabel.text = [[self.selectedPeoplePickerContacts objectAtIndex:[indexPath row]] objectForKey:#"number"];
}
cell.accessoryType = UITableViewCellAccessoryNone;
return cell;
}
UPDATE:
So I subclassed UITableViewCell but still it exhibits the same error as before. This is now my code for tableView:cellForRowAtIndexPath::
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *addGroupContactCellIdentifier = #"AddGroupContactCell";
if ([indexPath section] == 0) {
UITableViewCellWithUITextField *cell = [tableView dequeueReusableCellWithIdentifier:addGroupContactCellIdentifier];
if (cell == nil) {
//cell = [[UITableViewCellWithUITextField alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:addGroupContactCellIdentifier];
cell = [[UITableViewCellWithUITextField alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:addGroupContactCellIdentifier textFieldPlaceholder:#"Type Group Name" textFieldDelegate:self];
}
cell.selectionStyle = UITableViewCellSelectionStyleNone;
cell.textLabel.text = #"Name";
// Need to set the UITableViewCell's textLabel properties otherwise they will cover the UITextField
cell.textLabel.opaque = NO;
cell.textLabel.backgroundColor = [UIColor clearColor];
return cell;
} else {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:addGroupContactCellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault
reuseIdentifier:addGroupContactCellIdentifier];
}
cell.textLabel.text = [[self.selectedPeoplePickerContacts objectAtIndex:[indexPath row]] objectForKey:#"name"];
cell.detailTextLabel.text = [[self.selectedPeoplePickerContacts objectAtIndex:[indexPath row]] objectForKey:#"number"];
return cell;
}
}
Third EDIT (I have now 2 different reuseIdentifiers which seem to give me my wanted results):
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
if ([indexPath section] == 0) { // Group Name Section
static NSString *groupNameCellIdentifier = #"GroupNameCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:groupNameCellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault
reuseIdentifier:groupNameCellIdentifier];
cell.textLabel.text = #"Name";
UITextField *groupNameTextField = [[UITextField alloc]initWithFrame:CGRectMake(80, 10, 210, 22)];
groupNameTextField.textAlignment = UITextAlignmentLeft;
groupNameTextField.backgroundColor = [UIColor clearColor];
groupNameTextField.placeholder = #"Type Group Name";
//groupNameTextField.borderStyle = UITextBorderStyleLine;
groupNameTextField.clearButtonMode = UITextFieldViewModeWhileEditing;
groupNameTextField.returnKeyType = UIReturnKeyDone;
groupNameTextField.autocapitalizationType = UITextAutocapitalizationTypeSentences;
groupNameTextField.delegate = self;
[cell.contentView addSubview:groupNameTextField];
}
// Customization
return cell;
} else {
static NSString *addGroupContactCellIdentifier = #"AddGroupContactCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:addGroupContactCellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault
reuseIdentifier:addGroupContactCellIdentifier];
}
// Customization
cell.textLabel.text = [[self.selectedPeoplePickerContacts objectAtIndex:[indexPath row]] objectForKey:#"name"];
cell.detailTextLabel.text = [[self.selectedPeoplePickerContacts objectAtIndex:[indexPath row]] objectForKey:#"number"];
return cell;
}
}
Subclassing is not necessary as some have suggested.
But you cannot use logic like "if ([indexPath section] == 0) {" inside of the "if (cell == nil) {" because that is only called the first time the cell is created, and it will be-re used at other indexes on subsequent recycles.
Instead, you need to use two different CellIdentifiers, so that cells you have set up for section zero do not get re-used at other places in the table. Put your if ([indexPath section] == 0) { before you dequeue the cell and use a different cell identifiers for section zero and subsequent section cells.
Also, make sure you do any indexpath-specific outside of the "if (cell == nil) {" so that it will be applied each time the cell is re-used not just the first time it is created.
You are right ! The problem is definitely due to the reusability feature of UITableView. Apple has done it in such a was so that you can reuse cells, and it works beautifully at a performance stand-point ! And so, when you try scrolling up and down, and indexPath values continue to be the same and your tableView gets data from the cellForRowAtIndexPath that you had defined in your class !
Solution:
You will need to subclass your UITableViewCell and add a UITextField in your -(void)layoutSubviews method.
Then you will need to reference this CustomUITableViewCell and use that to load your TableView.
A link that will help : Read this !
The values are mixed up because when you go offscreen and then reload the table again the cells are dequed from the internal table cells pool but they are not reloaded in the same order they were in the table previously. Note that this mixing will happen even if you have a table with many rows and you scroll it. The solution is to store your textfield data in a "data source" array and then configure the cell.
EXPLANATION
Basically in your code there is one main conceptual flaw: once you have regenerated the cell, you don't configure the content properly (you don't configure it at all). What I mean is that initially, when the table is displayed the first time, the pool is empty. So each new cell that needs to be displayed is recreated from scratch (not dequed from the pool); let's say your table can show 10 cells on screen, so the first 10 cells will be all created from scratch with empty text fields.
Then you start entering text in these fields, and all works correctly.
At a certain point you start scrolling the cell: what happens is that all cells that are in the top will disappear from screen and stored (queued) in the table pool, with their textfield and its edited content; let's say you queue cell at row 0. When a new cell needs to be displayed on bottom of the screen the first thing your code does is to try to deque a cell. Now this time you have a cell in the pool (the cell that was at row 0), this cell is retrieved from the pool and placed in the table, INCLUDED THE TEXTFIELD CONTENT, at row 11. So "magically" you will find a text edited at row 0 in another row, 11. Besides the cells are retrieved in a sparse order from the pool, so after many textfield editings and scrollings you will have a complete mixup.
Solution, and this is the reason of the bug in your code: as soon as the cell has been created or dequed, configure it, that is set the textfield content. How to retrieve the textfield content? store in an array. This is why your view controller is a "data source", because you source data to fill the table. Storing data in the table is a mistake, due to this dequeing mechanism. Example:
groupNameTextField.text=[myTextFieldContentArray objectAtIndex:indexPath.row];
Another solution, but I don't suggest it, is to assign a unique identifier to each cell, that is:
NSString *myCellId = [NSString stringWithFormat:#"CellID_%d_%d",indexPath.section,indexPath.row];
In this case all cells will be enqued with a different name and you will never mix up them.
This solution is working most of the time but it is discouraged for two reasons:
a. it is non-optimal, as you don't reuse cells and so this takes extra memory for similar cells
b. you're not guaranteed that each cell is effectively queued, all in all this logic is inside the table and it's not exposed to the developer, so it may happen that you need to re-generate each time the cell when needed (performance loss).

Detailtext is not showing in case?

I have a case but the detailtext is not showing? Does anyone know what te problem is?
i used: UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
UIViewController *controller;
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
switch(indexPath.row) {
case 0:
{
NSLog(#"Case 0");
controller = [[wed1 alloc] init]; //WithStyle:UITableViewStylePlain];
[self.navigationController pushViewController:controller animated:YES];
cell.detailTextLabel.text = #"Wedstrijden!";
}
break;
case 0:
{
NSLog(#"Case 0");
controller = [[wed1 alloc] init]; //WithStyle:UITableViewStylePlain];
//created a controller
[self.navigationController pushViewController:controller animated:YES];
//at his point, you are showing next ViewController's view
cell.detailTextLabel.text = #"Wedstrijden!";
//here you are changing the text of the text for current tableView's cell (which is actually going to be off the screen as new controller's view will be shown after previous statement execution)
}
with the piece of code you shared - I don't find anything wrong here, the detail text should not be visible as this view will be gone.
I don't fully understand why you'd want to set the detail text on a cell after pushing a new view controller. As samfisher said, when the user presses the first cell, in case 0, the new view controller will be alloc'd, init'd and then pushed, and after that you set the detail text to "Wedstrijden".
I'm assuming you're probably just confused between which property to use. My question to you is: when and where do you want this text to appear?
If the cell was created with the style UITableViewCellStyleDefault, the detail text label isn't displayed. In tableView:cellForRowAtIndexPath: implementation, you may create the cell with style UITableViewCellStyleSubtitle or UITableViewCellStyleValue1.
Use UIListContentConfiguration instead, or subtitle won't show.
#import <UIKit/UIListContentConfiguration.h>
And in the cellForRowAtIndexPath method:
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"AddressCell" forIndexPath:indexPath];
if(cell==nil){
cell=[[UITableViewCell alloc]initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:#"AddressCell"];
}
NSRange range = [self.dataArray[indexPath.row] rangeOfString:#","];
NSString *street;
NSString *restAddress;
if (range.location != NSNotFound) {
street = [self.dataArray[indexPath.row] substringToIndex:range.location];
restAddress= [self.dataArray[indexPath.row] substringFromIndex:range.location + 1];
}
UIListContentConfiguration *configuration = cell.defaultContentConfiguration;
configuration.text=street;
configuration.image=kImage(#"Location");
configuration.secondaryText=restAddress;
cell.contentConfiguration = configuration;
return cell;
}
Will look like this:

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).