Table cells not deleting properly from tableView swift - ios

I am using a function other than apple's provided methods (canEditRowAtIndexPath and commitEditingStyle) to delete cells from a tableView. This is my code:
func deleteItem() {
items.removeAtIndex(currentIndexPath!.row)
tableView.deleteRowsAtIndexPaths([currentIndexPath!], withRowAnimation: .Automatic)
}
it does everything which generally occurs when deleting the rows. However, when new rows are inserted into the tableView, they layer the data from one of the previously deleted cells on top of the new ones, in this way:
The items can be added easily,
They can be deleted easily as well,
but when you try to add more cells, it has a problem:
At the moment, my best guess is that it has a problem with deletion of cells. any advice would be greatly appreciated.

At the moment, my best guess is that it has a problem with deletion of cells. any advice would be greatly appreciated.
If you can confirm for sure that your delete method is being called, by either using the debugger or a print statement, then I would say that your cells contain stale data from being dequeued, this would align with the laying you are seeing.
How do we confirm this?
Check your logic for your 'Add New Item' functionality.
Check your data source and make sure that it contains the correct data and number of items, you could check this in your custom delete method.
Set a breakpoint in the UITableViewDatasourceDelegate method below and inspect your cell's properties or use print statements to investigate. I would suggest cell.titleLabel!.text either way since that is the data you are seeing repeated.
tableView(_ tableView: UITableView,
cellForRowAtIndexPath indexPath: NSIndexPath)
Try reloading the data immediately after a delete with the reloadData() method for UITableView.
See the UITableView reference document.
Discussion 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.
If you registered a class for the specified identifier and a new cell
must be created, this method initializes the cell by calling its
initWithStyle:reuseIdentifier: method. For nib-based cells, this
method loads the cell object from the provided nib file. If an
existing cell was available for reuse, this method calls the cell’s
prepareForReuse method instead.

I just had this problem. You can't just have
self.tableView.deleteRows(at: [indexPath], with: .fade)
You have to remove it from your array too like this:
myArray.remove(at: indexPath.row)

Related

App crash while remove object from TableView

I have multiple data in my UITableView and when I delete one record.
Don't use didEndDisplayingCell to manipulate the cell or the data source array by the passed index path.
It's just a notification that the specified cell was removed from the table
The documentation also states:
Use this method to detect when a cell is removed from a table view, as opposed to monitoring the view itself to see when it appears or disappears.
The solution is to move the entire code in this method to the location where the cell / data source item is removed and use it before removing anything
The app is crashing because even though you have deleted the object( so now array will have one less element) but you have not told the table view to reset itself according to new datasource. Even cellforRowAt will crash if you delete the last element. One solution is to reload the tableview by calling:
<tableViewName>.reloadData()
but it will produce unnecessary flicker if tableViews are quite large and complex. And the Second thing, You should'nt be using didEndDisplayingCell

Should I use dequeReusableCellWithIdentifier?

I know this question is dumb. But suddenly got stuck up with this question.
When I use dequeReusableCellWithIdentifier, the cells are reused. To be more specific, first 'n' set of cells is reused - along with their references
As the same references are reused, I can't actually store local variables to the cells. I actually need to assign them everytime in cellForRowAtIndexPath
Assume I'm using a custom complex UITableviewcell. (I know we should reduce complexity. But still...)
Some views are to be added to the cell.
Best example i could think of is an image slider.
So the number of images in the image slider differs based on the datasource. So i need to add the views in cellForRowAtIndexPath. Can't avoid it.
Since the same reference of cells are reused, I need to add these views everytime cellForRowAtIndexPath is called. I think that is a bit of heavy load.
I thought of using drawRect method, but it does not work with UITableViewAutomaticDimension
If I don't use dequeReusableCell, I will have individual referenced cells, but it will cost the memory and performance.
So what is the best solution?
Can I use dequeReusableCell and still need not rewrite its content?
Edit 1:
By mentioning dequeReusableCell, I did mention dequeueReusableCellWithIdentifier - forIndexPath. I'm sorry for the confusion.
Edit 2:
I feel, I'm not so clear in my question.
Lets consider I have a array in my viewcontroller.
I'm configuring my cell in cellForRowAtIndexPath with dequeueReusableCellWithIdentifier - forIndexPath
So what happens is, everytime i scroll, when the invisible rows become visible, cellForRowAtIndexPath is called.
Lets say I have a image slider with n imageviews. For each cell, this 'n' differs. So I'm forced to draw the cell's view based on its dataSource.
So everytime the tableView calls cellForRowAtIndexPath, the cell is configured again and again.
I want to avoid this to improve performance.
what I do in this case is the following:
I always use dequeReusableCell:, for reasons you already said
I write a cleanCell method in the custom UITableViewCell class in order to clean everything has been already set on the cell (just to make it ready for reuse)
then in the cellForRowAtIndexPath: I configure my cell as desired
That's what I would do: I would move the logic of adding those heavy views inside the cell's class itself. So that in cellForRowAtIndexPath I would just set an object that has to be displayed inside the cell, and cell would create all the necessary views on its own (it's a view layer, after all). So inside cellForRowAtIndexPath it would look something like this:
cell = [tableView dequeueReusableCellWithIdentifier...forIndexPath...];
cell.modelObject = myModelObject;
and that's it.
Then inside the cell class I would implement optimizations for switching between different model objects. Like, you could defer releasing the heavy views, and reuse them, when the cell is reused.
To be honest, I don't get your example with the slider, but let's suppose you have a star rating with 5 stars, which can be visible or not. There are various way to do it, but let's assume you're just adding / removing a UIImageView for each star. The way you do it now, you would have an empty cell and create / add views in cellForRow. What I'm suggesting is to have 5 UIImageViews as part of your cell, and inside the cell set their visibility based on the modelObject.rating. Something like that. I hope, it helps to illustrate my point :)
UPDATE: when your cell can have an arbitary number of images inside of it, I would probably create them inside the cell's class. So, for instance, for the first model object we need 3 image views. So we create them. But then we don't release them in prepareForReuse, we wait for the next model object to come. And if it has, say, 1 image, we release 2 image views (or we don't, so that we didn't have to recreate them later, it depends on what is more critical: performance, or memory usage), and if it needs 5, we create two more. And if it needs 3, we're all set already.
In your case you should use dequeueReusableCellWithIdentifier(_ identifier: String, forIndexPath indexPath: NSIndexPath) method. This method automatically decides whether the cell needs to be created or dequeued.
I need to add these views everytime cellForRowAtIndexPath is called. I think that is a bit of heavy load.
You don't need to add those views every time cellForRowAtIndexPath gets called. Those views should be already added as a cell is instantiated. In cellForRowAtIndexPath you only assign images and other values to those views.
In case if you use storyboards and prototype cells your code should look like this:
var items = [UIImage]()
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("ImageCell", forIndexPath: indexPath)
let item = items[indexPath.row]
let recipient = item.recipients.first
// I have given my UIImageView tag = 101 to easily access it from the view controller.
// This allows me to avoid defining a custom UITableViewCell subclass for simple cells.
if let imageView = cell.viewWithTag(101) as? UIImageView {
imageView.image = item
}
return cell
}
1)I think you need to use "lazy load" of cells.
http://www.theappguruz.com/blog/ios-lazy-loading-images (for example)
2) You can create a property in your viewController, and create an NSMutableDictionary property, which will store the some data by the some key,
if your model have things like "ID" of the cells or you can use indexPath.row for it, then you can load it only one time, and next time when -cellForRow will be called, you can retrieve data from your dictionary by the
[self.dataDictionary objectForKey:#"cellID"];
And with this you, solve your repeating load.
In -cellForRow you can set the check,like.
if ([self.dataDictionary objectForKey:#"cellID"]) {
cell.cellImage = [self.dataDictionary objectForKey:#"cellID"];
} else {
UIImage *img = [uiimage load_image];
cell.cellImage = img;
[self.dataDictionary setObject:img forKey:#"cellID"];
}
And so on. Its like example, the dataType's you can choose by yourself, it's example.
Thanks for everyone helping me out. I think the better answer was from #Eiko.
When you use reusable cells, it must be completely reusable. If you are going to change its views based on the data, you either have to use a new identifier or avoid using reusable cells.
But avoiding reusable cells, will take a load. So you should either use different identifiers for similar cells or as #FreeNickName told - you can use an intermediate configuration and alter it on your need.

iOS: Adding row to tableview

I have a tableview that is based on a array of DB results, these results contains a date field. I have a custom cell that contains an image and labels. I'm trying to do:
At cellForRowAtIndexPath I verify if the date of current item (objectAtIndex:indexPath.row) has date field bigger than the last item (objectAtIndex:indexPath.row-1). If this is true: I want to add a cell filling the ImageView with a certain image, but I need to add a new row just for show this image.
How can I do this? I'm already doing this verification, but I need to add a new cell...
Do not use the cellForRowAtIndexPath to decide how many cells you want to have. At the point this method is called you should have already setup the data source to provide table view with all information needed.
Here is what you need to do. Refactor your code in a way so you:
Setup the data source first.
Force reload of the table view either by calling the reloadData method.
hey you can add the object in your data base(for example ns array) and refresh the table view with method
[tableView reloadData];
then the method cell for row at index path will be called again and it will refresh the table view's items.just make sure the method cellforrawantindexpath in your code knows to handle the new data type(make validations).
Your tableView data source should not contain any of that logic where the content of once cell depends on the content of another cell. Instead, you should have a data item for each requested indexPath and that data item should contain ALL logic necessary for the cell to be configured. If an action on that cell has an effect on how another cell should look, you apply a change to the corresponding data-item, and then call reloadRowsAtIndexPaths: for the indexPaths.
In short: configure cells ONLY in tableView:cellForRowAtIndexPath: or tableView:willDisplayCellAtIndexPath, and ONLY do configuring. Other logic should be placed in some data(-controller) object.
As suggested, you should add an item to your data-array. Then call -insertRowAtIndexPath: on the tableView. ReloadData is like a big ugly hammer that you only use when ALL of the data for that tableView changes.

Why UICollectionView.cellForItemAtIndexPath always calls UICollectionViewDatasource.cellForItemAtIndexPath?

Scenario: I want to check selected state of specific visible UICollectionViewCells with indexPath for which I am calling cellForItemAtIndexPath on the UICollectionView reference.
Problem: Calling UICollectionView.cellForItemAtIndexPath always calls UICollectionViewDatasource.cellForItemAtIndexPath which returns a new cell without the selection state.
Question: Why UICollectionView.cellForItemAtIndexPath always calls UICollectionViewDatasource.cellForItemAtIndexPath ?
Apple documentation says the return value is "The cell object at the corresponding index path or nil if the cell is not visible or indexPath is out of range."
Am I missing something or is my implementation of cellForItemAtIndexPath in datasource incorrect ?
- (UICollectionViewCell*) collectionView: collView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
SudoCell *cell = [collView dequeueReusableCellWithReuseIdentifier:CELL_ID forIndexPath:indexPath];
[cell setValuesWithSection:indexPath.section item:indexPath.item modelObject:_model];
cell.backgroundColor = [UIColor whiteColor];
return cell;
}
As a work around currently I am setting storing the section and item values as instance values of the cell. Looping through all visible cells to find matching cells with the section and item values and checking visible state. This becomes tedious when number of cells are huge.
Please advice.
You're misunderstanding how protocols and delegates work. cellForItemAtIndexPath: is a delegate method that UICollectionView and UITableView call on its datasource in order to populate the Collection or Table View.
So lets say you have a CollectionView and you gave it a datasource. At some point when you run your application that CollectionView is going to call a method on the datasource numberOfSectionsInCollectionView: in order to get how many sections are needed for the CollectionView
Then it calls collectionView:numberOfItemsInSection in order to get the items for each section for the collection. This method is called for each individual section defined in the Collection View.
Finally it calls collectionView:cellForItemAtIndexPath: in order to get the Cells for each item of the collection. This method is called for each individual item defined in the Collection View. This would be where you could programmatically configure the cell to display the information you want, for instance, if you wanted to give the collection a Cell that had an image attached to it, this is where you would do it.
https://developer.apple.com/library/ios/documentation/uikit/reference/UICollectionViewDataSource_protocol/Reference/Reference.html
These are all datasource methods with the sole responsibility of providing data for the Collection View. If you want to respond to user interactions you need to use UICollectionViewDelegate Protocol and implement the methods
collectionView:didSelectItemAtIndexPath:
collectionView:didDeselectItemAtIndexPath:
As the signature implies, these methods are called in response to actions performed on the collection, along with an IndexPath describing the section and item number of the cell the action as performed on.
https://developer.apple.com/library/ios/documentation/uikit/reference/UICollectionViewDelegate_protocol/Reference/Reference.html
UICollectionDelegate Protocol reference above you can use to respond to different events that occur in that view
But none of that information above is any use to you if you don't have a basic understanding of protocols and delegates. I would recommend spending time reinforcing that understanding first before continuing

Need UITableView's cellForRowAtIndexPath: to return nothing for some rows

In my code, some cells should not be shown to the user on the UITableView.
So my dataSource would supply objects, but I'd like to check in my cellForRowAtIndexPaht if the cell is hidden, and if it is, practically return nothing.
So that the cell's height would be zero, but more than that, I'd like to save my function the building of the whole cell.
I tried to return nil, but alas, it crashes.
The data source is supposed to return what is actually in the table.
You can easily make a new array to hold the data you want shown, and omit the data you do not want to see (keeping it in a master data source). Then as some rows in the table view are able to be seen, add them into the array of table data and use the insertRows method of UITableView to animate viewing the newly unveiled data.
Could you move the logic up into the numberOfSections or numberOfRowsInSection methods used to create your UITableView? Basically, whatever logic you are using to try and return null for those cells could move into the numberOfRowsInSection method in the UITableView class and return only the number of visible (not hidden) cells.
Then you would have to configure your cellForRowAtIndexPath method to hand out the cells from your data source in order by counting up through your visible rows instead of just pulling them out the array.
I wouldn't think you would want to return 'nothing' to a program expecting an object.
Link to Apple Reference Docs for UITableView, the numberOfSections and numberOfRowsInSections can easily be used to logically modify the amount of data being shown in a table view.

Resources