I am wondering what some of the differences are between invalidateLayout() and reloadData()
I have read the documentation here and here but am left with the following questions:
I have data as an array such that cellForItemAt indexPath can reference the array directly with dataArray[indexPath.item] and each data object in the array can populate the cell. reloadData() will ensure that when a change is made to the dataArray this is reflected in the collectionView. With some minor testing it appears that invalidateLayout() will also observe this change. Is this always true?
Both functions appear to have the same effect with regards to resizing the cells, is this always true?
When invalidateLayout() is called, will cellForItemAt indexPath also be called for each cell as with reloadData()?
If all the above are true, then would you ever even need to use reloadData() since Apple says to use it sparingly?
Any other comments on the differences between the two functions on a fundamental level would be helpful (when to use each one, etc)
After further testing...
cellForItemAt indexPath will be called only for cells which were not on screen but are now on screen as a result of the invalidateLayout().
This appears to be true
See answer to question 1. This could cause an issue if you have moved cells around and the data in each cell is not in the same order as the data source.
I am now using reloadData() a lot less
In general, it looks like reloadData() also calls invalidateLayout() (or at least has a similar effect) and will trigger cellForItemAt indexPath for every cell. invalidateLayout(), however, will only call cellForItemAt indexdPath if the cell is now within the screen bounds and needs to be reloaded as a result of the layout change.
Related
I made a UICollectionView, and everything is working. It makes 100 cells that I can scroll through in simulator with no problem.
However, rather than seeing all the cells at once, I want the cells to be released one by one whenever that red button is pressed.
I am confused because I noticed in the storyboard, it hard codes the number of cells it has on the screen at once. Is there any way to get around this?
Thank you!
This is what the UI looks like in storyboard.
This is the code I used to make it. It's basic, and just says to fill the text box of the cell with a string from the array.
Your question is garbled.
A collection view has a delegate and a data source. The data source responds to messages in the UICollectionViewDataSource protocol. That protocol lets the collection view ask how many sections it has, and how many rows in each section, as well as asking for the cells from those sections and rows.
There are also methods that let you tell the table view that you want to add more cells. Take a look at the method insertItems(at:). That lets you provide an array of indexPaths, which tells the table view that you have added new entries.
You could certainly write a button action method that added one or more entries to your data model and then used the insertItems(at:) method to notify the collection view that it had new entries. If there was room in the content view of the collection view to display additional cells it would then call the data source and ask for new cells at those index paths.
Sounds like you just need to keep track of how many items you want displayed (which will increase the more that button is pressed) and use that in your UICollectionViewDataSource method. Something like:
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return min(myRunningItemCount, maximumNumberOfItems) //assuming there's a maximum
}
Then you just need to call reloadData on the collection view whenever that number changes.
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)
Is there any reason that cellForItemAtIndexPath would not get called after numberOfItemsInSection gets called and returns a nonzero number? Would a reloadData from a completion block affect this? I'm running into an issue in which I am trying to delete a cell through a UIAlertController and the UI looks great, but when I go to reloadData after the UI animation finishes numberOfItemsInSection properly gets called with the right number of elements (the previous number of elements minus 1), yet cellForItemAtIndexPath does not get called, so my cells don't get reloaded. Any ideas as to why this would happen?
This is on Swift 1.2, XCode 6.4, and any help would be greatly appreciated
Just to give a bit more information, the method I'm using to delete a cell is looking a little like:
self.collectionView.performBatchUpdates({
let indexPathCellToDelete = self.indexPath(feedCell)
self.collectionView.deleteItemsAtIndexPaths([indexPathCellToDelete])
}, completion:nil)
Would having a second reload in the completion block change anything?
Turns out performBatchUpdates, according to UICollectionView Performing Updates using performBatchUpdates needs to be called after the change to my data source has been done. The issue was, though, that my method for deleting an item from my data source itself reloads the collection view, so there was an extra reload that did not need to happen, which was causing the issue, it seems. I initially had the call to my data source deletion method in the completion block of the performBatchUpdates, but moving that above and just reloading the entire collection view seems to have solved the issue. Seems like a weird race condition in my case where there are multiple reloads happening simultaneously and cellForItemAtIndexPath didn't like that.
I have a UICollectionView with several cells – there are always some of them outside the viewport, so there are cells being reused.
When an event occurs I want to take a specific cell and move it to the front of the UICollectionView. In order to make moveItemAtIndexPath() work I need to know the current indexPath of the Cell that I want to move.
When I initialize the Cells in the CollectionViewDelegate with the cellForItemAtIndexPath method I save its indexPath into a variable of the Object that is the model of the Cell (not it’s actual Object, just the model). Then I look at this variable when I want to move the cell.
This is working fine as long as the cell was visible once – it seems like it only initiated then, even though it is part of the CollectionViewData all the time.
How can I make sure my CollectionViewCell has an indexPath, even when it is not visible or has not been visible yet?
If you know what data (cell) you want to present at the front (I assume top) of your UICollectionView, why don't you just update your dataSource and reload your UICollectionView?
If I create a UICollectionViewCell subclass like:
class StudentCell: UICollectionViewCell {
var student: Student?
}
And in my controller I implement UICollectionView's didSelectItemAtIndexPath and set the variable:
func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
if let studentCell = collectionView.cellForItemAtIndexPath(indexPath) as? StudentCell {
studentCell.student = self.someStudent
}
}
When I click on the cell it should set student, but if the cell is scrolled off screen it seems like the whole cell might get wiped and when it comes back on screen it would need to rebuild itself in cellForItemAtIndexPath.
The problem I have is that I have a UICollectionView with cells that do not scroll off the screen that I'm storing data in. Because they don't leave the screen I assume they should not get wiped, but one of the cells does not seem to keep it's variable, which makes me think maybe it's getting wiped and I may need to move the state outside of the cells to a dictionary or something and in didSelectItemAtIndexPath instead of setting the variable in the cell I'd set the dictionary. Whenever I need to access the data instead of asking the cell for it I'd look it up in the dictionary.
But either way, I was wondering if it's possible (or a bad idea) to set it in the cell or not.
Yes, cells in both UICollectionView and UITableView can (will) be reused at the systems discretion and should not be used to store state information, only display information. Specifically, both views will reuse cells when they are scrolled off-screen, but there's no guarantee this is the only time they'll be reused. The usual way to handle this is to define some kind of cell data object which stores the data for each cell (visible and not) and refresh the cell view from that as needed/requested.
Tables display their data in cells. A cell is related to a row but it’s not exactly the same. A cell is a view that shows a row of data that happens to be visible at that moment. If your table can show 10 rows at a time on the screen, then it only has10 cells, even though there may be hundreds of rows with actual data. Whenever a row scrolls off the screen and becomes invisible, its cell will be re-used for a new row that scrolls into the screen.