I have a setup with sections in a UITableView which all have a custom section header view. Only one section at a time can have visible rows.
To create an effect that displays other cell rows I basically use the following function:
[self.tableView beginUpdates];
if ( sectionHeaderInsert >= 0 && sectionHeaderInsert <= numberOfSectionsInTableView ) {
[ticketReastepsScreenView.tableView insertRowsAtIndexPaths:indexPathsInsert withRowAnimation:UITableViewRowAnimationFade];
}
[self updateTmpSelectedNumberToCurrent];
if ( sectionHeaderRemove >= 0 && sectionHeaderRemove <= numberOfSectionsInTableView ) {
[ticketReastepsScreenView.tableView deleteRowsAtIndexPaths:indexPathsRemove withRowAnimation:UITableViewRowAnimationFade];
}
[self.tableView endUpdates];
Now what happens is that in the end the new section "opens" and the old one "closes" propperly, but: if I have more sections that fit on the screen the last section that is (partly) visible will not repaint propperly - it is completely covered in white.
All sections are asked gor new heights and I think it even asks for new views (which I hae in an array). The views are then asked to [view setNeedsDisplay];
When I scroll a bit so that the last section header disappears and then scroll it back in, it comes to life again and is visible until I change the currently open section again.
UPD: This funny behavior only appears when I do the insertRow and deleteRow together. I can also simply close one section (deleteRow) and then in a section touch-action open the new one (insertRow) and the behavior is different.
I also tried having an endUpdates and beginUpdates in between the two actions - no positive result.
UPD2: When transitioning to the final position (positions are good) I can see the last section header being painted correctly, but then disappear (no animation, simple plain white over it)
Whatever the REAL problem was - I rewrote heavy parts of the TableView and I think what I've learned is:
If you need a UITableViewCell, use a UITableViewCell. If you need a UIView for the section headers, use a UIView for the section headers.
I've mixed the section headers with the TableViewCells just to re-use some code I had in there. Since categories can only extend one specific class I had to use delegation/provider methods/objects to re-use certain code. It may need more subclassing but is actually much safer.
In addition auto-scrolling to a position now works significantly better and the drawing artifacts are gone.
Related
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.
When I call this Code
[[self tableView]reloadRowsAtIndexPaths:[NSArray arrayWithObject:[NSIndexPath indexPathForRow:0 inSection:kSectionShoppingList]] withRowAnimation:UITableViewRowAnimationNone];
HeightForRowAtIndexPath Calls
But in CellForRowAtIndexPath for section 3 row 0 does not call.
Looking for your suggestion.
and it is specific to Device 3g iOS4.0.1.
Is anyone earlier have faced same issue.
Is the cell you are reloading visible ?
Not sure about iOS4.0.1, don't have it installed to test, but on iOS5+, the cellForRowAtIndexPath is not called if the cell is not visible, just tried it in one of my apps.
The issue i have mentioned look like a bug in iOs4.0.1. anyway I have solved it by reloading whole table using reloadData method instead of reloading particular rows.
Make sure your are reloading sections and rows that are visible and corresponding the the right indices in your tableview. Also check that your tableView delegate and datasource are correctly set.
reloadData method as the following comment :
- (void)reloadData; // reloads everything from scratch. redisplays visible rows. because we only keep info about visible rows, this is cheap. will adjust offset if table shrinks
you may also have trouble reloading single rows if your table is mutating, then you should beginUpdate / endUpdate methods
- (void)beginUpdates; // allow multiple insert/delete of rows and sections to be animated simultaneously. Nestable
- (void)endUpdates; // only call insert/delete/reload calls or change the editing state inside an update block. otherwise things like row count, etc. may be invalid.
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.
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.
I'd like to add section headers to my grouped table view's sections but I'd like them to appear seamless (see image). The default, as we're all well aware of, is rounded top corners on the first row of a grouped table view cell so it ends up looking like crap when you merge them.
Any way to specify when indexPath.row = 0 that the UITableViewCell should use row style "middle" or something like that?
If not then what are my options? I guess I could scratch the section header and use Row 0 as a quasi-header then push my array data +1 to fill the rest of the table? I'd rather not roll my own from scratch...if possible.
Sample Table http://img35.imageshack.us/img35/8181/sampletable.png
Edit:
"Crap" looks like this:
alt text http://img25.imageshack.us/img25/9748/crapsection.png
Don't do what you're doing, it's against HIG
Ok, ok, I'll tell you how to do it:
You're going to want to do your own cell background views. The default grouped one is not what you want.
When a tableview asks you for a cell, set its backgroundView and selectedBackgroundView to something that looks appropriate for its place in the tableview.
Usually, this means a UIImageView with the appropriate image, though you can go wild here with a custom view, but there are gotchas.
So in your case, you would do
if (indexPath.row > sectionRowCount - 1) {
//Not the last row
//Put in the middle background
} else {
//Put in the end background
}
Then you'll want a custom table section header, but that's pretty easy.
In your case, you probably won't have to worry about when there's just one row, so that makes things even easier.
Take a look at the tutorial here:
cocoa with love basically what you need is 3 different images. One for the top row, one for the bottom, and a 3rd for the middle rows.
You could also not use the section header, but instead use a custom cell as the first cell of the section. So when ([indexPath row] == 0), return a custom cell that is the "header" and then return the "regular" cells (offset by one row) for the rest. You'll also have to make adjustments to the numberOfRowsInSection function to return +1.