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.
Related
I have a potentially very long scrolling log of messages within a UITableView The user would be able to navigate the table by scrolling.
What I'm trying to do is add new content at the top of the table. If the top of the table is visible, the new row would push the rest of the content down. But if the first row of the table is not visible, the table would silently add another row on top without scrolling.
I'm doing this to prevent the table scrolling to the top from interrupting the user browsing data. If I ask the table to simply scroll to the new insertion point, the user's position within the table would be lost, frustrating the user.
One of the ways I can try to implement this is by inserting data into an NSMutableArray at index 0. Would this work? An extra question: how expensive is inserting data at the beginning of an NSMutableArray. I would expect that such operation is optimized, but would like to make sure. What am I missing with this implementation?
Thank you!
How to insert without bumping the view:
Use the tableView's (UIScrollView) contentOffset values to find where the user is currently scrolled to.
Add the new row at the top, with animated:NO
Update the tableView's contentOffset to be where the user was at plus whatever the height of your row is.
How to avoid causing issues doing this while the user is dragging:
Keep track of when the user is dragging, and if you want to insert rows during a drag / motion, add them to a queue that is executed after the user releases.
CGSize beforeContentSize = self.tableView.contentSize;
[self.tableView reloadData];
CGSize afterContentSize = self.tableView.contentSize;
CGPoint afterContentOffset = self.tableView.contentOffset;
CGPoint newContentOffset = CGPointMake(afterContentOffset.x, afterContentOffset.y + afterContentSize.height - beforeContentSize.height);
self.tableView.contentOffset = newContentOffset;
Just posted an answer with code snippets here
Keep uitableview static when inserting rows at the top
Basically:
Save the scroll offset where you would have called beginUpdate:.
In place of calls to InsertCell you add the cell's height to the saved offset.
Where you would have called endUpdate:, call reloadData, then scroll by the saved offset with NO animation.
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.
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.
Is there any message I can override called when a Table Cell goes away (on scrolling the table)?
I think it should be something like dealoc.
I'm asking this because I have below situation:
I have a table with many cells (100+) and each of this cell contains a ImageView. For loading the image (from a URL) I'm using NSOperationQueue/NSInvocationOperation. The problem appears when user is scrolling the table before the image is completely loaded: because I'm reusing the cells the image is displayed in wrong cell.
To avoid this I'm thinking to use "cancelAllOperations" of NSOperationQueue object when the cell goes away.
Note: I've tried but is not working if I call this message on "prepareForReuse".
iOS 6:
- (void)tableView:(UITableView *)tableView didEndDisplayingCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath {
// Cancel operation here for cell at indexPath
}
Why not keeping the images in your table data source - the array that holds all the data for the table?
This way you won't have to load these images once again when scrolling back and it will solve your problem...
You can subclass UITableViewCell (or any UIView) and override willMoveToWindow:. It is called whenever the cell appears (or scrolls off screen).
When it goes out of the window the parameter will be nil:
- (void)willMoveToWindow:(UIWindow *)newWindow
{
[super willMoveToWindow:newWindow];
if (newWindow==nil) {
// Cell is no longer in window
}
}
If there were, it would be in the UITableViewDelegate class reference: http://developer.apple.com/iphone/library/documentation/UIKit/Reference/UITableViewDelegate_Protocol/Reference/Reference.html#//apple_ref/occ/intf/UITableViewDelegate
The only thing they have is willDisplayCell which lets you do last minute adjustments BEFORE the cell appears. They don't have anything for when it disappears, but you could probably figure that out since there are only a certain number of cells on the screen at a time for a given cell height.
So if one is appearing and for a cell height of 80 for instance (in portrait mode so 480px screen height), then you can say that the one 6 cells away is about to disappear (6 cells * 80 pixels = 480). There are a couple other things to consider like which way you are scrolling, but you get the general idea.
Example Code: You should also look at lazy table loading via Apple's sample code http://developer.apple.com/iphone/library/samplecode/LazyTableImages/Introduction/Intro.html
I had the same issue and got some nice feedback in the developer forum. Quinn - The Eskimo from Apple:
As an aside, cancelling a network
small transfer because something has
scrolled off the screen is probably a
performance negative. For small
transfers, it's usually more efficient
to let it run to completion (and cache
the results in case they're needed in
the future). This is because of the
way that NSURLConnection manages HTTP
connection reuse. If you cancel a
transfer, NSURLConnection has to
either a) drop the underlying HTTP
connection on the floor, which means
it can't be reused, or b) continue
reading and just junk the data.
Neither of this is the best use of
resources.
Share and Enjoy
-- Quinn "The Eskimo!"
So, I'm not cancelling all the ImageDownload Operation, but rather only start them, when the user stops scrolling. Up to then only a placeholder is shown:
- (void)scrollViewWillBeginDragging:(UITableView *) tableView
{
self.dragging = TRUE;
}
- (void) scrollViewDidEndDragging: (UITableView *) tableView willDecelerate: (BOOL) decelerate
{
if(!decelerate && self.dragging)
[self loadThumbsForVisibleCells];
else
self.dragging = FALSE;
}
- (void) scrollViewDidEndDecelerating: (UITableView *) tableView
{
[self loadThumbsForVisibleCells];
}
Hope this helps!
This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
Can you animate a height change on a UITableViewCell when selected?
I've been doing a lot of Googling to try to figure out the correct way of doing this, and so far I am at a loss.
I have subclassed UITableViewCell with my own view and I am trying to animate the height of a UITableViewCell to expand when it is selected, and to contract when it is selected again. This table has the potential to contain thousands of rows, so I don't want to override the tableView's heightForRowAtIndexPath. Ideally I'd like to be able to have more than one cell expanded at a time, but that isn't as critical. What is the best way to do this?
Thank you,
Justin
There is no other mechanism for specifying cell height than heightForRowAtIndexPath. Unless you're properly accounting for the expanded cell in that method you're going to find your other cells either running over it or hidden under it. From boneheaded code where I forgot to set up heightForRowAtIndexPath, I'm pretty sure your other cells will be displayed over it.
Since you're talking thousands of rows, we'll assume the user can't rearrange the cells.
What you could do is store the expanded cell's index path when the user taps a given cell. Then, heightForRowAtIndexPath might look like this:
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath*)indexPath
{
if ([indexPath isEqual:lastSelectedIndexPath])
{
return 80;
}
else {
return 44;
}
}
If you really want multiple selections, you could store the appropriate index paths to an array and check for them like this:
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath*)indexPath
{
CGFloat cellHeight = 44;
for (NSIndexPath *expandedIndexPath in expandedIndexPathsArray)
{
if ([indexPath compare:expandedIndexPath] == NSOrderedSame)
{
cellHeight = 80;
break;
}
}
return cellHeight;
}
Getting the animation to look right will take some work. I've toyed with this particular UI idea for awhile now and could never bring myself to sit down and really make it happen. One thing you could do is display a dummy view that animates while you're updating the tableview behind it.
- (void)reloadRowsAtIndexPaths:(NSArray *)indexPathswithRowAnimation:(UITableViewRowAnimation)animation
could be of interest for this as you might be able to use the cell animations to simulate movement of the cells below to accommodate your expansion.
If you can forgo animating the cell's frame altogether, you could do an opacity animation of the expanded data once the new height has been set.
It's a toughie but if you make it work I bet it'll be pretty cool. Good luck.