Benachrichtigt werden, wenn UITableView die Datenanfrage beendet hat?

108

Gibt es eine Möglichkeit herauszufinden, wann a UITableViewdie Daten von seiner Datenquelle abgefragt hat?

Keine der viewDidLoad/ viewWillAppear/ viewDidAppearMethoden des zugehörigen View Controllers ( UITableViewController) ist hier von Nutzen, da sie alle zu früh ausgelöst werden. Keiner von ihnen garantiert (völlig verständlich), dass Abfragen an die Datenquelle vorerst abgeschlossen sind (z. B. bis die Ansicht gescrollt wird).

Eine Abhilfe , die ich gefunden habe , ist zu nennen reloadDatain viewDidAppear, da bei der reloadDataRückkehr, die Tabellenansicht ist garantiert beendet zu haben , die Datenquelle so viel abfragt , wie es für den Augenblick braucht.

Dies scheint jedoch ziemlich unangenehm zu sein, da ich davon ausgehe, dass die Datenquelle reloadDatabeim ersten Laden zweimal (einmal automatisch und einmal aufgrund des Aufrufs) nach denselben Informationen gefragt wird.

Der Grund, warum ich dies überhaupt tun möchte, ist, dass ich die Bildlaufposition von UITableView- beibehalten möchte, aber bis auf die Pixelebene, nicht nur bis zur nächsten Zeile.

Wenn die Bildlaufposition Wiederherstellung (mit scrollRectToVisible:animated:), muß ich die Tabellenansicht bereits genügend Daten darin hat, oder aber der scrollRectToVisible:animated:Methodenaufruf tut nichts (was was ist, wenn Sie den Anruf auf seinem eigenen in einem Platz geschieht , ist viewDidLoad, viewWillAppearoder viewDidAppear).

kennethmac2000
quelle
Es sieht so aus, als ob Sie etwas Ähnliches wie dieses stackoverflow.com/a/11672379/2082172 suchen .
Timur Kuchkarov
Nach meiner Erfahrung kann UITableView Aufrufe an reloadData zwischenspeichern, genauso wie es Aufrufe zwischenspeichert, um Zeilen und andere Dinge einzufügen / zu löschen. UITableView ruft den Datenquellendelegierten auf, wenn er fertig ist, abhängig von der CPU-Auslastung und anderen Faktoren.
Walt Sellers

Antworten:

61

Diese Antwort scheint nicht mehr zu funktionieren, da einige Änderungen an der UITableView-Implementierung vorgenommen wurden, seit die Antwort geschrieben wurde. Siehe diesen Kommentar: Benachrichtigt werden, wenn UITableView die Datenanfrage beendet hat?

Ich habe für ein paar Tage mit diesem Problem gespielt und denke , dass Subclassing UITableView‚s reloadDataist der beste Ansatz:

- (void)reloadData {

    NSLog(@"BEGIN reloadData");

    [super reloadData];

    NSLog(@"END reloadData");

}

reloadDataendet nicht, bevor die Tabelle ihre Daten neu geladen hat. Wenn also die zweite NSLogausgelöst wird, fragt die Tabellenansicht tatsächlich nach Daten.

Ich habe eine Unterklasse erstellt UITableView, um vorher und nachher Methoden an den Delegaten zu senden reloadData. Es wirkt wie ein Zauber.

Eric MORAND
quelle
2
Ich muss sagen, das scheint die beste Lösung zu sein. Einfach implementiert und wie Sie sagen, funktioniert es wie ein Zauber. Vielen Dank!
Theorie
2
@EricMORAND Sie sagen, dass "reloadData nicht endet, bevor die Tabelle ihre Daten neu geladen hat." Können Sie klarstellen, was Sie damit meinen? Ich finde, dass reloadDatadas sofort zurückkehrt und ich sehe "END reloadData", bevor die Zellen tatsächlich neu geladen werden (dh bevor die UITableViewDataSourceMethoden aufgerufen werden). Mein Experimentieren zeigt das genaue Gegenteil von dem, was Sie sagen. Ich muss falsch verstehen, was Sie sagen wollen.
Rob
3
Ist Erics Antwort nicht gleichbedeutend damit, Reloaddata nicht zu implementieren (lesen, nicht überschreiben), Reloaddata aufzurufen (was im Wesentlichen [Super-Reloaddata] sein wird) und dann nach dem Aufrufen die gewünschten Aufgaben zu erledigen?
Nirav Bhatt
5
Es war einmal, dass reloadData den Ladevorgang abgeschlossen hat, bevor er beendet wurde. Aber Apple hat es irgendwann geändert. Jetzt kann die UITableView-Klasse den reloadData-Aufruf mit allen Aufrufen zum Einfügen und Löschen von Zeilen zwischenspeichern. Wenn Sie sich die @ interface-Deklaration für UITableView ansehen, finden Sie das NSMutableArray-Mitglied _reloadItems direkt unter _insertItems und _deleteItems. (Ich musste Code, den ich wegen dieser Änderung geerbt hatte, überarbeiten.)
Walt Sellers
3
Das Posten des Abschlusscodes in einem Block in der Hauptwarteschlange nach dem Aufruf [super reloadData]funktioniert für mich : dispatch_async(dispatch_get_main_queue(), ^{NSLog(@"reload complete");});. Es springt im Grunde Frösche die Blöcke, die von der Tabellenansicht in gepostet werden reloadData.
Timothy Moose
53

Ich hatte das gleiche Szenario in meiner App und dachte, ich würde meine Antwort an euch senden, da andere hier erwähnte Antworten für iOS7 und höher nicht funktionieren

Schließlich ist dies das einzige, was für mich geklappt hat.

[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];
 });

Schnelles 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)

})

Also wie das funktioniert.

Grundsätzlich wird der Haupt-Thread beim Neuladen ausgelastet. Wenn wir also einen asynchronen Versand-Thread ausführen, wartet der Block, bis der Haupt-Thread fertig ist. Sobald die Tabellenansicht vollständig geladen wurde, wird der Hauptthread beendet und unser Methodenblock wird versendet

Getestet in iOS7 und iOS8 und es funktioniert super ;)

Update für iOS9: Dies funktioniert gut, auch iOS9. Ich habe ein Beispielprojekt in Github als POC erstellt. https://github.com/ipraba/TableReloadingNotifier

Ich füge hier den Screenshot meines Tests hinzu.

Getestete Umgebung: iOS9 iPhone6 ​​Simulator von Xcode7

Geben Sie hier die Bildbeschreibung ein

iPrabu
quelle
8
beste answear die ich gefunden habe!
Nikolay Shubenkov
@Gon Ich habe einen Test in iOS9 gemacht und es funktioniert einfach gut. Können Sie bitte github.com/ipraba/TableReloadingNotifier
iPrabu
Es funktioniert gut auf meinem Emulator, scheint aber auf dem tatsächlichen Gerät nicht zu funktionieren. Hat noch jemand dieses Problem?
sosale151
1
Diese Lösung funktioniert endlich für mich! es funktioniert gut unter iOS 9
Strong
1
Ich habe auf Real Device, einem iPhone 6s, getestet. Es funktioniert auch gut.
Stark
25

EDIT: Diese Antwort ist eigentlich keine Lösung. Es scheint wahrscheinlich zunächst zu funktionieren, da das Neuladen ziemlich schnell erfolgen kann, aber tatsächlich wird der Abschlussblock nicht unbedingt aufgerufen, nachdem die Daten vollständig neu geladen wurden - da reloadData nicht blockiert. Sie sollten wahrscheinlich nach einer besseren Lösung suchen.

Um die Antwort von @Eric MORAND zu erweitern, fügen wir einen Abschlussblock ein. Wer liebt einen Block nicht?

@interface DUTableView : UITableView

   - (void) reloadDataWithCompletion:( void (^) (void) )completionBlock;

@end

und...

#import "DUTableView.h"

@implementation DUTableView

- (void) reloadDataWithCompletion:( void (^) (void) )completionBlock {
    [super reloadData];
    if(completionBlock) {
        completionBlock();
    }
}

@end

Verwendung:

[self.tableView reloadDataWithCompletion:^{
                                            //do your stuff here
                                        }];
Bandejapaisa
quelle
2
Ich kann nicht genug von Blöcken bekommen. Wer braucht einen Delegierten, wenn Sie einen schönen Block haben!
Bandejapaisa
Ich mag das, aber was ist mit Zeiten, in denen das System reloadData () aufruft, z. B. wenn die Tabelle zum ersten Mal angezeigt wird?
Symmetrisch
12
Dies ist keine Lösung. Der Abschlussblock startet die Ausführung vor cellForRowAtIndexPath
zvjerka24
1
Dies wird nicht funktionieren; reloadData ist keine Blockierungsmethode. Dieser Code wird sofort nach dem Aufruf von reloadData aufgerufen, auch wenn die Zellen noch nicht neu geladen wurden. Wenn Sie sich diesen Code ansehen, werden Sie feststellen, dass Sie Ihren Code genauso gut nach reloadData einfügen können .
Colinta
1
Das funktioniert bei mir mit einer kleinen Änderung. Rufen Sie den Abschlussblock nicht direkt auf, sondern in einem Block in der Hauptwarteschlange : dispatch_async(dispatch_get_main_queue(), ^{completionBlock();});. Dieser Sprung führt im Grunde zu den Blöcken, die von der Tabellenansicht in gepostet wurden reloadData.
Timothy Moose
11

reloadData fragt nur nach Daten für die sichtbaren Zellen. Um benachrichtigt zu werden, wenn ein bestimmter Teil Ihrer Tabelle geladen wird, schließen Sie bitte die tableView: willDisplayCell:Methode an.

- (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
}
Schwarzer Codierer
quelle
1
Ich bin mir nicht sicher, ob das funktioniert. Der letzte Index ist das Ende der Tabellendaten (z. B. 100 Datensätze), aber die Tabelle zeigt nur das an, was auf dem Bildschirm sichtbar ist (z. B. 8 Datensätze).
Travis M.
10

Das ist meine Lösung. 100% funktioniert und wird in vielen Projekten verwendet. Es ist eine einfache UITableView-Unterklasse.

@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

Mit einer Ausnahme ähnelt es der Lösung von Josh Brown . Bei der performSelector-Methode ist keine Verzögerung erforderlich. Egal wie lange es reloadDatadauert. tableViewDidLoadData:Feuert immer, wenn Sie tableViewfertig sind dataSource cellForRowAtIndexPath.

Auch wenn Sie keine Unterklasse möchten, UITableViewkönnen Sie einfach aufrufen [performSelector:@selector(finishReload) withObject:nil afterDelay:0.0f]und Ihr Selektor wird direkt nach dem erneuten Laden der Tabelle aufgerufen. Sie sollten jedoch sicherstellen, dass der Selektor nur einmal pro Aufruf aufgerufen wird, um reloadData:

[self.tableView reloadData];
[self performSelector:@selector(finishReload) withObject:nil afterDelay:0.0f];

Genießen. :) :)

Mark Kryzhanouski
quelle
1
Die Verwendung von performSelector ist brillant. Einfacher und Arbeits, thanx
Julia
Das ist absolut großartig. Ich habe seit Tagen damit gerungen. Danke dir!
David Carrico
@ MarkKryzhanouski, hast du auf iOS 9 getestet?
Victor
@ MarkKryzhanouski, danke für das schnelle Feedback! Funktioniert hervorragend unter iOS 7 und 8.
Victor
Die Lösungen performSelectoroder die Ausführung im Hauptthread mit dispatch_asynchfunktionieren unter iOS 9 nicht .
Manuel
8

Dies ist eine Antwort auf eine etwas andere Frage: Ich musste wissen, wann UITableViewauch der Anruf beendet war cellForRowAtIndexPath(). Ich habe eine Unterklasse layoutSubviews()(danke @Eric MORAND) erstellt und einen Rückruf für Delegierte hinzugefügt:

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

Verwendung:

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;
    }
}

HINWEISE: Da dies eine Unterklasse ist, auf UITableViewdie bereits eine Delegate-Eigenschaft verweist, muss MyTableViewControllerkeine weitere hinzugefügt werden. Der "@dynamic-Delegat" weist den Compiler an, diese Eigenschaft zu verwenden. (Hier ist ein Link diese beschreiben: http://farhadnoorzay.com/2012/01/20/objective-c-how-to-add-delegate-methods-in-a-subclass/ )

Die UITableViewEigenschaft in MyTableViewControllermuss geändert werden, um die neue SDTableViewKlasse zu verwenden. Dies erfolgt im Interface Builder Identity Inspector. Wählen Sie das UITableViewInnere von UITableViewControllerund setzen Sie die "Benutzerdefinierte Klasse" auf SDTableView.

Symmetrisch
quelle
Wo legen Sie Ihren MyTableViewController als Delegaten für SDTableView fest? Wie kommt es, dass es möglich ist, eine Eigenschaft mit dem Namen "delegate" in SDTableView festzulegen, wenn die Oberklasse bereits eine Eigenschaft mit diesem Namen hat (UITableView.delegate)? Wie können Sie Ihr benutzerdefiniertes SDTableView an die MyTableViewController.tableView-Eigenschaft anhängen, wenn die Eigenschaft vom Typ "UITablewView" und das Objekt (eine Instanz von SDTableView) vom Typ "SDTableView" ist? Ich kämpfe das gleiche Problem, also hoffe ich, dass es eine Lösung für diese Probleme gibt :)
Earl Grey
Sollte reloadInProgress im Code von Symmetric (SDTableView) nicht in didReloadData anstelle von didLayoutSubviews auf FALSE gesetzt werden? Da reloadData nach layoutSubviews aufgerufen wird und das Laden nicht als abgeschlossen betrachtet werden sollte, bevor reloadData abgeschlossen ist.
tzuchien.chiu
Dies funktioniert nicht für iOS 8, musste die Antwort von iPrabu enthalten .
Stunner
6

Ich hatte etwas Ähnliches gefunden, um eine Benachrichtigung für die Änderung contentSizevon zu erhalten TableView. Ich denke, das sollte auch hier funktionieren, da sich contentSize auch beim Laden von Daten ändert.

Versuche dies:

In viewDidLoadschreiben,

[self.tableView addObserver:self forKeyPath:@"contentSize" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld | NSKeyValueObservingOptionPrior context:NULL];

und fügen Sie diese Methode Ihrem viewController hinzu:

- (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
            }
        }


    }
}

Möglicherweise müssen Sie bei der Überprüfung auf Änderungen geringfügige Änderungen vornehmen. Das hatte aber bei mir funktioniert.

Prost! :) :)

Abdullah Umer
quelle
1
Sehr schlau. Funktioniert perfekt für mich. Danke für das Teilen!
DZenBot
2
Elegant! Das einzige, was ich hinzufügen möchte, ist, dass Sie sich und einen Beobachter entfernen müssen (möglicherweise in der -dealloc-Methode). Oder fügen Sie sich als Beobachter in die -viewWillAppearund entfernen Sie sich in der -viewWillDisapearMethode.
Loozie
2

Hier ist eine mögliche Lösung, obwohl es ein Hack ist:

[self.tableView reloadData];
[self performSelector:@selector(scrollTableView) withObject:nil afterDelay:0.3];

Wo Ihre -scrollTableViewMethode die Tabellenansicht mit scrollt -scrollRectToVisible:animated:. Und natürlich können Sie die Verzögerung im obigen Code von 0,3 bis zu dem konfigurieren, was für Sie zu funktionieren scheint. Ja, es ist lächerlich hackig, aber es funktioniert für mich auf meinem iPhone 5 und 4S ...

Josh Brown
quelle
2
Es mag "hacky" erscheinen, aber dies ist wirklich ein Standardansatz für das Scrollen: Verschieben Sie es auf den nächsten Laufzyklus. Ich würde die Verzögerung auf 0 ändern, da dies genauso gut funktioniert und weniger Latenz hat.
Phatmann
Hat bei mir mit der Verzögerung 0 nicht funktioniert. 0,3 war der niedrigste Wert, den ich erreichen konnte, um die Daten zu erhalten.
Josh Brown
@phatmann Das hat bei mir funktioniert. Ich konnte auch 0 für meine Verzögerung verwenden. Danke euch beiden.
Mcphersonjr
1

Ich hatte etwas Ähnliches, glaube ich. Ich habe eine BOOL als Instanzvariable hinzugefügt, die mir sagt, ob der Offset wiederhergestellt wurde, und das eincheckt -viewWillAppear:. Wenn es nicht wiederhergestellt wurde, stelle ich es in dieser Methode wieder her und setze BOOL, um anzuzeigen, dass ich den Offset wiederhergestellt habe.

Es ist eine Art Hack und es kann wahrscheinlich besser gemacht werden, aber das funktioniert im Moment für mich.

Joost
quelle
Ja, das Problem ist, dass viewWillAppear zu früh zu sein scheint (zumindest in meinem Szenario). Der Versuch, den Offset in viewWillAppear wiederherzustellen, führt zu nichts - es sei denn, ich füge den Hack hinzu, zuerst reloadData aufzurufen. Nach meinem Verständnis bezieht sich viewWillAppear / viewDidAppear nur auf die Tabellenansicht selbst, die tatsächlich angezeigt wird. Sie erheben keinen Anspruch darauf, ob die Tabellenzellen in der Ansicht entweder aufgelistet oder ausgefüllt wurden. Dies muss geschehen, bevor Sie sie wiederherstellen können ein Offset, da Sie sonst einen Offset in einer leeren Ansicht wiederherstellen würden (und ich verstehe, warum das nicht funktioniert!).
kennethmac2000
Aber vielleicht haben Sie gemeint, dass Sie das Laden der Tabellenzellen zuerst erzwingen, indem Sie reloadData in viewWillAppear aufrufen?
kennethmac2000
Nein, ich erzwinge kein Nachladen. Als ich das Problem hatte, habe ich versucht, den Offset wiederherzustellen -viewDidLoad(wo es natürlich passieren sollte), aber das hat nur funktioniert, wenn ich den Offset animiert eingestellt habe. Das Verschieben der Einstellung des Versatzes -viewWillAppear:funktionierte, aber ich musste ein Flag beibehalten, um es nur einmal zu setzen. Ich nehme an, die Tabellenansicht lädt ihre Daten neu, sobald sie einer Ansicht hinzugefügt wurden -loadView. Sind Sie sicher, dass Ihre Daten beim Laden der Ansicht verfügbar sind? Oder wird es in einen separaten Thread geladen oder so?
Joost
OK, vielleicht können Sie mir hier das Verständnis erleichtern. So verstehe ich die Reihenfolge der Aufrufe beim Aufbau einer UITableView. 1) viewDidLoad wird zuerst ausgelöst. Dies zeigt an, dass UITableView in den Speicher geladen ist. 2) viewWillAppear ist neben dem Feuer. Dies zeigt an, dass UITableView angezeigt wird, aber nicht unbedingt, dass alle sichtbaren UITableViewCell-Objekte vollständig instanziiert / fertig gerendert werden.
kennethmac2000
2
Und all das ist wahr, die Frage ist dann: Welche nicht-hackigen Optionen haben wir, um herauszufinden, wann die UITableView genügend Informationen aus ihrer Datenquelle erhalten hat, um zu gewährleisten, dass eine Bildlaufanforderung (dh ein scrollRectToVisible: animierter: Aufruf) ) wird tatsächlich funktionieren (und nicht einfach nichts tun)?
kennethmac2000
1

Es hört sich so an, als ob Sie den Zelleninhalt aktualisieren möchten, aber ohne die plötzlichen Sprünge, die mit dem Einfügen und Löschen von Zellen einhergehen können.

Es gibt mehrere Artikel dazu. Dies ist einer.

Ich empfehle die Verwendung von setContentOffset: animiert: anstelle von scrollRectToVisible: animiert: für pixelgenaue Einstellungen einer Bildlaufansicht.

Walt Sellers
quelle
1

Sie können die folgende Logik ausprobieren:

-(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;
}

Und bevor Sie reloadData aufrufen, setzen Sie prevIndexPath auf nil. Mögen:

prevIndexPath = nil;
[mainTableView reloadData];

Ich habe mit NSLogs getestet, und diese Logik scheint in Ordnung zu sein. Sie können nach Bedarf anpassen / verbessern.

Joe M.
quelle
0

Endlich habe ich meinen Code damit arbeiten lassen -

[tableView scrollToRowAtIndexPath:scrollToIndex atScrollPosition:UITableViewScrollPositionTop animated:YES];

Es gab nur wenige Dinge, die erledigt werden mussten -

  1. nenne es innerhalb von " - (UITableViewCell *)MyTableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath"
  2. Stellen Sie einfach sicher, dass die Nachricht "scrollToRowAtIndexPath" an die relevante Instanz von UITableView gesendet wird, in diesem Fall definitiv MyTableview.
  3. In meinem Fall ist UIView die Ansicht, die eine Instanz von UITableView enthält
  4. Dies wird auch für jede Zellenlast aufgerufen. Erstellen Sie daher eine Logik in "cellForRowAtIndexPath", um zu vermeiden, dass "scrollToRowAtIndexPath" mehrmals aufgerufen wird.
smile.al.d.way
quelle
und stellen Sie einfach sicher, dass dieses Stück nicht mehr als einmal aufgerufen wird. Wenn nicht, wird das Scrollen gesperrt.
smile.al.d.way
Das mag funktionieren, ist aber definitiv nicht elegant und nicht die Lösung, nach der ich suche. Leider scheint es keinen besseren Weg zu geben, um festzustellen, ob -reloadData die Daten tatsächlich vollständig abgerufen hat ...
Josh Brown
0

Sie können die Größe Ihrer Tabellenansicht ändern oder die Inhaltsgröße in dieser Methode festlegen, wenn alle Daten geladen sind:

- (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);
}
Bankier Mittal
quelle
0

Ich führe nur das Wiederholen des geplanten Timers aus und mache ihn nur ungültig, wenn die contentSize der Tabelle größer ist, wenn die tableHeaderView-Höhe (dh die Tabelle enthält Zeileninhalt). Der Code in C # (Monotouch), aber ich hoffe die Idee ist klar:

    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;
                    }
                }));
        }
    }
Eugene Tiutiunnyk
quelle
0

Wird nicht UITableView layoutSubviewsaufgerufen, bevor die Tabellenansicht den Inhalt anzeigt? Ich habe festgestellt, dass es aufgerufen wird, sobald die Tabellenansicht ihre Daten geladen hat. Vielleicht sollten Sie dies untersuchen.

Eric MORAND
quelle
0

Seit iOS 6 heißt die UITableviewDelegatenmethode:

-(void)tableView:(UITableView *)tableView willDisplayHeaderView:(UIView *)view forSection:(NSInteger)section

wird ausgeführt, sobald Ihre Tabelle erfolgreich neu geladen wurde. Bei dieser Methode können Sie die erforderlichen Anpassungen vornehmen.

Bharath Vankireddy
quelle
0

Die beste Lösung, die ich in Swift gefunden habe

extension UITableView {
    func reloadData(completion: ()->()) {
        self.reloadData()
        dispatch_async(dispatch_get_main_queue()) {
            completion()
        }
    }
}
YannSteph
quelle
-1

Warum nicht einfach verlängern?

@interface UITableView(reloadComplete)
- (void) reloadDataWithCompletion:( void (^) (void) )completionBlock;
@end

@implementation UITableView(reloadComplete)
- (void) reloadDataWithCompletion:( void (^) (void) )completionBlock {
    [self reloadData];
    if(completionBlock) {
        completionBlock();
    }
}
@end

bis zum Ende scrollen:

[self.table reloadDataWithCompletion:^{
    NSInteger numberOfRows = [self.table numberOfRowsInSection:0];
    if (numberOfRows > 0)
    {
        NSIndexPath *indexPath = [NSIndexPath indexPathForRow:numberOfRows-1 inSection:0];
        [self.table scrollToRowAtIndexPath:indexPath atScrollPosition:UITableViewScrollPositionTop animated:NO];
    }
}];

Nicht mit vielen Daten getestet

Andrii Tishchenko
quelle