Make custom dynamic cells NOT reusable in UITableView - ios

I'm using a custom UITableViewCell subclass, called MessageCell.
I initialise the cell like so:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
MessageCell *cell = [tableView dequeueReusableCellWithIdentifier:#"messageCell"];
// more code
return cell;
}
In each custom cell I have a checkbox used to mark this cell. When a cell is marked it is being added to an array, and vice versa.
While this works in terms of data, the UI is not reflecting it as it should. What happens is that in addition to the marked cell, other cells are being marked.
I assume this behaviour stems from cell reusability, i.e when I mark a cell as selected it has a common pointer with all future cells in this screen position.
For this reason, I want to use "normal" non-reusable cells.
So I tried:
MessageCell *cell = [[MessageCell alloc] init];
OR:
MessageCell *cell = [[MessageCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:nil];
But both of them resulted in displaying empty cells.

You still want to reuse cells! Just check your data and set the checkbox to checked or unchecked in your cellForRowAtIndexPath:
MessageCell *cell = [tableView dequeueReusableCellWithIdentifier: #"messageCell"];
if (condition cell should be checked) //set the cells checkbox to checked
else //set the cells checkbox to unchecked
Or some approach like that.
PS on re-reading your question:
do you really need a reference to a cell in your array? Can't you for example store the indexPath and use cellForRowAtIndexPath: if you need to make a cell out of it?

Related

Duplicate rows on uitableview when scrolling with custom cell

Here is my code :
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSString *cellIdentifier=#"menucell";
MenuCell *cell = (MenuCell*) [tableView dequeueReusableCellWithIdentifier:cellIdentifier forIndexPath:indexPath];
if (cell==nil)
cell= [[MenuCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier] ;
MenuModel *resturntent = (self.menuList)[indexPath.row];
cell.foodNameLbl.text = resturntent.MenuName;
cell.priceLbl.text = [NSString stringWithFormat:#"%# %#",#"$" , resturntent.MenuRate];
cell.foodImage.image = [UIImage imageNamed:#"full_breakfast.jpg"];
return cell;
}
While scrolling uitextfields values gets duplicates .
I need help to fix this issues
When you use dequeueReusableCellWithIdentifier, your cells will be reused. This means when the user scrolls the tableview, a cell which moves out of the screen will be reused to display the contents of a cell which is about to move onto the screen.
Even though this helps in saving memory, the problem it causes is the that the cell needs to be prepared for display before it is loaded with the content of the new cell.
In your case, it seems you need to maintain the values the user has entered in a textfield of a cell.
So to fix your problem, if there are not that many cells in the tableview, simply stop reusing the cell. Else whenever the user enters a value in the textfield of a cell, save it in an array (array of 'MenuModel' in your case). And fetch the value from from the model and set it to your textfield in the cellForRowAtIndexPath method like your have set values for other labels.

iOS get current and previous selected UITableViewCells from cellForRowAtIndexPath method

So basically what I am doing now is expanding my cells by selecting on them and using heightForRowAtIndexPath to change their height and if I select on it again or select a different cell those cells will expand or go back to their normal size.
However, upon the cell expanding I have some extra data to show in the expanded section, change the background color of the cell and set some other properties that I have defined in my tableviewcell subclass. So as an example when the cell is in its normal height the background will be light green. When its expanded it needs to be white. I set my tableviewcell subclass property (a BOOL) so that when its time to loop through the cells again (cellforRowatindexpath) i can update these properties as needed. Unfortunately, I haven't been able to find a way to get the current cell thats been selected in cellForRowAtIndexPath.
Here is the relevant code below. Keep in mind that I want to keep track of the current cell selected and the previous cell (if different from the current one) so that i can update both cells properties. Only one cell can be expanded at a time. When the current cell is selected and expanded the previous one(if applicable) will contract back to normal height. My configureCell method is just there to assign the properties based its BOOL property.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
MyCustomCell *cell = [tableView dequeueReusableCellWithIdentifier:myIdentifier forIndexPath:indexPath];
[cell configureCell:self.item[indexPath.row] isCollapsed:cell.isCollapsed];
return cell;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
MyCustomCell *currentCell;
MyCustomCell *previousCell;
self.currentSelectedIndex = indexPath;
//assign previous and current if previous is nil
if(!self.previousSelectedIndex){
self.previousSelectedIndex = self.currentSelectedIndex;
currentCell = [tableView cellForRowAtIndexPath:self.currentSelectedIndex];
currentCell.isCollapsed = NO;
}
//we have tapped the same cell as before
else if(self.previousSelectedIndex == self.currentSelectedIndex){
previousCell = [tableView cellForRowAtIndexPath:self.previousSelectedIndex];
previousCell.isCollapsed = YES;
self.previousSelectedIndex = self.currentSelectedIndex = nil;
}
//if they aren't equal, then collapse the previous selected cell
//and expand the current selected cell
else{
previousCell = [tableView cellForRowAtIndexPath:self.previousSelectedIndex];
previousCell.isCollapsed = YES;
currentCell = [tableView cellForRowAtIndexPath:self.currentSelectedIndex];
currentCell.isCollapsed = NO;
self.previousSelectedIndex = self.currentSelectedIndex;
}
[tableView beginUpdates];
if(self.currentSelectedIndex){
[tableView reloadRowsAtIndexPaths:#[self.currentSelectedIndex, self.previousSelectedIndex] withRowAnimation:UITableViewRowAnimationAutomatic];
}
[tableView endUpdates];
}
So, obviously my current and previous cell will be trashed once we leave this method since they are local but I am struggling with how to:
a. reload the cells which would cause cellForRowAtIndexPath to execute again(this works when trying to use reloadRows - but maybe I'm doing that wrong)
b.once cellForRowAtIndex starts going through the cells how to capture the currentCell and the previousCell so that i can update its content as I described above. [dequeueReusableCellWithIdentifier:myIdentifier] just gets a new cell which I do not want obviously.
The cells expand and contract fine so thats not an issue.

What is the purpose of an Identifier in UITableView

I'm a little confused to why an identifer (*MyIdentifier) is always required. The code below demonstrates this. I have noticed all tableviews require at least one.
- (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] autorelease];
}
What is the purpose behind having an identifier? Ive have seen a few tutorials where there is more than one. Also, reading the Apple documentation, I was a little confused about why the following is called:
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:MyIdentifier] autorelease];
}
I would like to know why it takes the input of 'MyIdentifier'? Assuming we had more than one identifier, which one should we choose? To be exact, what if we had a Parent, Child and sub-child etc.
Suppose you have several customized UITableViewCells and each of them have different backgound colors. In your storyboard or xib file, you may name the cell with red background color "redCell" and the one with blue "blueCell". Then you can select what kind of cell to add to a particular row using their identifiers. Let's say, you wanna apply red cells to odd rows and blue ones to even rows then you can use the following code to do this:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *redCell = [tableView dequeueReusableCellWithIdentifier:#"redCell"];
UITableViewCell *blueCell = [tableView dequeueReusableCellWithIdentifier:#"blueCell"];
if (indexPath.row % 2 == 0) return redCell;
else return blueCell;
}
Without specifying an identifier, the system wouldn't know which kind of cell to pick.
From the docs:
The identifier is a string identifying the cell object to be reused. This parameter must not be nil.
For performance reasons, a table view’s data source should generally reuse UITableViewCell objects when it assigns cells to rows in its tableView:cellForRowAtIndexPath: method. A table view maintains a queue or list of UITableViewCell objects that the data source has marked for reuse. Call this method from your data source object when asked to provide a new cell for the table view. This method dequeues an existing cell if one is available or creates a new one using the class or nib file you previously registered. If no cell is available for reuse and you did not register a class or nib file, this method returns nil.
Every time you return a cell you have 2 options - you can create new cell and return it, dequeue cell that already exists and reconfigure it.
So the way this works is when you create cell you give it reuse identifier, so when cell goes off screen it can be used instead of creating new cell. After the cell is dequeued though, you might want to change its properties(like text or image)
You can have different reuse identifiers for different cell types(different content views)

UITableViewCells reverting back to default or other values

So I've created a TableView as below, but I have quite an annoying problem.
When I come to this VC, I click Choose User, and I selected user Atoshum.
When I scroll down, this top cell goes off screen as I scroll through the bottom cells.
When I scroll back up, the cell has reverted to a default (or occasionally, takes the value of another cell).
I make the cells as such.
static NSString *CellIdentifier = #"Cell";
DMSDrugInstanceCell *Cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (!Cell) {
Cell = [[DMSDrugInstanceCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
Cell.chooseUserButton.tag = indexPath.row;
[Cell.chooseUserButton addTarget:self
action:#selector(performSegue:)
forControlEvents:UIControlEventTouchUpInside];
return Cell;
In the cell creation you dont ever set any values. All you do is set the tag and then add an event target. If you want it to keep the choose, you need to store/save that choice when it is made and then in the cell create, set it based on that saved value.
you are reusing table cell. So every time your cell reload then you need to set value in cell.
in cellForRowAtIndexPath method , you need to set value in cell according to index path.
This is because the UITableView do not create the new cells for total number of elements. Rather it re-uses the cells which are off the visibility. Hence you feel that your data is reset or getting reflected on some other cell.
The best approach is to store all your data in some Array (let it be tagDataArray) and then set your cells as
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"Cell";
DMSDrugInstanceCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[DMSDrugInstanceCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
Cell.chooseUserButton.tag = [tagDataArray objectAtIndex:indexPath.row];
...
...
return Cell;
}

Creating a reusable UIView similar to UITableViewCell

Ahoy!
I'm trying to create a reusable UIView (for various reasons) similar to the UITableViewCell implementation used in UITableViewController. I'd like to use the reusable view in a UIScrollView so I know i'm not trying to achieve something that's entirely unattainable.
The default implementation of this is:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
//declare cell identifier
static NSString *cellIdentifier = #"cell_identifier";
//dequeue cell
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
//check cell is valid
if(cell == nil)
{
//create a new cell
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:cellIdentifier];
}
//
//return cell
return cell;
}
From this, it's worth noting that the cell is dequeued from the UITableView. If the cell is invalid, a new cell is created. My question is, how does this cell then become "queued" for reuse later?
My current attempted implementation looks like this:
- (TestScrollViewCell *)scrollView:(TestScrollView *)_scrollView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
//declare cell identifier
static NSString *cellIdentifier = #"cell_identifier";
//dequeue cell
TestScrollViewCell *cell = (TestScrollViewCell *)[scrollView dequeueReusableCellWithIdentifier:cellIdentifier];
//check cell is valid
if(cell == nil)
{
//create a new cell
cell = [[TestScrollViewCell alloc] initWithFrame:CGRectZero];
}
//
//return cell
return cell;
}
I'm thinking that adding a NSMutableDictionary to my TestScrollView to store the cellIdentifier and the TestScrollViewCell (UIView) and then plucking them back out based on the dictionary key would be a good start but is this really a true implementation of "reusable" cells?
The issue I can see is that I would then be adding the UIView to the ScrollView which is positioned based on the frame. Dequeing a view in this sense wouldn't allow me to then add the view to the scroll view without affecting the first view (by modifying the frame) but surely this is how UITableViewCells work, as well as section headers/footers?
I've been looking at this implementation which seems to be following the same route I was intending on implementing but i'm not 100% sold that this is a true implementation of reusable cells.
Has anyone had any luck with this previously? I'm trying to take Apple's lead on this one but other than UITableViewCell and MKAnnotationView (MapKit) there aren't any accessible implementations of this for me to glean from.
Any help would be greatly appreciated.
It's not just the view, it's the whole UITableViewController you'll need to recreate. The reuse flow goes like this:
dequeueReusableCell gets empty reused cell from some storage, I guess, from NSMutableArray (grab first object from array, then delete it from array and return it). If array is empty, method returns nil. You check for cell value, if it's nil, you create a new instance of cell class. If it's not nil, you fill it with your data.
This goes for every visible cell, that is, every cell that can fit on screen. Any non-visible cells are not initialized. When user scrolls the table, cell that are gone completely off-screen (not a single pixel visible) sent to reuseQueue – all their subviews and values return to default values or just nilled, and then cell gets added to the end of our NSMutableArray that is the queue.
I hope I explained well enough.
EDIT: Oh, and one more thing - you'll need different reuse queues for each reuse identifier.

Resources