ロードが終了したときにテーブルのオフセットを変更したいのですが、そのオフセットはテーブルにロードされたセルの数に依存します。
とにかくSDKで、uitableviewの読み込みがいつ終了したかを知ることはありますか?デリゲートにもデータソースプロトコルにも何も表示されません。
表示されているセルのみを読み込んでいるため、データソースの数を使用できません。
ロードが終了したときにテーブルのオフセットを変更したいのですが、そのオフセットはテーブルにロードされたセルの数に依存します。
とにかくSDKで、uitableviewの読み込みがいつ終了したかを知ることはありますか?デリゲートにもデータソースプロトコルにも何も表示されません。
表示されているセルのみを読み込んでいるため、データソースの数を使用できません。
回答:
@RichXの回答を改善:
lastRow
両方[tableView numberOfRowsInSection: 0] - 1
またはにすることができます((NSIndexPath*)[[tableView indexPathsForVisibleRows] lastObject]).row
。したがって、コードは次のようになります。
-(void) tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
{
if([indexPath row] == ((NSIndexPath*)[[tableView indexPathsForVisibleRows] lastObject]).row){
//end of loading
//for example [activityIndicator stopAnimating];
}
}
更新:
まあ、@ htafoyaのコメントは正しいです。このコードでソースからのすべてのデータのロードの終了を検出したい場合は検出されませんが、それは元の問題ではありません。このコードは、表示されるはずのすべてのセルがいつ表示されるかを検出するためのものです。willDisplayCell:
ここでは、UIをよりスムーズにするために使用します(通常、単一セルはwillDisplay:
呼び出し後に高速に表示されます)。で試すこともできtableView:didEndDisplayingCell:
ます。
viewForFooterInSection
メソッドに適用できます。
Swift 3&4&5バージョン:
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
if let lastVisibleIndexPath = tableView.indexPathsForVisibleRows?.last {
if indexPath == lastVisibleIndexPath {
// do here...
}
}
}
私は常にこの非常にシンプルなソリューションを使用しています:
-(void) tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
{
if([indexPath row] == lastRow){
//end of loading
//for example [activityIndicator stopAnimating];
}
}
[tableView numberOfRowsInSection: 0] - 1
です。0
必要な値に置き換える必要があります。しかし、それは問題ではありません。問題は、UITableViewが表示のみをロードすることです。しかし((NSIndexPath*)[[tableView indexPathsForVisibleRows] lastObject]).row
、問題を解決します。
これは私にとってうまくいくように見える別のオプションです。viewForFooterデリゲートメソッドで、それが最後のセクションかどうかを確認し、そこにコードを追加します。willDisplayCellはフッターを持っている場合、フッターを考慮しないことに気づいた後、このアプローチが思い浮かびました。
- (UIView *)tableView:(UITableView *)tableView viewForFooterInSection:(NSInteger)section
{
// Perform some final layout updates
if (section == ([tableView numberOfSections] - 1)) {
[self tableViewWillFinishLoading:tableView];
}
// Return nil, or whatever view you were going to return for the footer
return nil;
}
- (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section
{
// Return 0, or the height for your footer view
return 0.0;
}
- (void)tableViewWillFinishLoading:(UITableView *)tableView
{
NSLog(@"finished loading");
}
この方法UITableView
は、表示されているセルだけでなく、全体の最終読み込みを検索する場合に最適です。必要に応じて、表示されているセルのみが必要な場合があります。その場合、フォレックスの回答が適切なルートです。
nil
をtableView:viewForFooterInSection:
めちゃくちゃにしてから戻る
return CGRectMake(0,0,0,0);
self.tableView.sectionFooterHeight = 0
。どちらの方法でも、高さが約10のセクションフッタービューが挿入されているようです。返すビューに高さ0の制約を追加することで、これを修正できると思います。とにかく、最後のセルでUITableViewを開始する方法を実際に理解したかったが、最初にこれを確認したので、私は元気です。
Swift 2ソリューション:
// willDisplay function
override func tableView(tableView: UITableView, willDisplayCell cell: UITableViewCell, forRowAtIndexPath indexPath: NSIndexPath) {
let lastRowIndex = tableView.numberOfRowsInSection(0)
if indexPath.row == lastRowIndex - 1 {
fetchNewDataFromServer()
}
}
// data fetcher function
func fetchNewDataFromServer() {
if(!loading && !allDataFetched) {
// call beginUpdates before multiple rows insert operation
tableView.beginUpdates()
// for loop
// insertRowsAtIndexPaths
tableView.endUpdates()
}
}
プライベートAPIの使用:
@objc func tableViewDidFinishReload(_ tableView: UITableView) {
print(#function)
cellsAreLoaded = true
}
パブリックAPIの使用:
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
// cancel the perform request if there is another section
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(tableViewDidLoadRows:) object:tableView];
// create a perform request to call the didLoadRows method on the next event loop.
[self performSelector:@selector(tableViewDidLoadRows:) withObject:tableView afterDelay:0];
return [self.myDataSource numberOfRowsInSection:section];
}
// called after the rows in the last section is loaded
-(void)tableViewDidLoadRows:(UITableView*)tableView{
self.cellsAreLoaded = YES;
}
考えられるより良い設計は、表示されているセルをセットに追加することです。その後、テーブルがロードされているかどうかを確認する必要がある場合は、代わりにこのセットのforループを実行できます。
var visibleCells = Set<UITableViewCell>()
override func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
visibleCells.insert(cell)
}
override func tableView(_ tableView: UITableView, didEndDisplaying cell: UITableViewCell, forRowAt indexPath: IndexPath) {
visibleCells.remove(cell)
}
// example property you want to show on a cell that you only want to update the cell after the table is loaded. cellForRow also calls configure too for the initial state.
var count = 5 {
didSet {
for cell in visibleCells {
configureCell(cell)
}
}
}
Swift 3で選択した回答バージョンの場合:
var isLoadingTableView = true
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
if tableData.count > 0 && isLoadingTableView {
if let indexPathsForVisibleRows = tableView.indexPathsForVisibleRows, let lastIndexPath = indexPathsForVisibleRows.last, lastIndexPath.row == indexPath.row {
isLoadingTableView = false
//do something after table is done loading
}
}
}
デフォルトのセルを選択する前にテーブルの読み込みが完了していることを確認したかったので、isLoadingTableView変数が必要でした。これを含めないと、テーブルをスクロールするたびにコードが再度呼び出されます。
私が知っている最善のアプローチは、Ericの回答です。UITableViewがデータの要求を完了したときに通知を受けますか?
更新:それを機能させるには、これらの呼び出しを入れなければなりません -tableView:cellForRowAtIndexPath:
[tableView beginUpdates];
[tableView endUpdates];
テーブルビューがコンテンツの読み込みを完了するタイミングを知るには、まず、ビューが画面にどのように配置されるかを基本的に理解する必要があります。
アプリのライフサイクルには、4つの重要な瞬間があります。
2回と3回は完全に分かれています。どうして ?パフォーマンス上の理由から、変更が行われるたびに瞬間3のすべての計算を実行することは望ましくありません。
だから、私はあなたがこのようなケースに直面していると思います:
tableView.reloadData()
tableView.visibleCells.count // wrong count oO
ここで何が問題になっていますか?
他のビューと同様に、テーブルビューはコンテンツを遅延して再読み込みします。実際、reloadData
複数回呼び出してもパフォーマンスの問題は発生しません。テーブルビューは、デリゲートの実装に基づいてコンテンツサイズのみを再計算し、セルが読み込まれるまで3待機します。今回はレイアウトパスと呼ばれます。
さて、どのようにレイアウトパスに入るのですか?
レイアウトパス中に、アプリはビュー階層のすべてのフレームを計算します。関与取得するには、専用のメソッドをオーバーライドすることができlayoutSubviews
、updateLayoutConstraints
などでA UIView
とビューコントローラのサブクラスで同等の方法。
これがまさにテーブルビューの機能です。これはオーバーライドlayoutSubviews
し、デリゲートの実装に基づいてセルを追加または削除します。cellForRow
新しいセルを追加してレイアウトする直前、直後に呼び出しますwillDisplay
。reloadData
階層にテーブルビューを呼び出すか追加した場合、テーブルビューは、この重要な瞬間にフレームを埋めるために必要な数のセルを追加します。
よし、でも今は、テーブルビューがコンテンツの再読み込みをいつ終了したかを知る方法は?
これで、この質問を言い換えることができます。テーブルビューがサブビューのレイアウトを終了したときを知る方法は?
• 最も簡単な方法は、テーブルビューのレイアウトを表示することです。
class MyTableView: UITableView {
func layoutSubviews() {
super.layoutSubviews()
// the displayed cells are loaded
}
}
このメソッドは、テーブルビューのライフサイクルで何度も呼び出されることに注意してください。テーブルビューのスクロールとデキューの動作により、セルは頻繁に変更、削除、追加されます。しかし、それはsuper.layoutSubviews()
、セルがロードされた直後に機能します。このソリューションはwillDisplay
、最後のインデックスパスのイベントを待機することと同じです。このイベントは、layoutSubviews
追加された各セルのテーブルビューの実行中に呼び出されます。
• もう1つの方法は、アプリがレイアウトパスを終了したときに呼び出されるようにすることです。
ドキュメントで説明されているように、次のオプションを使用できますUIView.animate(withDuration:completion)
。
tableView.reloadData()
UIView.animate(withDuration: 0) {
// layout done
}
このソリューションは機能しますが、レイアウトが完了してからブロックが呼び出されるまでの間に画面が1回更新されます。これはDispatchMain.async
ソリューションと同等ですが、指定されています。
• または、テーブルビューのレイアウトを強制したい
任意のビューにそのサブビューフレームを即座に計算させる専用の方法がありますlayoutIfNeeded
。
tableView.reloadData()
table.layoutIfNeeded()
// layout done
ただし、注意してください。そうすると、システムで使用されている遅延読み込みが削除されます。これらのメソッドを繰り返し呼び出すと、パフォーマンスの問題が発生する可能性があります。テーブルビューのフレームが計算される前にそれらが呼び出されないことを確認してください。呼び出されない場合、テーブルビューが再度読み込まれ、通知されません。
完璧な解決策はないと思います。クラスをサブクラス化すると、問題が発生する可能性があります。レイアウトパスは上から始まり、下に行くため、すべてのレイアウトが完了したときに通知を受け取るのは簡単ではありません。そしてlayoutIfNeeded()
パフォーマンスの問題などを作成することができます。しかし、それらのオプションを知って、あなたはあなたのニーズに適合します一つの代替で考えることができるはずです。
Swift 3での実行方法は次のとおりです。
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if indexPath.row == 0 {
// perform your logic here, for the first row in the table
}
// ....
}
ここに私がSwift 3でそれを行う方法があります
let threshold: CGFloat = 76.0 // threshold from bottom of tableView
internal func scrollViewDidScroll(_ scrollView: UIScrollView) {
let contentOffset = scrollView.contentOffset.y
let maximumOffset = scrollView.contentSize.height - scrollView.frame.size.height;
if (!isLoadingMore) && (maximumOffset - contentOffset <= threshold) {
self.loadVideosList()
}
}
これが私がすることです。
あなたの基本クラス(rootVC BaseVcなどにすることができます)では、
A.「DidFinishReloading」コールバックを送信するプロトコルを記述します。
@protocol ReloadComplition <NSObject>
@required
- (void)didEndReloading:(UITableView *)tableView;
@end
B.テーブルビューをリロードする汎用メソッドを記述します。
-(void)reloadTableView:(UITableView *)tableView withOwner:(UIViewController *)aViewController;
基本クラスメソッドの実装では、reloadDataを呼び出し、その後にdelegateMethodを遅延して呼び出します。
-(void)reloadTableView:(UITableView *)tableView withOwner:(UIViewController *)aViewController{
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
[tableView reloadData];
if(aViewController && [aViewController respondsToSelector:@selector(didEndReloading:)]){
[aViewController performSelector:@selector(didEndReloading:) withObject:tableView afterDelay:0];
}
}];
}
コールバックが必要なすべてのView Controllerのリロード完了プロトコルを確認します。
-(void)didEndReloading:(UITableView *)tableView{
//do your stuff.
}
リファレンス:https : //discussions.apple.com/thread/2598339?start=0&tstart=0
@folexの答えは正しいです。
ただし、tableViewに一度に複数のセクションが表示されている場合は失敗します。
-(void) tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
{
if([indexPath isEqual:((NSIndexPath*)[[tableView indexPathsForVisibleRows] lastObject])]){
//end of loading
}
}
私はこれが答えられていることを知っています、私は単に推奨事項を追加しています。
次のドキュメントに従って
https://www.objc.io/issues/2-concurrency/thread-safe-class-design/
dispatch_asyncでのタイミングの問題を修正することは悪い考えです。FLAGなどを追加してこれを処理することをお勧めします。
複数のセクションがある場合、最後のセクションの最後の行を取得する方法は次のとおりです(Swift 3):
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
if let visibleRows = tableView.indexPathsForVisibleRows, let lastRow = visibleRows.last?.row, let lastSection = visibleRows.map({$0.section}).last {
if indexPath.row == lastRow && indexPath.section == lastSection {
// Finished loading visible rows
}
}
}
Andrewのコードをコピーして、テーブルに1行しかない場合を考慮してコードを拡張しています。これまでのところ、うまくいきます。
- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath {
// detect when all visible cells have been loaded and displayed
// NOTE: iOS7 workaround used - see: http://stackoverflow.com/questions/4163579/how-to-detect-the-end-of-loading-of-uitableview?lq=1
NSArray *visibleRows = [tableView indexPathsForVisibleRows];
NSIndexPath *lastVisibleCellIndexPath = [visibleRows lastObject];
BOOL isPreviousCallForPreviousCell = self.previousDisplayedIndexPath.row + 1 == lastVisibleCellIndexPath.row;
BOOL isLastCell = [indexPath isEqual:lastVisibleCellIndexPath];
BOOL isFinishedLoadingTableView = isLastCell && ([tableView numberOfRowsInSection:0] == 1 || isPreviousCallForPreviousCell);
self.previousDisplayedIndexPath = indexPath;
if (isFinishedLoadingTableView) {
[self hideSpinner];
}
}
注:私はAndrewのコードの1つのセクションを使用しているだけなので、覚えておいてください。
iOS7.0xでは、ソリューションは少し異なります。これが私が思いついたものです。
- (void)tableView:(UITableView *)tableView
willDisplayCell:(UITableViewCell *)cell
forRowAtIndexPath:(NSIndexPath *)indexPath
{
BOOL isFinishedLoadingTableView = [self isFinishedLoadingTableView:tableView
indexPath:indexPath];
if (isFinishedLoadingTableView) {
NSLog(@"end loading");
}
}
- (BOOL)isFinishedLoadingTableView:(UITableView *)tableView
indexPath:(NSIndexPath *)indexPath
{
// The reason we cannot just look for the last row is because
// in iOS7.0x the last row is updated before
// looping through all the visible rows in ascending order
// including the last row again. Strange but true.
NSArray * visibleRows = [tableView indexPathsForVisibleRows]; // did verify sorted ascending via logging
NSIndexPath *lastVisibleCellIndexPath = [visibleRows lastObject];
// For tableviews with multiple sections this will be more complicated.
BOOL isPreviousCallForPreviousCell =
self.previousDisplayedIndexPath.row + 1 == lastVisibleCellIndexPath.row;
BOOL isLastCell = [indexPath isEqual:lastVisibleCellIndexPath];
BOOL isFinishedLoadingTableView = isLastCell && isPreviousCallForPreviousCell;
self.previousDisplayedIndexPath = indexPath;
return isFinishedLoadingTableView;
}
目的C
[self.tableView reloadData];
[self.tableView performBatchUpdates:^{}
completion:^(BOOL finished) {
/// table-view finished reload
}];
迅速
self.tableView?.reloadData()
self.tableView?.performBatchUpdates({ () -> Void in
}, completion: { (Bool finished) -> Void in
/// table-view finished reload
})
UICollectionView
、私が理解している範囲でのみ使用できます。