Highlighting cell when selected - iphone

I have a tableView, and i can select multiple records. When i click more that 1 record the cell should get highlighted. i have attached my code below; What hapence now is that when i click on multiple records it higlights, but when i try to remove it (as in click the same row again), a different cell gets un-highlighted. Why is this ?
But if i replace the [cell setHighlighted:YES animated:YES/NO]; with [cell setAccessoryType:UITableViewCellAccessoryCheckmark]; everything works properly. How can i fix this ?
I have added code in the didSelectRowAtIndexPath
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
if ([cell accessoryType] == UITableViewCellAccessoryNone) {
[cell setHighlighted:YES animated:YES];
}
else {
[cell setHighlighted:NO animated:YES];
}

You might want to use UITableViewCellAccessoryCheckmark, as it is intended for this purpose.

According to apple developer documentation for selecting more than one row you should use .
cell.accessoryType = UITableViewCellAccessoryCheckmark;
and for Deselect
cell.accessoryType = UITableViewCellAccessoryNone;

As others have stated, highlighting is not the preferred way to do this. Users want a consistent experience across apps, which is why the Human Interface Guidelines recommend using a checkmark to indicate row selection: http://developer.apple.com/library/ios/#documentation/userexperience/conceptual/mobilehig/UIElementGuidelines/UIElementGuidelines.html#//apple_ref/doc/uid/TP40006556-CH13-SW42 (Table 7-1)

Aside from the UI issues being raised, the problem you are experiencing may be due to your table cells being reused, and retaining the highlighted state from the record that was previously displayed in that cell.
I suggest you keep a separate record of indexes/id's for those highlighted. You should then always check against this, rather than the state of the cell itself. (You can use NSArray or other collection class depending on your needs).
In tableView:cellForRowAtIndexPath: you should also check this array and set the highlighted state accordingly.

Related

UITableView renames every eighth cell

I have a UITableView in my MainViewController. When a user taps a cell,
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
selectedRow = indexPath;
....
[self performSegueWithIdentifier:#"OtherViewControllerSegue" sender:self];
}
and they are taken to another UIViewController (let's call it OtherViewController). In OtherViewController, the name for the selected cell is set. When OtherViewController is dismissed, it updates the cell in MainViewController with the new name:
[[[mainvc.myTableView cellForRowAtIndexPath:mainvc.selectedRow] textLabel] setText:namecell.textField.text];
[self.navigationController popViewControllerAnimated:YES];
This all works fine until I have more cells than will fit on the screen. If there are more cells than will fit on the screen (8 for iPhone or 16 for iPad), then this will also set the name for every eighth or sixteenth cell respectively. Any ideas on what I am doing wrong?
Update:
- (UITableViewCell *) tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [pointsTableView dequeueReusableCellWithIdentifier:#"myTableCell"];
return cell;
}
This is due to cell-reuse and you are mixing up your model with your view (in the MVC context).
A table-cell is a transient thing, once it goes off the screen it is reused (instead of creating new cells) when another cell is needed. This is what the dequeueReusableCellWithIdentifier: method does.
This means you can't store data in there and expect it to still be valid later on. In this example you are trying to store the name in the table cell. The reason to set a property (like the label text) on any view object is purely for display, not for storage. So to solve this problem you should maintain a list of objects in your model (this could be in separate classes or in an array in your mainvc object for example). Then in cellForRowAtIndexPath: you should set the label text every time - even when there should be no label you need to set it to nil or an empty string because the cells are re-used it might contain something from the last time it was used.
Update:
Instead of calling cellForRowAtIndexPath: yourself and setting its text, you should set the text in your model using a method or property in your controller and then tell the table view to reload that cell. The code might look something like this:
// This code is in where you want to set the text from
[mainvc setText:someText forIndexPath:indexPath];
.. and in your main view controller:
- (void)setText(NSString*)newText forIndexPath:(NSIndexPath*)indexPath
{
// Store the text in your model here...
...
// If the view is loaded, the table view should reload the cell.
if(self.isViewLoaded)
{
[self.tableView reloadRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
}
}
The table view will then call cellForRowAtIndexPath: where the text will be set correctly. This may seem a little convoluted at first, but when you get used to using the Model-View-Controller design pattern you will find that keeping the jobs of each MVC component separate like this will mean your code is tidier, easier to understand, has less bugs, is easier to update/extend, etc.
You're trying to store data (the new name) in a view (the cell's label). What's probably happening is that when you re-use cells in the data source's cellForRowAtIndexPath method, some of them are ones that have had this text set for them and it's still there.
The better idea is to make your changes in whatever array you use as cell information and then reload the table view to make the changes visible.
As I suppose, you shouldn't call cellForRowAtIndexPath by yourself. It can be called to create cell, not to change it.
You can update your table by passing needed string to the first view via delegate, for example. And on the event (user sets the name) you can update all table and set needed names to cells.
Hard to say exactly what the problem is, but one possible solution might be this:
Make sure that in your cellForRowAtIndexPath you are initializing the cells like this:
// Create the Cell
static NSString *recordCell = #"pickerTableCell";
cell = [tableView dequeueReusableCellWithIdentifier:recordCell];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:recordCell];
}
I know this is primarily a memory solution, but might gelp here too.
Also, look through your code and check how you are determining which cell is renamed. You could be accidentally calling the rename on more than one cell without realizing it

How to handle references to UITextFields in dynamic UITableView (iPhone)

I have a UITableView that is broken up into a user defined number of sections. Within each of these sections there are always 2 rows. Each of these two rows contains a UITextField which the user is able to edit.
What I need is a way of being able to access the data in these UITextFields at a later point. I was hoping it would be a simple problem however it's causing me a great deal of grief.
So far I have tried two approaches:
Attempt 1
I created two NSMutableArrays in which I added the UITextField objects to at index i (corresponding to the section it came from). I then tried to access the values by iterating through the array. This didn't work since the UITextFields kept getting wiped clean. Every-time I scroll down the table and the UITextField is out of view, when I go back it's contents have been wiped clean.
Attempt 2
I tried to get hold of the number of sections in the UITableView (this was fine). I then wanted to iterate through each section of the UITableView, recording the values in the rows of each. This is where I became unstuck since I'm not sure how to do this or if it's even possible.
I apologise if this is a naive question to ask, however I'm really struggling and would appreciate any advice.
Keep in mind that the text fields get reused as you scroll, so you don't really want to store references to them.
What you do instead, is to capture the information as it is entered. The easiest way to do this is to implement the textFieldDidEndEditing protocol method in the delegate.
The tricky part is figuring out which row the text field is in. The best way is to create a UITableViewCell subclass which has a NSIndexPath property. You can then set that when you configure the cell with tableview:willDisplayCell:forRowAtIndexPath:.
Then, in textFieldDidEndEditing, access the tableViewCell indexPath property through its superview. i.e.:
NSIndexPath indexPathOfParentCell = [(MyUITableViewCellSubclass *)self.superview indexPath];
Doing it this way allows you to know both the section and row of the cell.
Create your TextField in the cellForRow of the Table like so and give it a tag
UITextField * userField = [[[UITextField alloc] initWithFrame:CGRectMake(0, 12, self.view.frame.size.width -20, 20)] autorelease];
userField.tag = 1001;
userField.font = [UIFont fontWithName:#"HelveticaNeue-Bold" size:14];
userField.textAlignment = UITextAlignmentCenter;
userField.delegate = self;
userField.autocorrectionType = UITextAutocorrectionTypeNo;
userField.autocapitalizationType = UITextAutocapitalizationTypeNone;
userField.clearButtonMode = UITextFieldViewModeWhileEditing;
if (indexPath.row == 0)
[cell.contentView addSubview:userField];
then access the TextField like so:
UITextField *userField = (UITextField *)[[(UITableViewCell *)[(UITableView *)tableView cellForRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0]] contentView] viewWithTag:1001];
Your "Attempt 1" should work OK if you keep the text field's text in your arrays rather than the text field itself. (Anything that tries to make a view or control into a data object has a good chance of going wrong.)
Whatever acts as a data source for your table view should be able to re-populate the scrolled cells according to section and row if the content is stored separately.

UITableView cell redraw causes text to overlap

I do following in cellForRowAtIndexPath
NSString *cellIdentifier = #"Cell";
LibraryCell *cell = (LibraryCell*)[tableView dequeueReusableCellWithIdentifier:cellIdentifier];
if (cell != nil) {
[cellIdentifier release];
[self setItems:cell forRowAtIndexPath:indexPath];
return cell;
}
cell = [[[LibraryCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:cellIdentifier] autorelease];
[self setItems:cell forRowAtIndexPath:indexPath];
return cell;
[self setItems:cell forRowAtIndexPath:indexPath]; changes only some UILabel's value inside the corresponding cell.
So now when I edit UITableView and delete let say the first row, the UILabel's value is no redrawn completly, i.e. the old value remains and new value is drawn overlapping the old one.
Why this happens and how to fix it ?
I guess you misunderstood the cell identifier concept.
It is only used to distinguish what you might call "stamps", used to print a cell's appearance on the screen. So you would most probably only need a single cell identifier.
It helps the system to cache instances of the "stamps". When cellForRowAtIndexPath is called, you only have to pick which kind of stamp you want to use. If you created an instance of the correct one before (i. e. you get one back when asking for it using the cell identifier string), you only need to change the label texts etc. and then return it. In real life this might be likened to one of those date stamps where you can change the date by turning the little knobs on the stamp. This is what you would do by assigning a new text to the label contained in the cell.
You instead seem to be creating a stamp for each index in your model by concatenating the string value, effectively creating as many instances as there are rows in your model. Apart from being unnecessary it might also cause memory pressure and stuttering, because it counteracts all sorts of optimizations the UITableView has.
I recommend you read up on Apple's documentation or see iTunes U (here)
for the Stanford courses on iOS development. It gets explained very clearly there.
First of all, You should use the same name for your cellIdentifier
Please refer to the UITableView Class reference
http://developer.apple.com/library/ios/#documentation/uikit/reference/UITableView_Class/Reference/Reference.html#//apple_ref/occ/instm/UITableView/dequeueReusableCellWithIdentifier:
If you cannot get a reusable cell, then create it. After that update your cell.
The code should be like this
LibraryCell *cell = (LibraryCell*)[tableView dequeueReusableCellWithIdentifier:cellIdentifier];
if (cell == nil) {
// create a new cell
...
}
// Update cell
...
return cell;
Actually I figured out ! The problem was coming from setItems:forIndextPath method.
It was creating UILabel locally and releasing it. So every time it was drawn over the last text text. Making the UILable instance variable solved the issue.
PS. The code originally was not written by me, I'm just fixing it :)

How to reuse uitableviewcell??

Every time when scrolling the cells reload shouldn't be trigged.
Apple knows: http://developer.apple.com/library/ios/#documentation/userexperience/conceptual/TableView_iPhone/TableViewCells/TableViewCells.html%23//apple_ref/doc/uid/TP40007451-CH7-SW19
Yes you are correct. I'll elaborate on Steven's post of the Apple documentation a little.
For cells not to be reloaded, first look at this method:
TimeZoneCell *timeZoneCell = (TimeZoneCell *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
It says "give me the existing view that represents a table cell". This is a great saving because if the user is scrolling up or down quickly, creating views can be quite expensive.
Of course, you can't re-use a cell if one hasn't been created in the first place. So, you need to handle that scenario. That's where this code comes in:
if (timeZoneCell == nil) {
timeZoneCell = [[[TimeZoneCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
timeZoneCell.frame = CGRectMake(0.0, 0.0, 320.0, ROW_HEIGHT);
}
It says, "is the cell nil?", i.e., have we not yet created a table cell? It then goes on to create one. That cell will subsequently be be re-used.
The actual contents of the cells will change, for example the label or whatever. However you will continue to re-use the existing objects.

Reloading UITableViewCell on select

Okidoke. Here's my problem: I have a series of complex UITableViewCells set up to display stories from a news feed (yawn). What I want to happen, is for the cell background image and height to change on selection (as a means of marking that the story has been read).
Now, prior to setting up dequeueing, I was able to do this with a simple [self.tableView reloadData]. That seems to be a no-go with dequeued cells; reloading the table view does not redraw the cells to match their changed state.
I've tried reloadRowsAtIndex- and while this works - beautifully - for the first cell a user clicks on, it goes wonky after that point: sometimes the cell reloads correctly, sometimes not.
Obviously, each story is an NSMutableDictionary object. I'm using an NSNumber object to track whether or not a story has been read.
I would post the code, and I will if anyone asks, but I'm looking for a generic solution that could be implemented in any UITableViewController (share the love).
So, simply put: how does one reliably redraw complex cells on selection?
Try giving each cell a unique ID in order for dequeuing to work, your cells should be coming back with their changed states if you use a unique id for each cell, so 20 cells = 20 ids, hope this helps
Assuming you have the index path, you can access the cell and manipulate it directly:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
// handle the selection...
UITableViewCell *cell = [tableView cellForRowAtIndexPath: indexPath];
if (nil != cell) {
//
// now update the cell to reflect the new state
//
}
}