I would like to completely reset the scroll position of a UITableView, so that every time I open it, it is displaying the top-most items. In other words, I would like to scroll the table view to the top every time it is opened.
I tried using the following piece of code, but it looks like I misunderstood the documentation:
- (void)viewWillAppear:(BOOL)animated {
[tableView scrollToNearestSelectedRowAtScrollPosition:UITableViewScrollPositionTop animated:NO];
}
Is this the wrong approach here?
August got the UITableView-specific method. Another way to do it is:
[tableView scrollRectToVisible:CGRectMake(0, 0, 1, 1) animated:NO];
This method is defined in UIScrollView, the parent class to UITableView. The above example tells it to scroll to the 1x1 box at 0,0 - the top left corner, in other words.
The method you're using scrolls to (as the method name implies) the nearest selected row. In many cases, this won't be the top row. Instead, you want to use either
scrollToRowAtIndexPath:atScrollPosition:animated:
or
selectRowAtIndexPath:animated:scrollPosition:
where you use the index path of the row you want to scroll to. The second method actually selects a row, the first method simply scrolls to it.
What I ended up doing was this:
Swift 4
self.tableView.contentOffset = CGPoint.zero
DispatchQueue.main.async {
self.tableView.reloadData()
}
Swift:
self.tableView.scrollRectToVisible(CGRect.zero, animated: false)
Related
I am using a custom uitableviewcell, which is written in Objective C (everything else I have is written in Swift) for the purpose of having easy customer slide buttons. I have a tableview which uses these cells, and I want it to update even when one of the slide buttons (think slide to delete button) is showing. This is my code here:
dispatch_async(dispatch_get_global_queue(priority, 0)) {
// do some task
dispatch_sync(dispatch_get_main_queue()) {
self.tasksTableView.reloadData()
self.tasksTableView.cellForRowAtIndexPath(NSIndexPath(forRow: searchForTaskByName(taskSlices[selectedSlice].name), inSection: 0))!.contentView.addSubview(self.view1)
(self.tasksTableView.cellForRowAtIndexPath(NSIndexPath(forRow: searchForTaskByName(taskSlices[selectedSlice].name), inSection: 0)) as! SESlideTableViewCell).setSlideState(SESlideTableViewCellSlideState.Left, animated: true)
})
})
Now, the subview I am adding is a rectangle that originally covers the cell, but over time shrinks (kind of like a reverse loading bar). The third line is part of the custom code:
[UIView animateWithDuration: 0.0 delay: 0.0 options:UIViewAnimationOptionCurveLinear animations:^{
//code with animation
CGRect frame = snapshotView.frame;
frame.origin.x = targetPositionX;
snapshotView.frame = frame;
m_slideState = slideState;
//[m_leftButtonGroupView layoutIfNeeded];
//[m_rightButtonGroupView layoutIfNeeded];
} completion:^(BOOL finished){
if (finished) {
if (animationId != m_slideAnimationId) {
return;
}
m_slideState = slideState;
if (slideState == SESlideTableViewCellSlideStateCenter) {
m_preparedSlideStates = SESlideStateOptionNone;
isMoved1 = NO;
[self cleanUpSlideView];
}
}
}];
This moves the cell to keep the button in view. Now, the problem is, the tableview has to reload to show the changes (update subviews??) and this causes the cell to shift back to starting position, then the cell is shifted back to button displaying position. This causes random flashes of the cell. I have already tried reloadCellsAtIndexPaths, same deal. I have tried to hide and unhide around the animations; using different threads; dispatch_sync and dispatch_async; removing the table from superview, updating, and putting back; UIView.animationsSetEnable(false) blocks; deleteCellsAtIndexPaths and insertCellsAtIndexPaths, NOTHING will work. This is reproducible on my iPhone 5 and simulator.
What I really need is to figure out what reloadData does so I can call only the bit I need, and hopefully not animate anything. All my animations used have time 0.0 and no delay. I am running a small counting up algorithm in the background, which is variable++ once every .01 seconds, if this matters. Thank you for any help you can give me, I have been working on this for two days now (I am fairly new to iOS development and I wanted to do my research before asking questions).
dispatch_sync(dispatch_get_main_queue()) { } is the source of your problem change it to dispatch_async(dispatch_get_main_queue()) { // reload table view } and your good to go
Well, I figured out a REALLY hacky fix. I created a duplicate UITableView, which I placed under the main one. I set the main one to get removed from superview and then put back with the reloadData call and other updates between these two calls. This would reveal the second tableview for a split second, and then 0.5 seconds after the first update I would update the second tableview to keep it current. This actually works. I can't believe it works and I am flabbergasted that it is necessary. I am still definitely looking for a legitimate solution and so I will not be accepting my answer for a while, in case someone can help me out.
[self.tableView registerNib:[UINib nibWithNibName:#"CellXibName" bundle:nil] forCellReuseIdentifier:CELL_IDENTIFIER];
I have the flash problem.I finally find that the cell are not reuse when table reload.
I have some query block when I set each cell, The cell always recreate make it flash.
I am using scrollToRowAtIndexPath: to scroll UITableView to top. Then I handle the animation in scrollViewDidEndScrollingAnimation:. After it scrolls, it should perform an action. My problem is, that when it doesn't scroll - e.g. the table view is already on top before I call the method - the action doesn't get called in scrollViewDidEndScrollingAnimation:. How do I find out whether the UITableView is already on top?
if (yourTableView.contentOffset.y > 0)
{
// yourTableView is not on top.
}
else
{
// yourTableView is already on top.
}
Use UITableView contentOffset property. If contentOffset.y is 0 then table is at top else it is not.
Hope it helps you.
You need to use the UIScrollViewDelegate function - (void)scrollViewDidEndDecelerating:(UIScrollView *)aScrollView and work out if the content offset is set to 0 on the y axis for that scrollview. If its 0 then you have reached the top.
while you're scrolling up tableView(_:cellForRowAt) gets called to display rows and when the indexPath.row == 0 that's when you're at the top of the tableview
I am just wondering whether or not it is possible to shift a UITableView down the page by, say, maybe 50 pixels. I know this would usually work if I had used a UIViewController then added a table view on top, but would I be able to do this and still keep it as UITableViewController?
I had the same problem and the answer above didn't work. This did:
[self.tableView setContentInset:UIEdgeInsetsMake(50,0,0,0)];
My solution is to override tableViewcontroller's method viewWillLayoutSubviews
- (void) viewWillLayoutSubviews
{
[super viewWillLayoutSubviews];
self.tableView.frame = CGRectMake(0,0,CGRectGetWidth(self.view.frame),300);
}
Works great and always for me with changing orientations and in all situations
A UITableView is actually a UIScrollView. This means that you can scroll the UITableView to the point you want. This is a previous link which shows you how to do this, including sample code and discussion.
Edit: In order to shift the WHOLE tableview down, just use:
float yOffset = 50.0f; // Change this how much you want!
tableview.view.frame = CGRectMake(tableview.view.frame.origin.x, tableview.view.frame.origin.y + yOffset, tableview.view.frame.size.width, tableview.view.frame.size.height);
Hope that Helps!
Since a Table View is backed by a UIScrollView you can move in around using the content Offset.
self.tableView.contentOffset = CGPointMake( x, y);
You might want to wrap in a UIView animation
If you are trying to add a UI element at the top of the table, why not just set it to the tableHeaderView instead?
UILabel *someLabel;
// configure label
self.tableView.tableHeaderView = someLabel;
If you need a view behind (or on top of) the tableview, then you'll have to subclass UIViewController instead and add a UITableView afterwards.
Another solution could be to set the table's header view (reference) but in this case, keep in mind that this view will scroll together with the table.
More information about the limitations of UITableViewController in this article: "Clean table view code".
Swift 2.2:
To shift the tableView inside a UITableViewController down:
let edgeInsets = UIEdgeInsetsMake(20, 0, 0, 0)
self.tableView.contentInset = edgeInsets
UITableViewController is actually a UIViewController, only plus is it gives you some methods to override and useful for table actions. so you can do whatever you want
check this, once you get the idea of what UITableViewController actully is you will do whatever you want
http://cocoawithlove.com/2009/03/recreating-uitableviewcontroller-to.html
I want to scroll down to given row in my table view.
if I use following code inside a button event it works correctly.
[planTableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:7 inSection:0 ] atScrollPosition:0 animated:YES];
But I want to do it just after the page load. Placing above code inside viewDidLoad or viewDidAppear did not work.
Any help??
it's kinda hack, but you also might want to perform the -scrollToRowAtIndexPath:atScrollPosition:animated: with some delay, because when either -viewDidLoad or -viewDidAppear: is called, table view rows haven't been created yet so there's nothing to scroll to.
so:
- (void)doScrolling
{
[planTableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:7 inSection:0 ] atScrollPosition:0 animated:YES];
}
and in -viewDidAppear:
[self performSelector:#selector(doScrolling) withObject:nil afterDelay:0.3];
also pay attention to atScrollPosition: argument. it's enum actually:
typedef enum {
UITableViewScrollPositionNone,
UITableViewScrollPositionTop,
UITableViewScrollPositionMiddle,
UITableViewScrollPositionBottom
} UITableViewScrollPosition;
so if the row is visible and 0 is passed, no scrolling will be performed
UITableView is a subclass of UIScrollView, so you can just set the contentOffset of your tableView in viewDidLoad: or viewWillAppear.
If you need to scroll 7 table view cells down in length, calculate the height of these 7 cells (the default is 44px, but if you have custom cell heights you need to factor that in) and set the contentOffset to CGPointMake(0, *calculated height*). In the case of 7 44px height cells, it would be CGPointMake(0, 308).
I have a UITableView that needs to introduce new content from the bottom. This is how a table view behaves when the view is full and you add new rows with animation.
I was starting it by altering the contentInset as rows are introduced but then when they change things go off, and the entry animation is all wrong... My problem with this approach is compounded by the fact that users can delete rows, and the row contents update, causing them to resize (each row has it's own height which changes).
Any recommendations on how to get a UITableView rows to always appear at the bottom of the UITableView's view space?
I've got a solution that works for me perfectly, but it causes a bunch of double thinking so it's not as simple in theory as it is in practice... kinda...
Step 1, apply a transform to the table view rotating it 180deg
tableView.transform = CGAffineTransformMakeRotation(-M_PI);
Step 2, rotate your raw cell 180deg in tableView:cellForRowAtIndexPath:
cell.transform = CGAffineTransformMakeRotation(M_PI);
Step 3, reverse your datasource. If you're using an NSMutableArray insert new objects at location 0 instead of using AddObject...
Now, the hard part is remembering that left is right and right is left only at the table level, so if you use
[tableView insertRowsAtIndexPaths:targetPath withRowAnimation:UITableViewRowAnimationLeft]
it now has to be
[tableView insertRowsAtIndexPaths:targetPath withRowAnimation:UITableViewRowAnimationRight]
and same for deletes, etc.
Depending on what your data store is you may have to handle that in reverse order as well...
Note: rotate the cells OPPOSITE the table, otherwise floating point innacuracy might cause the transform to get off perfect and you'll get crawlies on some graphics from time to time as you scroll... minor but annoying.
The accepted method introduces issues for my app - the scroll bar is on wrong side, and it mangles cell separators for UITableViewStyleGrouped
To fix this use the following
tableView.transform = CGAffineTransformMakeScale (1,-1);
and
cell.contentView.transform = CGAffineTransformMakeScale (1,-1);
// if you have an accessory view
cell.accessoryView.transform = CGAffineTransformMakeScale (1,-1);
Similar approach to ima747, but rotating 180 degrees also makes the scrolling indicator go to the opposite side. Instead I flipped the table view and its cells vertically.
self.tableView.transform = CGAffineTransformMakeScale(1, -1); //in viewDidLoad
cell.transform = CGAffineTransformMakeScale(1, -1);//in tableView:cellForRowAtIndexPath:
Create a table header that is the height of the screen (in whatever orientation you are in) LESS the height of the of rows you have that you want visible. If there are no rows, then the header is the full height of the table view. As rows are added, simultaneously reduce the height of the table header by the height of the new row. This means changing the height of the frame of the view you provide for the table header. The point is to fill the space above the table rows to give the appearance that the rows are entering from the bottom. Using a table header (or section header) pushes the table data down. You can put whatever you like in the header view, even have it blank and transparent if you like.
This should have the effect you are looking for, I think.
Look at the attribute tableHeaderView. You simply set this to the view you want displayed in the table header. Then you can manipulate it as needed as you add rows. I can't recall just how forceful you then need to be to get the view to actually update in the UI. Might be as simple as calling setNeedsDisplay, if anything.
Alternatively, look at the methods tableView:viewForHeaderInSection: and tableView:heightForHeaderInSection:. Similar to using a table header view, you would want to have an instance variable that you setup once but that you can access from these methods to return either the view itself or its height, respectively. When you need to change the view for the (first) section, you can use reloadSections:withAnimation: to force an update to the view on screen after you have changed the views height (or content).
Any of that make sense? I hope so. :-)
Swift 3.01 - Other solution can be, rotate and flip the table view. Works very well for me and not mess with the animation and is less work for the reload data on the table view.
self.tableView.transform = CGAffineTransform.init(rotationAngle: (-(CGFloat)(Double.pi)))
self.tableView.transform = CGAffineTransform.init(translationX: -view.frame.width, y: view.frame.height)
I just wanted to add something to all of these answers regarding the use of this technique with UICollectionView... Sometimes when invalidating the layout, my cells would get transformed back the wrong way, I found that in the UICollectionViewCell and UICollectionReusableView subclasses I had to do this:
- (void)applyLayoutAttributes:(UICollectionViewLayoutAttributes *)layoutAttributes {
[super applyLayoutAttributes:layoutAttributes];
[self setTransform:CGAffineTransformMakeScale(1, -1)];
}
dataSourceArray = dataSourceArray.reversed()
tableView.transform = CGAffineTransform(scaleX: 1, y: -1)
cell.transform = CGAffineTransform(scaleX: 1, y: -1)
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
if let text = textField.text {
dataSourceArray.insert(text, at: 0)
self.tableView.reloadData()
textField.text = ""
}
textField.resignFirstResponder()
return true
}
An easier way is to add the following lines at the bottom of cellForRowAtIndexPath
if(indexPath.section == self.noOfSections - 1)
[self scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:([self numberOfRowsInSection:self.noOfSections -1] - 1) inSection:self.noOfSections -1] atScrollPosition:UITableViewScrollPositionBottom animated:animated];
Late to the party but, inspired by #Sameh Youssef's idea, a function to scroll to the last cell in the tableview, assuming you only have one section. If not, just return the number of sections instead of hardcoding the 0.
The microsecond delay was arbitrarily chosen.
func scrollToLast() {
DispatchQueue.main.asyncAfter(deadline: .now() + .microseconds(5)) {
let lastIndex = IndexPath(row: self.tableView.numberOfRows(inSection: 0) - 1, section: 0)
if lastIndex.row != -1 {
self.tableView.scrollToRow(at: lastIndex, at: .bottom, animated: false)
}
}
}
I would recommend to use the approach described in this blog post.
Have checked it on iOS 12 and 13. Works perfectly.
Well, if you load your tableview with an NSMutableArray i would suggest you to sort out the array in the inverse order. So the table view will be filled up like you want.