UITableView
がデータソースからのデータの要求を終了したことを確認する方法はありますか?
関連付けられたビューコントローラー(viewDidLoad
)のviewWillAppear
/viewDidAppear
/UITableViewController
メソッドはどれも役に立たないため、すべてが早すぎるため起動します。それらのどれも(完全に理解できるほど)データソースへのクエリが当面(たとえば、ビューがスクロールされるまで)終了することを保証しません。
私が見つけた回避策の1つは、reloadData
が返されたときに、テーブルビューisのクエリの完了が保証されているため、viewDidAppear
でreloadData
を呼び出すことです。とりあえず必要なだけのデータソース。
ただし、最初にロードされたときにデータソースに同じ情報を2回(自動的に1回、reloadData
呼び出しのために1回)求められるため、これはかなり厄介に思えます。
私がこれをしたい理由は、UITableView
のスクロール位置を保持したいからです-もっとも近い行だけでなく、ピクセルレベルまで下ろしたいからです。
スクロール位置を復元するとき(scrollRectToVisible:animated:
)、Table Viewに十分なデータが既に含まれているか、scrollRectToVisible:animated:
メソッド呼び出しは何も行いません(viewDidLoad
、viewWillAppear
、またはviewDidAppear
のいずれかで独自に呼び出しを行った場合に発生します)。
この回答は、回答が書かれてからUITableViewの実装に変更が加えられたため、もう機能していないようです。このコメントを参照してください: ITableViewがデータの要求を完了したときに通知を受け取りますか?
私はこの問題を数日間遊んでいますが、UITableView
のreloadData
をサブクラス化するのが最善の方法だと思います。
- (void)reloadData {
NSLog(@"BEGIN reloadData");
[super reloadData];
NSLog(@"END reloadData");
}
reloadData
は、テーブルがデータのリロードを完了するまで終了しません。したがって、2番目のNSLog
が起動されると、Table Viewは実際にデータの要求を完了します。
UITableView
の前後にデリゲートにメソッドを送信するために、reloadData
をサブクラス化しました。それは魅力のように機能します。
私のアプリには同じシナリオがありましたが、ここに記載されている他の回答はiOS7以降では機能しないため、あなたに答えを投稿すると思いました
最後に、これは私のために働いた唯一のものです。
[yourTableview reloadData];
dispatch_async(dispatch_get_main_queue(),^{
NSIndexPath *path = [NSIndexPath indexPathForRow:yourRow inSection:yourSection];
//Basically maintain your logic to get the indexpath
[yourTableview scrollToRowAtIndexPath:path atScrollPosition:UITableViewScrollPositionTop animated:YES];
});
Swift Update:
yourTableview.reloadData()
dispatch_async(dispatch_get_main_queue(), { () -> Void in
let path : NSIndexPath = NSIndexPath(forRow: myRowValue, inSection: mySectionValue)
//Basically maintain your logic to get the indexpath
yourTableview.scrollToRowAtIndexPath(path, atScrollPosition: UITableViewScrollPosition.Top, animated: true)
})
これがどのように機能するか。
基本的に、リロードするとメインスレッドがビジーになるため、非同期スレッドをディスパッチすると、メインスレッドが終了するまでブロックが待機します。テーブルビューが完全にロードされると、メインスレッドが終了し、メソッドブロックをディスパッチします
iOS7およびiOS8でテスト済み、それは素晴らしい動作します;)
iOS9のアップデート:これはiOS9でも正常に機能します。 POCとしてgithubでサンプルプロジェクトを作成しました。 https://github.com/ipraba/TableReloadingNotifier
ここにテストのスクリーンショットを添付します。
テスト済みの環境:Xcode7のiOS9 iPhone6シミュレーター
編集:この答えは実際には解決策ではありません。リロードは非常に高速に行われる可能性があるため、最初は動作しているように見えますが、実際には、ブロックしません。より良い解決策を検索する必要があります。
@Eric MORANDの答えを拡張するには、完了ブロックを入れましょう。ブロックが好きではない人はいますか?
@interface DUTableView : UITableView
- (void) reloadDataWithCompletion:( void (^) (void) )completionBlock;
@end
そして...
#import "DUTableView.h"
@implementation DUTableView
- (void) reloadDataWithCompletion:( void (^) (void) )completionBlock {
[super reloadData];
if(completionBlock) {
completionBlock();
}
}
@end
使用法:
[self.tableView reloadDataWithCompletion:^{
//do your stuff here
}];
reloadDataは、表示されているセルのデータを要求するだけです。テーブルの特定の部分が読み込まれたときに通知されるようにするには、tableView: willDisplayCell:
方法。
- (void) reloadDisplayData
{
isLoading = YES;
NSLog(@"Reload display with last index %d", lastIndex);
[_tableView reloadData];
if(lastIndex <= 0){
isLoading = YES;
//Notify completed
}
- (void) tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
{
if(indexPath.row >= lastIndex){
isLoading = NO;
//Notify completed
}
それが私の解決策です。 100%動作し、多くのプロジェクトで使用されています。シンプルなUITableViewサブクラスです。
_@protocol MyTableViewDelegate<NSObject, UITableViewDelegate>
@optional
- (void)tableViewWillReloadData:(UITableView *)tableView;
- (void)tableViewDidReloadData:(UITableView *)tableView;
@end
@interface MyTableView : UITableView {
struct {
unsigned int delegateWillReloadData:1;
unsigned int delegateDidReloadData:1;
unsigned int reloading:1;
} _flags;
}
@end
@implementation MyTableView
- (id<MyTableViewDelegate>)delegate {
return (id<MyTableViewDelegate>)[super delegate];
}
- (void)setDelegate:(id<MyTableViewDelegate>)delegate {
[super setDelegate:delegate];
_flags.delegateWillReloadData = [delegate respondsToSelector:@selector(tableViewWillReloadData:)];
_flags.delegateDidReloadData = [delegate respondsToSelector:@selector(tableViewDidReloadData:)];
}
- (void)reloadData {
[super reloadData];
if (_flags.reloading == NO) {
_flags.reloading = YES;
if (_flags.delegateWillReloadData) {
[(id<MyTableViewDelegate>)self.delegate tableViewWillReloadData:self];
}
[self performSelector:@selector(finishReload) withObject:nil afterDelay:0.0f];
}
}
- (void)finishReload {
_flags.reloading = NO;
if (_flags.delegateDidReloadData) {
[(id<MyTableViewDelegate>)self.delegate tableViewDidReloadData:self];
}
}
@end
_
Josh Brownのソリューション に似ていますが、1つ例外があります。 performSelectorメソッドで遅延は必要ありません。 reloadData
がどれだけ長くても。 _tableViewDidLoadData:
_は、tableView
がdataSource
cellForRowAtIndexPath
の確認を完了すると常に起動します。
UITableView
をサブクラス化したくない場合でも、[performSelector:@selector(finishReload) withObject:nil afterDelay:0.0f]
を呼び出すだけで、テーブルの再読み込みが完了した直後にセレクターが呼び出されます。ただし、reloadData
の呼び出しごとにセレクターが1回だけ呼び出されるようにする必要があります。
_[self.tableView reloadData];
[self performSelector:@selector(finishReload) withObject:nil afterDelay:0.0f];
_
楽しい。 :)
これは、わずかに異なる質問に対する答えです。UITableView
がcellForRowAtIndexPath()
の呼び出しを終了した時期を知る必要がありました。 layoutSubviews()
をサブクラス化し(@Eric MORANDに感謝)、デリゲートコールバックを追加しました。
SDTableView.h:
@protocol SDTableViewDelegate <NSObject, UITableViewDelegate>
@required
- (void)willReloadData;
- (void)didReloadData;
- (void)willLayoutSubviews;
- (void)didLayoutSubviews;
@end
@interface SDTableView : UITableView
@property(nonatomic,assign) id <SDTableViewDelegate> delegate;
@end;
SDTableView.m:
#import "SDTableView.h"
@implementation SDTableView
@dynamic delegate;
- (void) reloadData {
[self.delegate willReloadData];
[super reloadData];
[self.delegate didReloadData];
}
- (void) layoutSubviews {
[self.delegate willLayoutSubviews];
[super layoutSubviews];
[self.delegate didLayoutSubviews];
}
@end
使用法:
MyTableViewController.h:
#import "SDTableView.h"
@interface MyTableViewController : UITableViewController <SDTableViewDelegate>
@property (nonatomic) BOOL reloadInProgress;
MyTableViewController.m:
#import "MyTableViewController.h"
@implementation MyTableViewController
@synthesize reloadInProgress;
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
if ( ! reloadInProgress) {
NSLog(@"---- numberOfSectionsInTableView(): reloadInProgress=TRUE");
reloadInProgress = TRUE;
}
return 1;
}
- (void)willReloadData {}
- (void)didReloadData {}
- (void)willLayoutSubviews {}
- (void)didLayoutSubviews {
if (reloadInProgress) {
NSLog(@"---- layoutSubviewsEnd(): reloadInProgress=FALSE");
reloadInProgress = FALSE;
}
}
注:これはUITableView
を指すデリゲートプロパティを既に持っているMyTableViewController
のサブクラスであるため、必要はありません。別のものを追加します。 「@dynamicデリゲート」は、このプロパティを使用するようコンパイラーに指示します。 (これを説明するリンクがあります: http://farhadnoorzay.com/2012/01/20/objective-c-how-to-add-delegate-methods-in-a-subclass/ )
UITableView
のMyTableViewController
プロパティは、新しいSDTableView
クラスを使用するように変更する必要があります。これは、Interface Builder Identity Inspectorで行われます。 UITableView
内のUITableViewController
を選択し、その「カスタムクラス」をSDTableView
に設定します。
contentSize
のTableView
の変更通知を受け取るのに似たものを見つけました。 contentSizeもデータの読み込みに応じて変化するため、ここでも同様に機能するはずです。
これを試して:
viewDidLoad
書き込みで、
[self.tableView addObserver:self forKeyPath:@"contentSize" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld | NSKeyValueObservingOptionPrior context:NULL];
そして、このメソッドをviewControllerに追加します。
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
if ([keyPath isEqualToString:@"contentSize"]) {
DLog(@"change = %@", change.description)
NSValue *new = [change valueForKey:@"new"];
NSValue *old = [change valueForKey:@"old"];
if (new && old) {
if (![old isEqualToValue:new]) {
// do your stuff
}
}
}
}
変更のチェックでわずかな変更が必要になる場合があります。これは私にとってはうまくいきました。
乾杯! :)
ハックですが、考えられる解決策は次のとおりです。
[self.tableView reloadData];
[self performSelector:@selector(scrollTableView) withObject:nil afterDelay:0.3];
-scrollTableView
メソッドは、-scrollRectToVisible:animated:
でテーブルビューをスクロールします。そして、もちろん、上記のコードの遅延を0.3からあなたに合っているように設定することもできます。うん、それはとんでもないハックですが、iPhone 5と4Sで動作します...
セルの内容を更新したいようですが、セルの挿入や削除に伴う突然のジャンプはありません。
それを行う上でいくつかの記事があります。 これは1つです
スクロールビューのピクセル単位の設定には、scrollRectToVisible:animated:ではなくsetContentOffset:animated:を使用することをお勧めします。
似たようなものがあったと思います。インスタンス変数としてBOOLを追加して、オフセットが復元されたかどうかを確認し、-viewWillAppear:
。復元されていない場合は、その方法で復元し、BOOLを設定して、オフセットを復元したことを示します。
それは一種のハックであり、おそらくもっと良くできるかもしれませんが、これは今のところうまくいきます。
次のロジックを試すことができます。
-(UITableViewCell *) tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"MyIdentifier"];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:@"MyIdentifier"];
cell.selectionStyle = UITableViewCellSelectionStyleNone;
}
if ( [self chkIfLastCellIndexToCreate:tableView :indexPath]){
NSLog(@"Created Last Cell. IndexPath = %@", indexPath);
//[self.activityIndicator hide];
//Do the task for TableView Loading Finished
}
prevIndexPath = indexPath;
return cell;
}
-(BOOL) chkIfLastCellIndexToCreate:(UITableView*)tableView : (NSIndexPath *)indexPath{
BOOL bRetVal = NO;
NSArray *visibleIndices = [tableView indexPathsForVisibleRows];
if (!visibleIndices || ![visibleIndices count])
bRetVal = YES;
NSIndexPath *firstVisibleIP = [visibleIndices objectAtIndex:0];
NSIndexPath *lastVisibleIP = [visibleIndices objectAtIndex:[visibleIndices count]-1];
if ((indexPath.row > prevIndexPath.row) && (indexPath.section >= prevIndexPath.section)){
//Ascending - scrolling up
if ([indexPath isEqual:lastVisibleIP]) {
bRetVal = YES;
//NSLog(@"Last Loading Cell :: %@", indexPath);
}
} else if ((indexPath.row < prevIndexPath.row) && (indexPath.section <= prevIndexPath.section)) {
//Descending - scrolling down
if ([indexPath isEqual:firstVisibleIP]) {
bRetVal = YES;
//NSLog(@"Last Loading Cell :: %@", indexPath);
}
}
return bRetVal;
}
ReloadDataを呼び出す前に、prevIndexPathをnilに設定します。好む:
prevIndexPath = nil;
[mainTableView reloadData];
NSLogsでテストしましたが、このロジックは問題ないようです。必要に応じてカスタマイズ/改善できます。
最後に、これでコードを機能させました-
_[tableView scrollToRowAtIndexPath:scrollToIndex atScrollPosition:UITableViewScrollPositionTop animated:YES];
_
世話をする必要があるものはほとんどありませんでした-
- (UITableViewCell *)MyTableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
」内で呼び出します繰り返しスケジュールされたタイマーを実行し、tableHeaderViewの高さ(テーブルに行コンテンツがあることを意味する)のときにテーブルのcontentSizeが大きい場合にのみ無効にします。 C#(モノタッチ)のコードですが、アイデアが明確であることを願っています。
public override void ReloadTableData()
{
base.ReloadTableData();
// don't do anything if there is no data
if (ItemsSource != null && ItemsSource.Length > 0)
{
_timer = NSTimer.CreateRepeatingScheduledTimer(TimeSpan.MinValue,
new NSAction(() =>
{
// make sure that table has header view and content size is big enought
if (TableView.TableHeaderView != null &&
TableView.ContentSize.Height >
TableView.TableHeaderView.Frame.Height)
{
TableView.SetContentOffset(
new PointF(0, TableView.TableHeaderView.Frame.Height), false);
_timer.Invalidate();
_timer = null;
}
}));
}
}
テーブルビューがコンテンツを表示する直前にUITableView
layoutSubviews
が呼び出されていませんか?テーブルビューがデータの読み込みを完了すると呼び出されることに気付きました。おそらくその方向で調査する必要があります。
すべてのデータがロードされたら、このメソッドでTableViewのサイズを変更するか、コンテンツのサイズを設定できます。
- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
{
tableView.frame =CGRectMake(tableView.frame.Origin.x, tableView.frame.Origin.y, tableView.frame.size.width, tableView.contentSize.height);
}
Swiftで見つけた最高のソリューション
extension UITableView {
func reloadData(completion: ()->()) {
self.reloadData()
dispatch_async(dispatch_get_main_queue()) {
completion()
}
}
}
IOS 6以降、UITableview
デリゲートメソッドは以下を呼び出しました。
-(void)tableView:(UITableView *)tableView willDisplayHeaderView:(UIView *)view forSection:(NSInteger)section
テーブルが正常にリロードされると実行されます。この方法では、必要に応じてカスタマイズを行うことができます。