Previous Cells remain after using collectionView.reloadData? - ios

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.

Related

ScrollToItemAtIndexPath for Infinite Collection View

I have an infinite collection view, and every time it appears, I'd like to display a specific indexPath. I attempted putting the scrollToItemAtIndexPath method in viewDidAppear, viewWillAppear, and viewDidLayoutSubviews to no avail.
On viewDidLayoutSubviews, the view shows up properly but displays the wrong index. It ends up being offset by 24 indexes. When I print the index, I see the proper index in the console. When I set the index in cellForItemAtIndexPath, it also displays the proper index, but all of the cells are just that index.
Here is the code I have in viewDidLayoutSubviews:
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
infiniteCollectionView.layoutIfNeeded()
self.infiniteCollectionView.scrollToItemAtIndexPath(self.initialIndexPath!, atScrollPosition: .CenteredHorizontally, animated: false)
}
This displays the cell 24 indexes before the one I actually want to be presented. I assume that I'm scrolling the collection view before all the cells are being loaded properly, therefore causing it to display the incorrect index.
How would I go about resolving this issue?

UITableViewCell flickers when adding new data and reloading

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!!

Configuring UICollectionView to keep the same cell view

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!

How to reload tableview after reordering a row? Rows have variable height

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.

UITableView didSelectRowAtIndexPath: not being called on first tap

I'm having an issue with UITableView's didSelectRowAtIndexPath.
My table is setup so that when I select row it initializes a new view controller and pushes it.
The first time I tap any row in the table, the method does not get called. Once I select another row, it begins to work as normal.
I have verified this by setting a breakpoint on didSelectRowAtIndexPath. When adding an NSLog to the method I see that when I select the second row that finally pushes the new view controller, I see two log statements appear in the console at the same time.
Any suggestions?
Any chance you accidentally typed didDeselectRowAtIndexPath?
Also check the selection property of your table view in xib file. Use 'Single Selection' or 'Multiple Selection' as required.
I experienced the following issue:
first tap in row -> no effect, no selection, never
second tap and following -> correct selection behavior, always
In my case, my error was checking Show Selection on Touch in Interface Builder. You can uncheck it in IB here:
Hope that helps someone
Check If you have set any Gesture recognisers in your class. Removing gesture worked for me.
I debated even posting this answer because I think the stars kind of aligned in order for this to manifest itself.
I am having a variation of this problem and have checked the other solutions. On my table view it isn't processing the very last row of my table on the first tap. It highlights it, but didSelectRowAtIndexPath isn't being called. All the other rows work fine. But if I turn the tableview bounce on then it seems to solve the problem (but then you have to deal with a tableview bounce).
UITableViewCellSelectionStyleNone was set for the cell displaying that problem
(ios9).
I have ended up calling
[tableView deselectRowAtIndexPath:indexPath animated:NO];
first thing in
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
which is not the same, but is good enough.
makes me wonder what kind of table breakage ios10 brings.
this issue happens also when you are working with gesture recogniser within a tableView in this case you don't need to remove them, you need only to make sure that your gesture property cancelsTouchesInView = false this is a boolean value affecting whether touches are delivered to a view when a gesture is recognised.
SWIFT 2
Make sure you have this set to true:
self.tableView.allowsSelection = true
Put this above your right after your viewDidLoad() and before the super.viewDidLoad()
SWIFT 3
If you are working with Swift 3 in a class which isn't a UITableViewController and you are using UITableViewDelegate, then you may need to use the method title:
#objc(tableView:didSelectRowAtIndexPath:) func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath){...}
This worked for me, although I have just migrated my project to Swift 3. It's fairly new so this may be fixed later.
SWIFT 3
None of the other answers worked in my case, what fixed it for me was:
tableView.delaysContentTouches = false
If you have set any UITapGestureRecognizer in your class, you can add this line in your didSelectRowAtIndexPath:
[tableView deselectRowAtIndexPath:indexPath animated:NO];
This worked for me.
Swift 4.2
Disabling "Delay Touch Down" in the attributes inspector solved the issue.
After that the clicks are smooth and didSelectRowAt fires immediately.
didSelectRowAt function was not called in my app in first or second tap...
I was trying to solve the problem could not find any solution. But suddenly I was recognise, I was using view.animation color change... Delegate method was not called while animation persist
In my case I had a UITableView section header with a gesture recognizer. When the header is tapped, it should insert few rows into that section and animate the insertion (like expand/collapse section).
First time when expanded and tapped, the didSelectRow delegate method was not fired. For further taps and expand/collapse actions, it was working as expected.
Per answer by #Ayoub Nouri I set cancelsTouchesInView to false, and this resolved the issue.
tapGestureRecognizer?.cancelsTouchesInView = false
What worked for me was to start typing "didSelect..." and then let autocorrect fill in the rest. Apparently some detail of my syntax was wonky. As others have said, Use the IDE!!
I had the same issue on my tableView(swift 4.2), it could be fixed with allowsMultipleSelection property on tableView.
If allowsMultipleSelection is set to true the table view selection mechanism will change in a way that by selecting each cell the tableView didSelectRowAtIndexPath: is called for the first time and by selecting the same cell for the second time the tableView didDeselectRowAtIndexPath: is called.
It means that if the number of times a cell tapped are odd (1, 3, 5, ...) then always tableView didSelectRowAtIndexPath: will be called and if the number of times a cell tapped are even (2, 4, 6, ...) then always tableView didDeselectRowAtIndexPath: will be called.
This makes tableView didSelectRowAtIndexPath: function to be called on the third selection for the same cell and so the result is double tap for calling didSelectRowAtIndexPath:!!!
If you want the tableView didSelectRowAtIndexPath: to be called on each selection for a cell then the tableView multiple selection has to be set false, tableView.allowsMultipleSelection = false.
By doing this, every time the cell is tapped tableView didSelectRowAtIndexPath: will be called on table view and by selecting another cell tableView didDeselectRowAtIndexPath: will be called for the cell was selected before and then the tableView didSelectRowAtIndexPath: will be called for the newly selected cell.
class TableViewController: UITableViewController {
override func viewDidLoad() {
super.viewDidLoad()
tableView.allowsMultipleSelection = false
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
print("This will be called for each cell tap")
}

Resources