简介
最近有个需求,在 tableView reloadData
结束后,需要滑动到底部,可是总是不能准确的滚动到底部。自然而言的就想到了三个方法,查看了一下有关的文档
- layoutSubviews
- setNeedsLayout
- layoutIfNeeded
layoutSubviews
Lays out subviews.
Discussion
Subclasses can override this method as needed to perform more precise layout of their subviews. You should override this method only if the autoresizing and constraint-based behaviors of the subviews do not offer the behavior you want. You can use your implementation to set the frame rectangles of your subviews directly.
You should not call this method directly. If you want to force a layout update, call the setNeedsLayout
method instead to do so prior to the next drawing update. If you want to update the layout of your views immediately, call the layoutIfNeeded
method.
在子类中,如果你需要更加准确的定位,你可以重写这个方法。如果 autoresizing 和 constraint不能为你提供你想要做的。你可以自己设置subviews的 frame。
你不应该直接调用这个方法,如果你想要强制更新布局,应该调用setNeedsLayout
,在下一次绘图更新之前,如果你想要立刻的更新布局,需要调用 layoutIfNeeded
setNeedsLayout
Invalidates the current layout of the receiver and triggers a layout update during the next update cycle.
Discussion
Call this method on your application’s main thread when you want to adjust the layout of a view’s subviews. This method makes a note of the request and returns immediately. Because this method does not force an immediate update, but instead waits for the next update cycle, you can use it to invalidate the layout of multiple views before any of those views are updated. This behavior allows you to consolidate all of your layout updates to one update cycle, which is usually better for performance.
你在想要更新 view 的 subviews 的布局的时候,可以在主线程调用此方法。此方法记录请求并且立即返回。因为这个方法不强制立即更新,而是等待下一个周期更新,你可以使用此方法来让很多 views 的布局失效,并且这样做可以把所有布局更新合并到一个更新周期,这通常会提高性能。
layoutIfNeeded
Lays out the subviews immediately, if layout updates are pending.
Discussion
Use this method to force the view to update its layout immediately. When using Auto Layout, the layout engine updates the position of views as needed to satisfy changes in constraints. Using the view that receives the message as the root view, this method lays out the view subtree starting at the root. If no layout updates are pending, this method exits without modifying the layout or calling any layout-related callbacks.
使用此方法强制视图立即更新其布局。使用“自动布局”时,布局引擎会根据需要更新视图的位置,以满足约束的更改,使用一个 view 作为 rootView ,此方法从根开始布局视图子树。如果没有约束需要更新,此方法会退出,不会修改 layout 或者调用和此 layout 有关的回调。
stackoverflow上的提问
在 stackoverflow上有查到一个问题, How to tell when UITableView has completed ReloadData?,提问的人想要滚动 tableView 到底部,发现调用直接 reloadData,然后滚动没有滚动到最底部,提出了这个问题。 这个下面有很多很多的答案,具体的如下:
1.调用layoutIfNeeded
强制的刷新布局,接着执行scrollToRowAtIndexPath,滚动到底部,确实是帮助我们解决了这个问题。
[self.tableView reloadData];
[self.tableView layoutIfNeeded];
NSIndexPath* indexPath = [NSIndexPath indexPathForRow: ([self.tableView numberOfRowsInSection:([self.tableView numberOfSections]-1)]-1) inSection: ([self.tableView numberOfSections]-1)];
[self.tableView scrollToRowAtIndexPath:indexPath atScrollPosition:UITableViewScrollPositionBottom animated:YES];
原因:
The reload happens during the next layout pass, which normally happens when you return control to the run loop (after, say, your button action or whatever returns).
So one way to run something after the table view reloads is simply to force the table view to perform layout immediately
重新加载发生在下一个布局过程中,这通常发生在将控制返回到运行循环时(例如,您的按钮操作或任何返回之后)。
所以,在tableView reloads 后可以立刻强制更新布局。
2.调用dispatch_async(dispatch_get_main_queue(), ^{})
[self.tableView reloadData];
dispatch_async(dispatch_get_main_queue(), ^{
NSIndexPath* indexPath = [NSIndexPath indexPathForRow: ([self.tableView numberOfRowsInSection:([self.tableView numberOfSections]-1)]-1) inSection:([self.tableView numberOfSections]-1)];
[self.tableView scrollToRowAtIndexPath:indexPath atScrollPosition:UITableViewScrollPositionBottom animated:YES];
});
原因
depending on how big your table data source is, you can animate going to the bottom of the tableview in the same run loop. If you try your test code with a huge table, your trick of using GCD to delay scrolling until the next run loop will work, whereas immediately scrolling will fail. But anyways, thanks for this trick!
但是这种方式并不总是能解决问题,使用GCD来延迟滑动到下一个 runloop,但是立即滑动的话,就会出错。
总结
如果你的 tableView 在 reloadData
后,无法滑动到底部,那么就可以这样做,在reloadData
后,调用 layoutIfNeeded
,来强制进行布局,然后在进行 tableView的滑动就可以了。
[self.tableView reloadData];
[self.tableView layoutIfNeeded];
//do somthing you want