I have a tableview that has fade in animation for each cell when it gets displayed. When scrolling down, my more data gets appended to my datasource and reloads my tableview. When that happens, all my tableview cells flickers(cell that is already done animating the fade in animation does the animation again). Is there a way to prevent my cells from animating again when I reload my tableview?
//reloading datasouce
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
//configure cell
//check to see if reloading datasource is required
self.dataSource += additionalDataArray
self.tableView.reloadData()
return cell
}
//animating cell
override func tableView(tableView: UITableView, willDisplayCell cell: UITableViewCell, forRowAtIndexPath indexPath: NSIndexPath) {
let view = cell.contentView
view.layer.opacity = 0.1
UIView.animateWithDuration(0.5, delay: 0, options: [.AllowUserInteraction, .CurveLinear], animations: { () -> Void in
view.layer.opacity = 1
}, completion: nil)
}
Never call reloadData() inside cellForRowAtIndexPath. This method is called just before every row is displayed for every single row. When reload data is called, each row is reloaded, and cellForRowAtIndexPath is called over again for each of the displayed rows. Calling reloadData() here is causing a cycle reloading each row over and over again. A symptom of this is the flickering you are seeing. Don't call reload data in either of those delegate methods. Another set of methods needs to monitor the data source and determine when to update or not. Only update when you have updated the data.
I can see you are trying to update the data when it gets to the bottom. I don't know what problem you are trying to overcome with this solution but keep two things in mind. One is dequeueReusableCellWithIdentifier solves memory problems with tables that have large amounts of data. If you have a table that has 100 rows however only 3 are visible at a time, the tableview only allocates memory for 3 rows and reuses them over and over. Thats why cellForRowAtIndexPath is called over and over again even for rows that it already created. Its reusing a row that went off screen and laying it out again with new data. Second is Swift uses pointers to reduce allocating memory the objects that already exist. If you have all 100 data objects allocated in an array somewhere then handing all 100 to a separate array that the view controller owns won't double the memory being used. It's ok to do that.
In the case where you don't have the next 5 avaialble because you need to make another network call then you will need a method in willDisplayCell to determine when you are nearing the bottom. You will need a loading cell to indicate to the user to wait while more rows are being fetched. When you get near the loading cell the check inside willDisplayCell tell the data source to get more data. When the data is returned you reload the table. Never reload inside of cellForRowAtIndexPath. Good luck!!
Related
I have a long list of textfields so I am using a tableView.
this is how the screen looks like
When I insert some text in a textfield in one of the cells and scroll down some other cells get the same textfield value. I have no idea why this would happen.
This is my code now:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cellId", for: indexPath) as! RelatieToevoegenCell
cell.label.text = items[indexPath.row]
cell.textfield.placeholder = items[indexPath.row]
return cell
}
Your main problem is that you are keeping your data inside Views (UITableVieCell).
Cells are reused by UITableView to optimize performance - so even if you have 1milion rows in your table, only few UITableViewCells are in memory (as many as are visible on the screen usually).
When you are calling dequeueReusableCell it takes one of already existing cells and reuse it.
To solve this problem you need to keep your data separately in an Array and keep modified texts there.
Then you need to modify code you posted, to take data every single time you configure UITableView from your "dataSource".
Also a good pratcice is to reset UITableViewCell when it's reused, you can do this by adding coding inside prepareForReuse - but that's optional, as text will be set from your data source anyways.
I would also recommend to read this before you start coding further - it will allow to understand how UITableView works on iOS:
https://developer.apple.com/library/content/documentation/UserExperience/Conceptual/TableView_iPhone/TableViewCells/TableViewCells.html#//apple_ref/doc/uid/TP40007451-CH7-SW1
Basically, you have to get and store the values in view-controller because due to the reusable behavior of UITableViewCell you lost the reference of the invisible cell with all the child reference.
So you can store the text-field value by the textViewDidChange action in RelatieToevoegenCell class.
I have all of my view stylings in the method
override func viewWillAppear(_ animated: Bool) {
displayDataForIndexPath()
}
Which is called when I need to reload data in my ViewController. My ViewController has a CollectionView that I need to refresh.
I've added collectionview.reloadData() to my displayDataForIndexPath() method. However, all the previous cells remain, instead of disappearing, so the new cells stack on top of the old ones. What is the proper method to remove those old cells so the CollectionView reloads properly?
you dont need to delete your datasource
in your cellForItemAtIndexPath you should not add more controls to the cell. it should be only done one time on creation of the cell.
as the name states when you normaly get your cell: reuse and that means it reuses from the last state it displayed in the collection.
I have a collection view delegate
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath)
-> UICollectionViewCell {
}
I wanted the property of the cell to persist between each call to this method.
let cell = levelView.dequeueReusableCellWithReuseIdentifier(reuseIdentifier,
forIndexPath: indexPath)
But retrieving cell using dequeueReusableCellWithReuseIdentifier seems to create a new cell view element that is different from the initial one.
cell.accessibilityIncrement()
So the cell accessibility value cannot persist after tapping on the cell.
I need to do this because I wanted to do UI testing by tapping on the cell.
Any help is appreciated!
Reusable cells in iOS are handled with a queue. This helps the tableViews and collectionViews move smoothly and remain efficient with a large amount of data. I don't think you would want to change the way that is designed to work in your application. This way, the device only needs to load as many cells into memory as can be displayed on a single screen. As cells move off the screen, they can be reused as another cell as cellForItemAtIndexPath will be called to load the required data into the reused cell.
If the goal is to have a value persisted between cell reloads, consider using a map or some other similar variable that could be managed by the collectionView's dataSource delegate.
For example, in your viewController that contains your dataSource delegate, you could have a Dictionary defined like so:
let numberOfTapsByIndexPath = [NSIndexPath: Int]()
Then every time a cell is tapped, you would increment the value in the map for the cell tapped. That code might look something like this:
let previousTaps = numberOfTapsByIndexPath[indexPath]
numberOfTapsByIndexPath[indexPath] = previousTaps + 1
I'm not 100% sure that was the goal you explained above, but regardless, you'll want to move any persistent information out of the cell and into a variable that the dataSource delegate or some other singleton can manage.
Hope this helps!
By default the height of my cell is set to 140.
But if expanded, it should be set to 265.
This is what I have:
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat
{
if(expanded){
return 265.0
}else{
return 140.0
}
}
The problem is, I need to scroll down, and scroll back up for the cell to change height. How do I fix this?
Secondary question (more interested in the above question, just if anyone happens to know)
Is it possible to have the cell animate from height 140 to 165?
Thanks
You need to reload the table view after you change anything in it like so:
tableView.reloadData()
When you scroll away from the cell, if it goes off the screen it is unloaded. When you scroll back up, it reloads it. You are basically just reloading the data as scrolling would do.
This code should work here:
tableView.reloadData()
It will reload the table everytime you changes something.
Source
I let user to reorder rows in tableView. Because this event affects content - some numeric values in cells should be updated - in all the other rows, I call a reloadData in moveRowAtIndexPath. And then strange effects occur.
I.e. cells seems overlapping when touching dragger, and some cell starts to move up and down. Its important to know, that cells height are varying.
The strange, that if I remove reloadData from moveRowAtIndexPath, then all these phenomenons disappear. Only the content is invalid.
So how should I reload data after reordering?
UPDATE: What I have done meantime reconfiguring cells in viewDidLayoutSubviews instead of call reloadData end of the moveRowAtIndexPath. And it works 90% like I expect, but still rows are sometimes somewhat higher they should.
override func tableView(tableView: UITableView, moveRowAtIndexPath sourceIndexPath: NSIndexPath, toIndexPath destinationIndexPath: NSIndexPath) {
//..
reorderOccured = true
}
override func viewDidLayoutSubviews() {
if reorderOccured {
for cell in tableView.visibleCells() as! [UITableViewCell] {
let ip = tableView.indexPathForCell(cell)
if ip != nil {
self.configureCell(cell, indexPath: ip!)
}
}
reorderOccured = false
}
}
You shouldn't call reloadData after reorder.
You have to make the same changes to your data, as the changes that has been made on the screen.
For example: if you moved cell nr 2 to position 6, you have to remove your object that populate cell nr.2 and insert it again at position 6.
You didn't provide enough details, but usually you would keep your data in an array of objects. This array you have to make the changes to, so your backing datasource is valid.
Here's a link to details from Apple.
I just read the updated after I posted my answer. It seems like you really need to reloadData. In this case I advise to reload after a small delay with a dispatch_async block on the main thread. Say after 0.1.
I found here the answer: https://stackoverflow.com/a/4692563/239219
[tableView beginUpdates];
[tableView endUpdates];
This code forces UITableView to reload cell sizes only, but not cell contents.
I can't yet comment on pteofil's answer, but he is correct: if you have a numbered set of rows in a table, and you move one calling moveRow(...), your animation for the move will be cancelled by a tableView.reloadData().
As such I delayed the data reload (which renumbers all the visible cells based on the updated data source (don't forget to do that when you move stuff around!)) a couple hundred milliseconds and it works perfectly now and looks great too.