I have a tableView that I'm inserting rows into at the top.
Whilst I'm doing this I want the current view to stay completely still, so the rows only appear if you scroll back up.
I've tried saving the current position of the underlying UIScrollview and resetting the position after the rows have been inserted but this results in a judder, up and down, although it does end up back in the same place.
Is there a good way of achieving this ?
Update: I am using beginUpdate, then insertRowsAtIndexPath, endUpdates. There is no reloadData call.
scrollToRowAtIndexPath jumps to the top of the current cell (saved before adding rows).
The other approach I tried, which ends up in exactly the right pace, but with a judder is.
save tableView currentOffset. (Underlying scrollView method)
Add rows (beginUpdates,insert...,endUpdates)
reloadData ( to force a recalulation of the scrollview size )
Recalculate the correct new offset from the bottom of the scrollview
setContentOffset (Underlying scrollview method)
Trouble is the reloadData causes the scrollview/tableview to start scrolling briefly, then the setContentOffset returns it to the correct place.
Is there a way of getting a tableView to work out it's new size without starting display ?
Wrapping the whole thing in a beginAnimation commitAnimation doesn't help much either.
Update 2: This can clearly be done - see the offical twitter app for one when you pull down for updates.
There's really no need to sum up all rows height,
the new contentSize after reloading the table is already representing that.
So all you have to do is calculate the delta of contentSize height and add it to the current offset.
...
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;
...
-(void) updateTableWithNewRowCount : (int) rowCount
{
//Save the tableview content offset
CGPoint tableViewOffset = [self.tableView contentOffset];
//Turn of animations for the update block
//to get the effect of adding rows on top of TableView
[UIView setAnimationsEnabled:NO];
[self.tableView beginUpdates];
NSMutableArray *rowsInsertIndexPath = [[NSMutableArray alloc] init];
int heightForNewRows = 0;
for (NSInteger i = 0; i < rowCount; i++) {
NSIndexPath *tempIndexPath = [NSIndexPath indexPathForRow:i inSection:SECTION_TO_INSERT];
[rowsInsertIndexPath addObject:tempIndexPath];
heightForNewRows = heightForNewRows + [self heightForCellAtIndexPath:tempIndexPath];
}
[self.tableView insertRowsAtIndexPaths:rowsInsertIndexPath withRowAnimation:UITableViewRowAnimationNone];
tableViewOffset.y += heightForNewRows;
[self.tableView endUpdates];
[UIView setAnimationsEnabled:YES];
[self.tableView setContentOffset:tableViewOffset animated:NO];
}
-(int) heightForCellAtIndexPath: (NSIndexPath *) indexPath
{
UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:indexPath];
int cellHeight = cell.frame.size.height;
return cellHeight;
}
Simply pass in the row count of the new rows to insert at the top.
#Dean's way of using an image cache is too hacky and I think it destroys the responsiveness of the UI.
One proper way:
Use a UITableView subclass and override -setContentSize: in which you can by some means calculate how much the table view is pushed down and offset that by setting contentOffset.
This is a simplest sample code to handle the simplest situation where all insertions happen at the top of table view:
#implementation MyTableView
- (void)setContentSize:(CGSize)contentSize {
// I don't want move the table view during its initial loading of content.
if (!CGSizeEqualToSize(self.contentSize, CGSizeZero)) {
if (contentSize.height > self.contentSize.height) {
CGPoint offset = self.contentOffset;
offset.y += (contentSize.height - self.contentSize.height);
self.contentOffset = offset;
}
}
[super setContentSize:contentSize];
}
#end
had the same problem and found a solution.
save tableView currentOffset. (Underlying scrollView method)
//Add rows (beginUpdates,insert...,endUpdates) // don't do this!
reloadData ( to force a recalulation of the scrollview size )
add newly inserted row heights to contentOffset.y here, using tableView:heightForRowAtIndexPath:
setContentOffset (Underlying scrollview method)
like this:
- (CGFloat) firstRowHeight
{
return [self tableView:[self tableView] heightForRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0]];
}
...
CGPoint offset = [[self tableView] contentOffset];
[self tableView] reloadData];
offset.y += [self firstRowHeight];
if (offset.y > [[self tableView] contentSize].height) {
offset.y = 0;
}
[[self tableView] setContentOffset:offset];
...
works perfectly, without glitches.
I did some testing with a core data sample project and got it to sit still while new cells were added above the top visible cell. This code would need adjustment for tables with empty space on the screen, but once the screen is filled, it works fine.
static CGPoint delayOffset = {0.0};
- (void)controllerWillChangeContent:(NSFetchedResultsController*)controller {
if ( animateChanges )
[self.tableView beginUpdates];
delayOffset = self.tableView.contentOffset; // get the current scroll setting
}
Added this at cell insertion points. You may make counterpart subtraction for cell deletion.
case NSFetchedResultsChangeInsert:
delayOffset.y += self.tableView.rowHeight; // add for each new row
if ( animateChanges )
[tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationNone];
break;
and finally
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller {
if ( animateChanges )
{
[self.tableView setContentOffset:delayOffset animated:YES];
[self.tableView endUpdates];
}
else
{
[self.tableView reloadData];
[self.tableView setContentOffset:delayOffset animated:NO];
}
}
With animateChanges = NO, I could not see anything move when cells were added.
In testing with animateChanges = YES, the "judder" was there. It seems the animation of cell insertion did not have the same speed as the animated table scrolling. While the result at the end could end with visible cells exactly where they started, the whole table appears to move 2 or 3 pixels, then move back.
If the animation speeds could be make to equal, it may appear to stay put.
However, when I pressed the button to add rows before the previous animation finished, it would abruptly stop the animation and start the next, making an abrupt change of position.
#Dean,
You can change your code like this to prevent animating.
[tableView beginUpdates];
[UIView setAnimationsEnabled:NO];
// ...
[tableView endUpdates];
[tableView setContentOffset:newOffset animated:NO];
[UIView setAnimationsEnabled:YES];
Everyone loves copy and pasting code examples, so here's an implementation of Andrey Z.'s answer.
This is in my delegateDidFinishUpdating:(MyDataSourceDelegate*)delegate method
if (self.contentOffset.y <= 0)
{
[self beginUpdates];
[self insertRowsAtIndexPaths:insertedIndexPaths withRowAnimation:insertAnimation];
[self endUpdates];
}
else
{
CGPoint newContentOffset = self.contentOffset;
[self reloadData];
for (NSIndexPath *indexPath in insertedIndexPaths)
newContentOffset.y += [self.delegate tableView:self heightForRowAtIndexPath:indexPath];
[self setContentOffset:newContentOffset];
NSLog(#"New data at top of table view");
}
The NSLog at the bottom can be replaced with a call to show a view that indicated there's fresh data.
I faced situation where there are many sections which may have different row count between -reloadData calls because of custom grouping, and row heights vary. So here is solution based on AndreyZ's. It contentHeight property of UIScrollView before and after -reloadData and it seems like more universal.
CGFloat contentHeight = self.tableView.contentSize.height;
CGPoint offset = self.tableView.contentOffset;
[self.tableView reloadData];
offset.y += (self.tableView.contentSize.height - contentHeight);
if (offset.y > [self.tableView contentSize].height)
offset.y = 0;
[self.tableView setContentOffset:offset];
I want add additional condition.
If your code in iOS11 or more, you need do like below;
In iOS 11, table views use estimated heights by default. This means that the contentSize is just as estimated value initially. If you need to use the contentSize, you’ll want to disable estimated heights by setting the 3 estimated height properties to zero:
tableView.estimatedRowHeight = 0
tableView.estimatedSectionHeaderHeight = 0 tableView.estimatedSectionFooterHeight = 0
How are you adding the rows to the table?
If you're changing the data source and then calling reloadData, that may result in the table being scrolled to the top again.
However, if you use the beginUpdates, insertRowsAtIndexPaths:withRowAnimation:, endUpdates methods, you should be able to insert rows without having to call reloadData thus keeping the table in its original position.
Don't forget to modify your data source before calling endUpdates or else you'll end up with an internal inconsistency exception.
You don't need to do so much difficult operations, furthermore these manipulations wouldn't work perfectly. The simple solution is to rotate table view, and then rotate cells into it.
tableView.transform = CGAffineTransformMakeRotation(M_PI);
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
cell.transform = CGAffineTransformMakeRotation(M_PI);
}
Use [tableView setScrollIndicatorInsets:UIEdgeInsetsMake(0, 0, 0, 310)] to set relative position to scroll indicator. It will be on the right side after you table view rotation.
Just a heads up it does not seem possible to do this if you return estimated heights for the tableview.
- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath ;
If you implement this method and return a rough height your tableview will jump about when reloading as it appears to use these heights when setting the offsets.
To get it working use one of the above answers (I went with #Mayank Yadav answer), don't implement the estimatedHeight method and cache the cell heights (remembering to adjust the cache when you insert additional cells at the top).
Simple solution to disable animations
func addNewRows(indexPaths: [NSIndexPath]) {
let addBlock = { () -> Void in
self.tableView.beginUpdates()
self.tableView.insertRowsAtIndexPaths(indexPaths, withRowAnimation: .None)
self.tableView.endUpdates()
}
tableView.contentOffset.y >= tableView.height() ? UIView.performWithoutAnimation(addBlock) : addBlock()
}
Late to the party but this works even when cell have dynamic heights (a.k.a. UITableViewAutomaticDimension), no need to iterate over cells to calculate their size, but works only when items are added at the very beginning of the tableView and there is no header, with a little bit of math it's probably possible to adapt this to every situation:
func tableView(tableView: UITableView, willDisplayCell cell: UITableViewCell, forRowAtIndexPath indexPath: NSIndexPath) {
if indexPath.row == 0 {
self.getMoreMessages()
}
}
private func getMoreMessages(){
var initialOffset = self.tableView.contentOffset.y
self.tableView.reloadData()
//#numberOfCellsAdded: number of items added at top of the table
self.tableView.scrollToRowAtIndexPath(NSIndexPath(forRow: numberOfCellsAdded, inSection: 0), atScrollPosition: .Top, animated: false)
self.tableView.contentOffset.y += initialOffset
}
I solved this in the end by rendering the current tableview into a UIImage and then putting a temporary UIImageView over the tableview whilst it animates.
The following code will generate the image
// Save the current tableView as an UIImage
CSize pageSize = [[self tableView] frame].size;
UIGraphicsBeginImageContextWithOptions(pageSize, YES, 0.0); // 0.0 means scale appropriate for device ( retina or no )
CGContextRef resizedContext = UIGraphicsGetCurrentContext();
CGPoint offset = [[self tableView] contentOffset];
CGContextTranslateCTM(resizedContext,-(offset.x),-(offset.y));
[[[self tableView ]layer] renderInContext:resizedContext];
UIImage *viewImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
You need to keep track of how much the tableview will have grown by whilst inserting rows and make sure you scroll the tableview back to the exact same position.
Based on Andrey Z's answer, here is a live example working perfect for me...
int numberOfRowsBeforeUpdate = [controller.tableView numberOfRowsInSection:0];
CGPoint currentOffset = controller.tableView.contentOffset;
if(numberOfRowsBeforeUpdate>0)
{
[controller.tableView reloadData];
int numberOfRowsAfterUpdate = [controller.tableView numberOfRowsInSection:0];
float rowHeight = [controller getTableViewCellHeight]; //custom method in my controller
float offset = (numberOfRowsAfterUpdate-numberOfRowsBeforeUpdate)*rowHeight;
if(offset>0)
{
currentOffset.y = currentOffset.y+offset;
[controller.tableView setContentOffset:currentOffset];
}
}
else
[controller.tableView reloadData];
AmitP answers, Swift 3 version
let beforeContentSize = self.tableView.contentSize
self.tableView.reloadData()
let afterContentSize = self.tableView.contentSize
let afterContentOffset = self.tableView.contentOffset
let newContentOffset = CGPoint(x: afterContentOffset.x, y: afterContentOffset.y + afterContentSize.height - beforeContentSize.height)
self.tableView.contentOffset = newContentOffset
How about using scrollToRowAtIndexPath:atScrollPosition:animated:? You should be able to just add an element to your data source, set the row with the above mentioned method and reload the table...
Related
I have a table view with a fairly complex cell. The cell includes a button that when pressed, should expand the cell and present some extra controls. I thought this would be pretty simple, so I wrote the following code (in my class derived from UITableViewCell):
self.extraView = [[MyView alloc] initWithFrame:CGRectMake(0,100,300,200)];
self.extraView.clipsToBounds = YES;
[self addSubview:self.extraView];
self.extraView.transform = CGAffineTransformMakeScale(1, 0.1f);
[UIView animationWithDuration:0.5 animations:^{
[tableView beginUpdates];
[tableView endUpdates];
self.extraView.transform = CGAffineTransformIdentity;
}];
Surprisingly, this makes extraView flash briefly on screen and then disappear. If I remove the calls to beginUpdates and endUpdates then extraView animates exactly as I expected. However, the table cell is not large enough to display it. I tried setting alpha to 0 and then fading it in during the table update, and that seems to work fine. Unfortunately, I am supposed to make the extraView grow in place and not fade.
I have played with various ways of modifying extraView such as changing the frame, but the table updates always produce some side effect. I also tried chaining the scale change in the completion handler, which of course didn't work either. I think that is because the table view is not done animating when the completion block is executed.
Is there any way to animate the frame of a view in a cell during a table update?
It appears that the answer is no. At least, I have not found anything that will look visually correct when modifying the view frame during the table view update. What I do instead now is wait a bit and then grow the view's frame. This is how the code looks:
extraView.alpha = 0; // hide the view immediately after adding it
[tableView beginUpdates];
[tableView endUpdates];
[UIView animateWithDuration:0.5 animations:^{
button.alpha = 0; // fade out old button
} completion:^(BOOL finished) {
extraView.alpha = 1; // reappear, but not fading in
extraView.transform = CGAffineTransformMakeScale(1, 0.01f);
[UIView animateWithDuration:0.5 animations:^{
extraView.transform = CGAffineTransformIdentity; // animate scale back to original size
}];
}];
This not exactly the effect I was trying to create, but it is the closest I have been able to get. I'm posting it as the answer in case anyone else encounters a similar issue.
I had a similar issue, and wanted to open / close and change specific elements in a cell by animation based on selection.
I did this by first of all creating a subclass for the uitableviewcell.
Each tableviewcell has an open and close method which does an animation of the backgroundcolor and the size (among other things).
The open and close methods of the tableviewcell look like this:
- (void)open {
[UIView animateWithDuration:0.3
animations:^{
[self.customBackgroundView setHeight:76];
self.deleteButton.alpha = 1;
self.selectedIndicator.transform = CGAffineTransformMakeRotation(M_PI);
[self.selectedIndicator setY:60];
}
];
}
- (void)close {
[UIView animateWithDuration:0.3
animations:^{
[self.customBackgroundView setHeight:40];
self.deleteButton.alpha = 0;
self.selectedIndicator.transform = CGAffineTransformMakeRotation(0);
[self.selectedIndicator setY:24];
}
];
}
It also has a setSelected method, so when the cell is selected it opens:
- (void)setSelected:(BOOL)selected animated:(BOOL)animated
{
[super setSelected:selected animated:animated];
if (selected) {
[self open];
} else {
[self close];
}
}
Now, only thing left, is to make sure to close other cells, when this cell is selected.
And call the tableview beginUpdates and tableview endUpdates:
- (void) tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
if (_currentSelection == indexPath.row) {
_currentSelection = -1;
SOColumnTableViewCell *cell = (SOColumnTableViewCell *)[tableView cellForRowAtIndexPath:indexPath];
[cell close];
} else {
_currentSelection = (int)indexPath.row;
}
[tableView beginUpdates];
[tableView endUpdates];
}
}
In our app the user is able in a intuitive way to scroll to next section in the tableview using some controls outside the tableview. Some sections contains many cells and scrolling animated does not look smooth because there is just too many cells to scroll by. For the sake of a simple and understood animation we want to temporarily remove the cells which are excessive for the animation.
Say the user is on
section.0 row.5 out of 100 rows
and he want to scroll to
section.1 row.0 out of 100 rows
Then we want to sort of skip all the excessive cells while scrolling animated. So we temporarily want to remove all cells between
e.g. section.0 row.10 untill section.0 row.98
Any ideas how I can get by this? I'm sure this could be usefull to others as well. I want to do this as clean as possible.
I have a few ideas on how you might be able to handle this. First is to reload the cells of interest, and return a lightweight cell. You might be able to use CGBitmapContext to copy the image data into a "facade" cell instead of the real one. Second would be to reload the data for the UITableView and then not return the data for the rows of interest. Third is to actually remove the rows. Another idea might be to disable interaction while you're animating.
Reload rows:
[self.tableView beginUpdates];
[self.tableView reloadRowsAtIndexPaths:[NSArray arrayWithObjects:indexPathOfYourCell, nil] withRowAnimation:UITableViewRowAnimationNone];
[self.tableView endUpdates];
Insert/Delete rows:
[tableView beginUpdate];
[tableView insertRowsAtIndexPaths:*arrayOfIndexPaths* withRowAnimation:*rowAnimation*];
[tableView endUpdate];
[tableView beginUpdate];
[tableView deleteRowsAtIndexPaths:*arrayOfIndexPaths* withRowAnimation:*rowAnimation*];
[tableView endUpdate];
Disable Interaction:
[UIApplication sharedApplication] beginIgnoringInteractionEvents];
This is an early attempt. I feel this is a bit messy..
Self is subclass of UITableView
- (void)scrollAndSkipCellsAnimatedToTopOfSection:(NSUInteger)section
{
CGRect sectionRect = [self rectForHeaderInSection:section];
CGPoint targetPoint = sectionRect.origin;
CGFloat yOffsetDiff = targetPoint.y - self.contentOffset.y;
BOOL willScrollUpwards = yOffsetDiff > 0;
if(willScrollUpwards)
{
[self scrollAndSkipCellsAnimatedUpwardsWithDistance:fabs(yOffsetDiff)];
}
else
{
[self scrollAndSkipCellsAnimatedDownwardsWithDistance:fabs(yOffsetDiff)];
}
}
- (void)scrollAndSkipCellsAnimatedUpwardsWithDistance:(CGFloat)distance
{
// when going upwards contentOffset should decrease
CGRect rectToRemove = CGRectMake(0,
self.contentOffset.y + (self.bounds.size.height * 1.5) - distance,
self.bounds.size.width,
distance - (self.bounds.size.height * 2.5));
BOOL shouldRemoveAnyCells = rectToRemove.size.height > 0;
if(shouldRemoveAnyCells)
{
// property on my subclass of uitableview
// these indexes may span over several sections
self.tempRemoveIndexPaths = [self indexPathsForRowsInRect:rectToRemove];
}
[UIView setAnimationsEnabled:NO];
[self beginUpdates];
[self deleteRowsAtIndexPaths:self.tempRemoveIndexPaths withRowAnimation:UITableViewRowAnimationNone];
[self endUpdates];
[UIView setAnimationsEnabled:YES];
[self setContentOffset:CGPointMake(0, self.contentOffset.y - distance) animated:YES];
}
// And then I would probably have to put some logic into
// - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section;
- (void)scrollAndSkipCellsAnimatedDownwardsWithDistance:(CGFloat)distance
{
}
I'm trying to mimic the iMessage bubble text behaviour with an UITableView. In order to always scroll to the bottom I'm using scrollToRowAtIndexPath when viewDidLoad and viewDidAppear. This is because when the viewDidLoad method is called, the table has not been completely loaded, so I need that extra scroll in viewDidAppear. This code makes the trick. However, what I want is not an animated scroll (setting animated to NO does not solve this), I want the table to be displayed always from the bottom, not load the table and then go to the last row.
Is this possible? I can't find any solution that fits completely with the desired behaviour.
This is the best solution!
Just reverse everything!
tableView.transform = CGAffineTransformMakeRotation(-M_PI);
cell.transform = CGAffineTransformMakeRotation(M_PI);
Swift 4.0:
tableView.transform = CGAffineTransform(rotationAngle: -CGFloat.pi)
cell.transform = CGAffineTransform(rotationAngle: CGFloat.pi)
Be careful though, because now the headerView and footerView positions are reversed as well.
You can avoid the call from viewDidLoad because scrolling from within viewDidAppear makes that first call redundant. viewDidAppear is called every time you navigate back to the view but viewDidLoad is only called once when the view is initialized.
I would agree with earlier suggestions of hiding the scroll from the user instead of changing the way a UITableView is loading data. My suggestion would be to use the scrollToRowAtIndexPath method in the viewWillAppear method with animation set to NO. After that if you have to add a new row while the table is visible to the user, use insertRowsAtIndexPaths:withRowAnimation: to add a row at the bottom of the table view. Be sure to take care of adding the data at the end of your data model so that when the user navigates away and comes back, s/he comes back to the same layout.
Hope this helps.
edit:
Just saw your reason for not accepting the previous answers and thought I'd elaborate a little more. The solution I propose would require minimum effort, avoid calling reloadData time and again and thus avoid calling the scrollToRowAtIndexPath method again and again. You only need to make one call to scrollToRowAtIndexPath in viewWillAppear to scroll to the bottom of the table view (hiding the transition from the user when doing so) and you wouldn't need to do that again.
I do something similar in an RPN calculator I've built. I have a table view with all the numbers in it and when a number is added to the stack, everything pops up one cell. When I load the view I call:
[self.myTableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:NumOfStackItems - 1 inSection:0]
atScrollPosition:UITableViewScrollPositionTop animated:NO];
In my viewWillAppear. This way my table view starts shown at the bottom of the stack and no animation is seen. By putting this in the viewWillAppear, every time I navigate to the view, it shows up at the bottom of the table.
When I add numbers to the stack, I just add it in an array that holds all the numbers and then put the text in the proper row like this:
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
// Cell initialization here...
NSUInteger row_num = [indexPath row];
cell.rowNumber.text = [NSString stringWithFormat:#"%g", [DataArray objectAtIndex:NumberOfStackItems-row_num-1];// subtract the row number off to get the correct array index
return cell
}
I also make sure that whenever I update the tableview with a new value i first call the reloadData function, and then call the scrollToRowAtIndexPath function I cited above, this way I stay at the bottom of the table.
You can have your UITableView hidden on viewDidLoad, and then change it to visible on viewDidAppear right after you scroll the table to the bottom. This way the user won't see the scrolling animation.
The solution is to override viewWillAppear and let it scroll (non-animated) to the bottom:
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[self goToBottom];
}
-(void)goToBottom
{
NSIndexPath *lastIndexPath = [self lastIndexPath];
[self.tableView scrollToRowAtIndexPath:lastIndexPath atScrollPosition:UITableViewScrollPositionBottom animated:NO];
}
-(NSIndexPath *)lastIndexPath
{
NSInteger lastSectionIndex = MAX(0, [self.tableView numberOfSections] - 1);
NSInteger lastRowIndex = MAX(0, [self.tableView numberOfRowsInSection:lastSectionIndex] - 1);
return [NSIndexPath indexPathForRow:lastRowIndex inSection:lastSectionIndex];
}
By performing this at viewWillAppear it will be done before the user sees the table.
You can fix it by making an invisible footer and do the calculations in there. When the footer is loaded the contentSize is updated. To make it scroll I check set the contentOffset of the tableview.
I have commented out the animation part, since you wanted it without, but it also works.
-(CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section
{
return 1;
}
-(UIView *)tableView:(UITableView *)tableView viewForFooterInSection:(NSInteger)section
{
if( tableView.contentOffset.y != tableView.contentSize.height - tableView.frame.size.height && automaticScroll ){
//[UIView animateWithDuration:0.0 animations:^{
self.contentTableView.contentOffset = CGPointMake(0, tableView.contentSize.height - self.contentTableView.frame.size.height);
//} completion:^(BOOL finished) {
[tableView reloadData];
//}];
automaticScroll = NO;
}
UIView *emptyFooter = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 320, 1)];
emptyFooter.backgroundColor = [UIColor clearColor];
return emptyFooter;
}
I created a BOOL automaticScroll to trigger the scroll to the bottom. This should be set in the viewWillAppear method, or whenever you load the data and reload the tableView.
If you want to add rows, you also need to set the BOOL, like:
-(void)addItemButtonClicked:(id)sender
{
automaticScroll = YES;
//Add object to data
[self.contentTableView reloadData];
}
If you need more help, please let me know.
scrollToRowAtIndexPath
use to scroll the row in tableview to particular position
just change content inset after load data to move content view of table view if height is less than parent view.
[self.tableView reloadData];
[self.tableView setContentInset:UIEdgeInsetsMake(self.view.frame.size.height - self.tableView.contentSize.height < 0 ? 0 : self.view.frame.size.height - self.tableView.contentSize.height, 0, 0, 0)];
Swift 3.1
tableView.transform = CGAffineTransform(rotationAngle: CGFloat.pi)
cell.transform = CGAffineTransform(rotationAngle: CGFloat.pi)
Credits: #Christos Hadjikyriacou
I want to select a table view row when the person reaches the center of the table view. Basically whenever the table reaches a point y I want the cell at that point to be selected.
I imagine it is similar to uiPickerView.
In your UITableViewController you should implement -scrollViewDidScroll: like this:
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
if (prevCell != nil)
self.prevCell.selected = NO;
CGPoint offset = self.tableView.contentOffset;
CGPoint point = offset;
point.y += self.tableView.center.y;
NSIndexPath *indexPath = [self.tableView indexPathForRowAtPoint:point];
UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:indexPath];
cell.selected = YES;
self.prevCell = cell;
}
This is tested and works. prevCell is a property of type UITableViewCell.
You must get the current offset of the scroll view for the table view and add this to the position of the cell you want (in this example, I take the cell in the center of the table view).
Then, using -indexPathForRowAtPoint: we grab the index path for the cell at the point and using -cellForRowAtIndexPath: we get the cell for the index path. Set it to selected and store it for later use (to deselect it on the next scroll).
have you tried
[[yourTableView cellForRowAtIndexPath:[yourTableView indexPathForRowAtPoint:wantPoint]] setSelected:true]
?
I think it can be achieved by getting all visible indexpaths, then data on visible indexpaths. The best answer you may see your own cz you know your problem well. Go through this class reference:
https://developer.apple.com/library/ios/#documentation/uikit/reference/UITableView_Class/Reference/Reference.html
As a little background, im building a rotated UITableView as a sort of sideways "picker" view. To do this, im taking a UITableView, appling a rotation transform to it, and then rotating again the UITableViewCells inside the tableview.
The problem im having is that some of the table cells become "misaligned" - their frame gets drawn at a certain distant offset (in both the x and y dimension) from the other table cells.
I've narrowed down that this bug occurs on the first table cell completely out of the visible tableview rect after a [tableView reloadData] call is made. (i.e. if I have 4 table cells, A which is completely visible and drawn, B which is half on/half off the view, and C and D which are completely off the screen and not yet rendered, when i scroll to C it is bugged, but when i scroll to D, it is not).
Now for some code -
the containing view's init
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
...
self.tableView = [[UITableView alloc] init];
[self addSubview:_tableView];
[_tableView setDelegate:self];
[_tableView setShowsVerticalScrollIndicator:NO];
[_tableView setSeparatorStyle:UITableViewCellSeparatorStyleNone];
CGAffineTransform transform = CGAffineTransformMakeRotation(-1.5707963);
_tableView.transform = transform;
_tableView.frame = self.bounds;
...
}
return self;
}
the relevant delegate methods
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
return [tableView.dataSource tableView:tableView cellForRowAtIndexPath:indexPath].frame.size.height;
}
- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
{
CGAffineTransform transform = CGAffineTransformMakeRotation(1.5707963);
cell.transform = transform;
}
the table cell's layoutSubviews
edit: I manually set the size of the cell (mainly the width) based on the length on the content of the cell
- (void)layoutSubviews {
[super layoutSubviews];
// some calculations and sizing of subviews
// height and width are swapped here, because the table cell will be rotated.
self.frame = (CGRect){self.frame.origin.x,self.frame.origin.y,calculatedHeight,calculatedWidth};
}
It would seem that the bugged tablecell's frame.origin is set incorrectly when it reaches layout subviews. Setting the frame's origin.x value to 0 fixes the x dimension offset problem, but obviously I can't do the same for the y dimension because this value determines the cell's position in the tableview.
Please let me know if there's some crucial info I might be leaving out. Thanks!
Have you tried to set the anchor point of the cells layer, which is the point the layer is rotated (transformed) about. It defaults to .5, .5 which is the centre of the layer, it may need to be set to 0, 0 (or 1, 1 - i can't remember if the layer coordinates are inverted off the top of my head)
or try setting the frame in willDisplayCell immediately after applying the transform instead of doing it in layout subviews
Good news - after spending many many previous hours trying to figure this out, I just stumbled upon the solution.
This code was getting called multiple times for the 'bugged' cell, and apparently due to some intricacies of CALayer and CGAffineTranform's, assigning the tranform had an additive affect.
- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
{
CGAffineTransform transform = CGAffineTransformMakeRotation(1.5707963);
cell.transform = transform;
}
The solution was to move the transform into the cell's init method, so that it is guaranteed to be set only once per cell.
// ... init stuff
self.transform = CGAffineTranformMakeRotation(1.5707963);
// ... more init stuff
Make a subclass of UITableViewCell and it its layoutSubviews override make sure to set the transform there:
class InvertedTableViewCell: UITableViewCell {
override func layoutSubviews() {
self.transform = CGAffineTranformMakeRotation(1.5707963)
}
}