UITableView scrollToRowAtIndexPath scrolls to wrong offset with estimatedRowHeight on iOS 7 - iphone

I am using the estimatedRowHeight method from UITableView in iOS 7, which works perfectly for fast loading of a UITableView with 5000 rows of variable heights.
My UITableView consists of 50 sections. Every section has 100 rows, of variable height.
At the start I use estimatedRowHeight for fast-loading, but after that when I call scrollToRowAtIndexPath, my UITableView scrolls to the wrong offset. I can understand why this is, because it has an estimatedRowHeight until I scroll the entire table and the proper cell heights are set in the heightForRowAtIndexPath delegate method.
Any solution?

Unfortunately this issue is to be expected when using estimatedRowHeight in your code with such a low value. When you scrollToRowAtIndexPath it does not actively calculating the correct size as you go. The underlying cause is, if you scrolled from Section 1 to Section 2, it could feasibly calculate the correct position on the fly and the estimatedRowHeight of the cells if it was a relatively new device. Any older devices would be brought to their knees, and even any new ones would be as well if you had to process 5000 cells, for example.
A maybe-solution to your problem could be to increase the estimatedRowHeight constant so the device doesn't have to do as much work.

When scrollToRowAtIndexPath is called, height of all the cells b/w current position and target offset are not calculated. Only some of them are.
Instead, UITableView uses estimatedRowHeight to calculate target offset which leads to wrong offset.
I'm facing the same issue and I found a small trick(which I don't really like) to calculate exact cell height only once after initial relaodData. I inserted two lines below:
tableView.reloadData()
// only for the initial reloadData
let numSections = tableView.numberOfSections
let numRowsInLastSection = tableView.numberOfRowsInSection(numSections-1)
// scrolls to the last row in the tableView
tableView.scrollToRowAtIndexPath(NSIndexPath(forRow: numRows-1, inSection: numSections-1), atScrollPosition: .Bottom, animated: false)
// back again to the original position(which is 0 for this is only called right after the initial reloadData)
tableView.scrollToRowAtIndexPath(NSIndexPath(forRow: 0, inSection: 0), atScrollPosition: .Top, animated: false)
This results in the tableView appearing on the screen late, but I think it's acceptable rather than freezing UI after the tableView appeared. It's even better when you call an API on the beginning because it feels like a little delay on the network, not the UI lag.
After this, tableView jumps to exact position of the cells.
Yeah, I don't really like this solution either :|
EDIT:
The only reason I did this was to calculate actual row height by calling cellForRowAtIndexPath:, but I found out when giving proper estimatedHeight for every row by delegate method estimatedHeightForRowAtIndexPath: instead of giving static value to UITableView.estimatedRowHeight solves the issue.
What I finally did was to cache the heights of rows from willDisplayCell:forRowAtIndexPath: to disk and use that value on estimatedHeightForRowAtIndexPath:.
By doing this, estimatedHeightForRowAtIndexPath: for all rows are called in the beginning and scrollToRowAtIndexPath works well.

It's not the prettiest solution but I use a workaround for this issue who do the trick. Always update the estimatedRowHeight to the tallest computed cell and then use something like this :
Scroll the content of the tableView to offset zero.
Finish by calling scrollToRowAtIndexPath to index path zero.
[self.tableView setContentOffset:CGPointZero animated:YES];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[self.tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0]
atScrollPosition:UITableViewScrollPositionTop
animated:YES];
});
As result the tableView should scroll to the top quite smoothly.

The solution for me was to set the estimatedRowHeight to 0

This solved my problem
let oldContentSize = tableView.contentSize
tableView.scrollToRow(at: indexPath, at: .top, animated: true)
// Called again when tableView contentSize change
if !tableView.contentSize.equalTo(oldContentSize) {
tableView.scrollToRow(at: indexPath, at: .top, animated: true)
}

Still no solid solution but for me it was acceptable to perform a scroll without animation which worked well in the sense that it scrolls to the expected cell with the correct offset, despite of what is mentioned in http://www.openradar.me/20829131.
self.tableView.scrollToRowAtIndexPath(indexPath, atScrollPosition:.Top, animated: false)

It seems to work after the view was layouted by the system. I'm calling scrollToRowAtIndexPath with correct results in the VC's viewDidAppear. Which is not very beautiful of course, because it scrolls when the view is already visible...

try to add this code in -cellForRowAtIndexPath: right before return the cell
[cell layoutIfNeeded]

Mine was wrong because the "atScrollPosition" was set to UITableViewScrollPositionNone - change it to UITableViewScrollPositionMiddle and it should work fine

Related

Pushing a view controller after scrolling is complete

When a user adds an item to my list, I want to scroll to the new row, highlight it, and select it (which will push a new controller). The key part is waiting for the scroll animation to complete before pushing the new controller.
In this answer, I learned how to use the animation delegate to wait until the scroll is complete.
However, if the insertion row is already on scree, the table view will not scroll and the method will not fire.
How can I wait to push the new controller until the end of the scroll, and deal with the case where no scroll will be initiated - and how might I tell the difference between each case?
The easiest way to check whether a given row is visible in your table view is something like this:
if (!CGRectContainsRect([self.tableView bounds], [self.tableView rectForRowAtIndexPath:indexPath])
{
// the row is partially outside the table view’s boundaries and needs to be scrolled for full visibility
}
else
{
// the row is within the boundaries and does not need to be scrolled
}
Try creating a method to see if scrolling is needed. If no scrolling is needed, call the push right away, otherwise wait for the delegate call and push.
- (BOOL)isSrollingingNeededForIndexPath:(NSIndexPath *)indexPath {
NSArray *visibleIndices = [self.tableView indexPathsForVisibleRows];
for (NSIndexPath *visibleIndexPath in visibleIndices)
if ([indexPath compare:visibleIndexPath] == NSOrderedSame)
return NO;
return YES;
}
Edit: Good point. Since indexPathsForVisibleRows is used for data rendering.
You could do essentially the same thing with indexPathsForRowsInRect where you use the content.offset.y and the tableview.frame.size.height to determine your "visible rect".
Then to account for partially visible rows at the top and bottom you could add rowHeight-1 to the top of the rect and subtract rowHeight - 1 from the bottom of the rect. Code shouldn't be too gnarly if you have static height rows. If you have varying height rows it would still work, but it would be a bit more involved.
All said though, it seems like a lot of code for something which you'd think would have a simple answer.

cellForRowAtIndexPath: not called

My app has two states: logged in and not logged in, and I have the following architecture (vastly simplified):
- ViewController A which contains a search box and a table view.
- ViewController B which is used for logging in the app.
The flow is the following:
- the user is not logged in;
- A is pushed on the stack. In viewWillAppear I check if the user is logged in and if yes, an async network request is being made and, once that is done, the table is loaded with the data from the network. However since the user is not logged in at this point, the table view is empty;
- the user taps on the search box; because he's not logged in, B is pushed (after a confirmation);
- he logs in successfully in B, then presses a button which pops B and shows A again;
- at this point in time, because he's logged in, from viewWillAppear I do the async request;
- when that is completed, I call reloadData on the table view.
What I notice is that numberOfRowsInSection: is called and it returns the correct result, however cellForRowAtIndexPath: is NOT called afterwards and the table remains empty.
I've checked and reloadData is called on the main thread.
Any idea what can it be? Cause it's driving me nuts!
Thanks,
S.
EDIT: Here's the async bit of code from viewWillAppear in A.
if ([User isLoggedIn]) {
[self.asyncRequest fetchDataWithCompletionHandler:^(id response, NSError *error) {
[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
if (error) {
[Utils displayError:error];
} else {
self.array = response;
self.isLoaded = YES;
[self.tableView reloadData];
[self.tableView setContentOffset:CGPointMake(0.0f, 0.0f) animated:NO];
}
}];
}
I've checked that the async request completes successfully and that response contains the correct data (this is the array used to back the UITableView).
After reloadData, I've put a breakpoint in tableView:numberOfRowsInSection: and it stops there and it returns the correct number of elements in array. After that, however, the breakpoint in tableView:cellForRowAtIndexPath: is never hit.
One valid scenario for why numberOfRowsInSection would be called but cellForRowAtIndexPath would not be called is when the size or positioning of the table does not require any rows to be displayed.
For example, let's say you had a table that was 20 pixels high, but the header for the first section was 30 high and you returned nil for the header (or did not implement viewForHeaderInSection). In this case, no rows would be displayed and it would look like the table just isn't there.
I see you are using IB. The sizing of the table can be deceptive in IB as it is assuming header and footer sizes. I would log the frame for the table so you understand where it is when the appliction is run (versus where it appears in IB). I would also be sure to manually size and position the table's frame before calling reloadData. This will solve this valid case where cellForRowAtIndexPath is not called.
Check that the numberOfSectionsInTableView is not returning 0.
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
// Return the number of sections.
return 1;
}
I had the exact same issue and the problem was that I was trying to call reloadData: from another thread. The solution would be:
dispatch_async(dispatch_get_main_queue(), ^{
[self.tableView reloadData];
});
If you are using a different dataSource, as I was, make sure you are retaining the dataSource. Merely instantiating the class that will be the dataSource and assigning it via tableView.dataSource = myDataClass will not be sufficient as the dataSource property of a tableView is weak and will be released when viewDidLoad completes. All of the other methods were being called for me — even, surprisingly, heightForRowAtIndexPath — so this took me some time to debug.
If a Table View inside a view that conflicts something like Scrool View, it does not called. You should separate the views in your Storyboard or *.xib file.
// True
▼ View
► Table View
► Scrool View
► Constraints
// False
▼ View
► Scrool View
► Table View
► Constraints
// For others showing up on this questions via Google, etc.
// Check and make sure you've set the table view's data source and delegate.
self.tableView.dataSource = self;
self.tableView.delegate = self;
We had the same/similar issue. The code reached numberOfSections and numberOfRowsInSection (and it even returned values) but could not reach cellForRowAt. At the time, Table View in the Storyboard had only constraints for right side, left side and top, but not for bottom. Once we added constraint to the bottom, it worked like a charm.
So, check that you provide all needed constraints for the Table View.
If you are using auto layout like me and adding tableview to your view controller's view make sure you have added this line when you are allocating your table view.
tableView?.translatesAutoresizingMaskIntoConstraints = false
Silly mistake from my side.
Everyone keeps talking about height, but my TableView in a StackView with leading alignment ended up with 0 width.
Make sure to check if your TableView is correct size using Debug View Hierarchy.
I solved the problem because my Subview on which i added the UITableView was not allocated so it was returning nill and tableview was not calling cellForRowAtIndexPath but numberOfRowsInSection getting called
Wasted hours and then found out that my tableview was inside a stackview. As soon as I removed the stackview, it worked like a charm. For me it's Xcode 11.3 .
I am using a customer datasource and it was released. Because it is a weak reference of tableview.
This is embarrassing and perplexing, but here's my fix.
My code:
_scanResultTable.delegate = self;
_scanResultTable.dataSource = self; // self's lifecycle was fine, wasn't getting released
[_scanResultTable reloadData];
So the weird part is: the identifier _scanResultTable was never declared in my code, anywhere in the project. I have no idea how this compiled (and I recompiled several times).
My root cause was that I had linked my table output to scanResultTable in my ViewController, but was referring to it as _scanResultTable. Once I started using scanResultTable like I should've been, things cleared up. This makes me wonder if objective-c has something special about leading underscores in identifiers...
Edit: It does! Good lord I can't wait to never touch this language again.
In my case, I had the TableView inside a StackView. When I put it outside the StackView it worked.
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
// Make sure self.data isn't nil!
// If it is, you'll always return 0 and therefore
// cellForRowAtIndexPath will never get called.
return [self.data count];
}
I'm using ReactiveCocoa. So I've created the model for table view. The data was prepared to be displayed, so numberOfRows and so on was called. But I haven't added the tableview as subview, thus cellForRowAtIndexPath was not called)))
This maybe too obvious but in my case i was deleting an element (Label) which was a reference to the top margin of my table programmatically, so it was not a problem with delegate/source but of the table having some problems with the content-size / height, this conflict created a weird behaviour, calling numberOfRowsInSection but not cellForRowAt indexPath
Are you sure that after the user logged in and B is popped the viewWillAppear method in A gets called in order to perform the refresh?
If you show B as a modal controller, when you dismiss it you won't have the viewWillAppear method called.
As far as I know, viewWillAppear/viewDidAppear (and other like it) are events generated by the UINavigationController in the case of navigation events (push/pop viewcontrollers).
So maybe that is why you eventually get your refresh when you leave A and return ... it all depends on the way to display the next viewcontroller.
Try to insert new rows manually instead of [self.tableView reloadData]:
[self.tableView beginUpdates];
for (int i = 0; i < responseArray.count; i++) {
_rowsNumber += 1;
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:i inSection:0];
[self.tableView insertRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationBottom];
}
[self.tableView endUpdates];
In dataSource method return incremented int _rowsNumber:
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return _rowsNumber;
}
i solved same issue by checking tableViewCell identifier. Go To Attributes İnspector and look at identifier section. Probably is missing. So write cell identifier.
Same case here, but it was a mistake:
I included a Scrollview with 2 tablesViews inside a ViewController (design requirements), in the scrollview controller I created the ViewControllers (that contains the TableViews) programatically and I used a weak var to store it, bad idea because they were released at the end of viewDidLoad method.
If you don't see your tableview content, please, check if it was released.
My mistake was very brain-painful for me because all methods (delegate&datasource) were called except viewForCell...
I have same issue
i have use Table View inside the StackView and table view scroll disable and set height constrain but after that
tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
Not working stop calling this method all Data Source and Delegate set properly.
if you have same issue than Solution is set Table View bottom , leading , trailing constrain
None of the answers here helped me.
I was using doing my constraints programmatically but forgot to write:
myTableView.translatesAutoresizingMaskIntoConstraints = false
Make sure your constraints are correct and none of them is removed at build time.

insertRowsAtIndexPaths calling cellForRowAtIndexPath for every row

I'm trying to insert a bunch of rows into an empty UITableView in one step using insertRowsAtIndexPaths. (I know that this doesn't sound all that useful, but it's a simplified version of what I really need to do.)
The issue I'm seeing is that after I make the insertRowsAtIndexPaths call, I get cellForRowAtIndexPath calls for every row I inserted, rather than just those that are visible.
That doesn't seem right. And it's pretty much useless to me if it does this.
The only slightly odd other artifact I see is that when I do this, it actually seems to animate the rows into place. This isn't the withRowAnimation, since that's set to none. But it seems there's some sort of higher level animation concept going on here. I had the off-idea that as it animated the rows into place it thought it needed cells for more/all the rows until they got pushed off screen. But I can't turn off this animation without just using reloadData which I'd prefer to avoid.
Generally I have a data set that changes behind the scenes. I can carefully go through and construct the changes to generate data for the insert/delete/reload calls. But I can't limit that to what's on screen since then it doesn't match the data provider.
I figure I must be missing something ... any ideas?
(I am doing this within a beginUpdates/endUpdates pair but that seems to make no difference.)
I talked to some Apple folks today. The issue is definitely related to the animation that UITableView does when you insert new rows. This is the animation that happens to create new rows for cells, separate from the animation that is used when those rows are brought in via insertRowsAtIndexPaths.
Since the table-level animation creates new rows through a sort of "grow down" kind of thing, during the insert animation small amounts of many cells are considered visible and the table will call cellForRowAtIndexPath for them.
There doesn't seem to be any alternative implementation for this that works for all cases. The recommended solution for my case is to "lie": figure out what cells will exist on page after the animation and insert those. After that animation completes, insert the remainder of cells that won't show and the table won't call cellForRowAtIndexPath.
This requires implementing an object between the table view and the "real" data source that can "lie" to the table view about the number of rows in the table for the duration of the animation. Painful, but that's apparently what has been done in similar cases.
I had the same problem, what I did was to declare an attribute called bMassiveLoad initialized to false.
Before I do the 'massive load' I set it to true and the first thing I do in the cellForRowAtIndexPath was to check for that attribute.
At the end of the 'maseive load' I set it to false again and call reloadData...
I know that it is not a beautiful answer, but it helps me.
I was able to solve this by combining a few answers from here:
set the massive load boolean as needed as suggested by pocjoc
- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(NSIndexPath *)newIndexPath {
UITableView *tableView = self.tableView;
switch(type) {
case NSFetchedResultsChangeInsert:
massiveLoad = YES;
[tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
break;
...
}
}
and reload the visible rows when it ends
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller {
[self.tableView endUpdates];
if (massiveLoad) {
massiveLoad = NO;
[self.tableView reloadRowsAtIndexPaths:[self.tableView indexPathsForVisibleRows] withRowAnimation:UITableViewRowAnimationFade];
}
}
UITableView calls the relevant delegate and data source methods immediately after a call to insertRowsAtIndexPaths to get the cells and other content for visible cells. This is regardless of whether or not animated is set to YES or NO.
See the discussion here
If you only want the tableView to update the visible cells, then just update your dataSource and call [tableView reloadData].
I don't know whether the behavior you describe is correct, but UITableView has got two methods: beginUpdates and endUpdates (here) that are meant to optimize redrawing (so that animating is done only at the end, otherwise they are done one by one and this could possibly produce the call to cellForRowAtIndexPath).
Now, I am not sure if this could help you, but perhaps you can give this a try...
I feel like there may be a different way to go about this, but what I have found is that if you have existing cells in the visible range (for example, if you have 10 cells visible at all times and you initialize the tableView with 10 blank cells), when you add new rows, the extra rows are not called in cellForRowAtIndexPath.
So, what to do about it. This is how I went about fixing this. When I created the tableView, I added cells to the visible region. They were blank. Then, when I inserted new rows, I only added index paths for the extra rows (i.e. if I have 10 visible rows and want to add 50 rows, including the visible rows, I only added 40 since the first 10 already exist). This of course leaves you with 10 blank cells. At this point, you can use:
NSArray *indexPaths = [myTableView indexPathsForVisibleCells];
[myTableView reloadRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationNone];
This selects your 10 visible blank cells and reloads them with the new information you have passed in (I assume you load data into an array for this part). You will need to add a little logic to your cellForRowAtIndexPath method such as:
if (![myDataArray count]) {
// create a blank cell;
} else {
// add context to your cells as you want;
}
This should do the trick for you and only load the cells you have showing on the screen
For swift you can try this
self.mainTableView.beginUpdates()
self.mainTableView.insertRowsAtIndexPaths(indexPaths, withRowAnimation: UITableViewRowAnimation.Fade)
self.mainTableView.reloadRowsAtIndexPaths(self.mainTableView.indexPathsForVisibleRows!, withRowAnimation: UITableViewRowAnimation.Fade)
self.mainTableView.endUpdates()
The UITableView will request the visible cells by cellForRowAtIndexPath: method, and it can reduce the processing time when there are many cells, ex 1k cells, and reduce spent memory by reusable cell mechanism. If you need to process all cells, then you should implement a custom method to re-process your source data, and then call [tableView reloadData] to update the UI.

how to implement cyclic scrolling on tableview

Could you please help me with circular scrolling in tableview please.
I want that if I scroll down tableview, the rows should go in the reverse way --
it should appear that move back around (bottom rows go around and now come back down from the top) i.e, cyclic scrolling basically.
How can I do so. Any suggestions please.
Thanx in advance.
You could "fake" the cyclic scrolling repeating the same cells all over again. In the numberOfRowsInSection method, return n times the actual number of rows. Make sure n is big enough.
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return numberOfActualRows*100;
}
Then in the cellForRowAtIndexPath method (and elsewhere) use the mod operator (%) to return the proper cell.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
NSUInteger actualRow = indexPath.row % numberOfActualRows;
...
}
You may want to hide the sroll indicator.
self.tableView.showsVerticalScrollIndicator = NO;
You may also want to scoll the table view to the middle before you display the table so scrolling backwards works fine.
[self.tableView scrollToRowAtIndexPath: [NSIndexPath indexPathForRow:[self tableView:self.tableView numberOfRowsInSection:0]/2 inSection:0] atScrollPosition:UITableViewScrollPositionTop animated:NO];
Of course, the user would eventually hit the bottom or the top if he/she kept scrolling over and over.
This question has already been asked: implementing a cyclic UITableView
I'm copying that answer here to make it easier because the asker hasn't ticked my answer.
UITableView is same as UIScrollView in scrollViewDidScroll method.
So, its easy to emulate infinite scrolling.
double the array so that head and tail are joined together to emulate circular table
use my following code to make user switch between 1st part of doubled table and 2nd part of doubled table when they tend to reach the start or the end of the table.
:
/* To emulate infinite scrolling...
The table data was doubled to join the head and tail: (suppose table had 1,2,3,4)
1 2 3 4|1 2 3 4 (actual data doubled)
---------------
1 2 3 4 5 6 7 8 (visualising joined table in eight parts)
When the user scrolls backwards to 1/8th of the joined table, user is actually at the 1/4th of actual data, so we scroll instantly (we take user) to the 5/8th of the joined table where the cells are exactly the same.
Similarly, when user scrolls to 6/8th of the table, we will scroll back to 2/8th where the cells are same. (I'm using 6/8th when 7/8th sound more logical because 6/8th is good for small tables.)
In simple words, when user reaches 1/4th of the first half of table, we scroll to 1/4th of the second half, when he reaches 2/4th of the second half of table, we scroll to the 2/4 of first half. This is done simply by subtracting OR adding half the length of the new/joined table.
Written and posted by Anup Kattel. Feel free to use this code. Please keep these comments if you don't mind.
*/
-(void)scrollViewDidScroll:(UIScrollView *)scrollView_
{
CGFloat currentOffsetX = scrollView_.contentOffset.x;
CGFloat currentOffSetY = scrollView_.contentOffset.y;
CGFloat contentHeight = scrollView_.contentSize.height;
if (currentOffSetY < (contentHeight / 8.0)) {
scrollView_.contentOffset = CGPointMake(currentOffsetX,(currentOffSetY + (contentHeight/2)));
}
if (currentOffSetY > ((contentHeight * 6)/ 8.0)) {
scrollView_.contentOffset = CGPointMake(currentOffsetX,(currentOffSetY - (contentHeight/2)));
}
}
P.S. - I've used this code on one of my apps called NT Time Table (Lite). If you want the preview, you can check out the app: https://itunes.apple.com/au/app/nt-time-table-lite/id528213278?mt=8
If your table can sometimes be too short, at the beginning of the above method you can add a if logic to exit when data count is say for example less than 9.
I haven't done this myself, but you could try the approach you'd use with a UIScrollView to implement cycling scrolling of views (after all UITableView is a subclass of UIScrollView).
I would do as follows:
Create a UITableView with an arbitrary number of cells (at least 7 but will need more to prevent fast scrolling bumping at the end)
Position your UITableView so the centre cell is visible
Maintain a pointer to the index of the cell you are looking to display
In your cellForRowAtIndexPath: use your pointer to as an offset and add the row to it to get the cell that you want
When the UITableView has stopped moving (your UITableViewDelegate can serve as UIScrollViewDelegate so you can use scrollViewDidEndDecelerating). Set your offset index to the current cell, move the table view back to the centre cell without animation and reload the data.
The issue you will have is if the user keeps scrolling without stopping they will eventually hit the bumpers as the number of cells in the table is reached.
Hope this helps, and please post back if you get this working and it looks at all reasonable.
Regards
Dave
This is very much difficult to implement. However, take a look at the ScorllingMadness, which shows the demo of nested (cyclic) pages in a scroll-view.
You need to use the similar kind of trick here as UITableView is a subclass of UIScrollView.

UITableView's indexPathsForVisibleRows incorrect?

When I check for a table's visible indexPaths with 'indexPathsForVisibleRows' during UIScrollViewDelegate's scrollViewDidScroll and scrollViewDidEndDragging, it seems to be accurate.
But sometimes just when scrolling and dragging is ending and 'cellForRowAtIndexPath' is invoked, the call to 'indexPathsForVisibleRows' returns 0. There are rows visible on-screen so I know it can't be 0. As soon as this happens, I can invoke UITableView::visibleCells and get a non-zero value.
Why does this discrepancy exist?
Did you tried calling methods in below sequence:
[MyTable visibleCells];
[MyTable indexPathsForVisibleRows];
There is a bug in iOS with indexPathsForVisibleRows. Use above two line code to get correct visible rows.
in swift, this works:
let viscells = tableView.visibleCells
let isThisCellVisible = (tableView.indexPathsForVisibleRows ?? []).contains(indexPath)
but this does not
_ = tableView.visibleCells
let isThisCellVisible = (tableView.indexPathsForVisibleRows ?? []).contains(indexPath)
visibleCells and NSFetchedResultsController
be careful how you access visibleCells. the UI will freeze indefinitely if you [attempt] to access the table view's visibleCells while they were in the process of being updated, which is not allowed.
e.g. how you use tableView.beginUpdates()/tableView.endUpdates(), or controllerWillChangeContent(_:)/controllerDidChangeContent(_:), etc
full debug message if this happens is:
[Assert] Attempted to access the table view's visibleCells while they were in the process of being updated, which is not allowed. Make a symbolic breakpoint at UITableViewAlertForVisibleCellsAccessDuringUpdate to catch this in the debugger and see what caused this to occur. Perhaps you are trying to ask the table view for the visible cells from inside a table view callback about a specific row?