UIRefreshControl - beginRefreshing funktioniert nicht, wenn sich UITableViewController in UINavigationController befindet

120

Ich habe ein UIRefreshControl in meinem UITableViewController eingerichtet (der sich in einem UINavigationController befindet) und es funktioniert wie erwartet (dh Pulldown löst das richtige Ereignis aus). Wenn ich jedoch die beginRefreshingInstanzmethode auf dem Aktualisierungssteuerelement programmgesteuert aufrufe, wie folgt:

[self.refreshControl beginRefreshing];

Nichts passiert. Es sollte animiert werden und den Spinner zeigen. Die endRefreshingMethode funktioniert einwandfrei, wenn ich das nach der Aktualisierung aufrufe.

Ich habe ein grundlegendes Prototypprojekt mit diesem Verhalten erstellt und es funktioniert ordnungsgemäß, wenn mein UITableViewController direkt zum Root-View-Controller des Anwendungsdelegierten hinzugefügt wird, z.

self.viewController = tableViewController;
self.window.rootViewController = self.viewController;

Wenn ich das tableViewControlleraber zuerst einem UINavigationController hinzufüge und dann den Navigationscontroller als das hinzufüge rootViewController, beginRefreshingfunktioniert die Methode nicht mehr. Z.B

UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:tableViewController];
self.viewController = navController;
self.window.rootViewController = self.viewController;

Ich habe das Gefühl, dass dies etwas mit den verschachtelten Ansichtshierarchien innerhalb des Navigationscontrollers zu tun hat, die mit der Auffrischungssteuerung nicht gut funktionieren - irgendwelche Vorschläge?

Vielen Dank

wows
quelle

Antworten:

195

Wenn Sie mit der programmgesteuerten Aktualisierung beginnen, müssen Sie die Tabellenansicht beispielsweise selbst scrollen, indem Sie beispielsweise den Inhaltsversatz ändern

[self.tableView setContentOffset:CGPointMake(0, -self.refreshControl.frame.size.height) animated:YES];

Ich würde vermuten, dass der Grund dafür ist, dass es unerwünscht sein könnte, zum Aktualisierungssteuerelement zu scrollen, wenn sich der Benutzer in der Mitte / unten in der Tabellenansicht befindet.

Swift 2.2 Version von @muhasturk

self.tableView.setContentOffset(CGPoint(x: 0, y: -refreshControl.frame.size.height), animated: true)

Kurz gesagt, um dieses tragbare Gerät zu erhalten, fügen Sie diese Erweiterung hinzu

UIRefreshControl + ProgramatischBeginRefresh.swift

extension UIRefreshControl {
    func programaticallyBeginRefreshing(in tableView: UITableView) {
        beginRefreshing()
        let offsetPoint = CGPoint.init(x: 0, y: -frame.size.height)
        tableView.setContentOffset(offsetPoint, animated: true)        
    }
}
Dmitry Shevchenko
quelle
6
Das ist seltsam, in meinen Tests passt endRefreshing den Offset nach Bedarf an
Dmitry Shevchenko
9
Übrigens, wenn Sie das automatische Layout verwenden, können Sie die Zeile in der Antwort durch Folgendes ersetzen: [self.tableView setContentOffset: CGPointMake (0, -self.topLayoutGuide.length) animiert: YES];
Eric Baker
4
@ EricBaker Ich glaube, das geht nicht. Nicht alle UITableViewController zeigen Navigationsleisten an. Dies würde zu einem topLayoutGuide mit einer Länge von 20 und einem zu kleinen Versatz führen.
Fábio Oliveira
3
@EricBaker können Sie verwenden: [self.tableView setContentOffset: CGPointMake (0, self.topLayoutGuide.length -self.refreshControl.frame.size.height) animiert: YES];
ZYiOS
1
Copy Paste funktioniert bei mir, was großartig ist. Ich hatte es satt, Apples Storyboard-Builder-Zeug zu haben.
Okysabeni
78

UITableViewController hat nach iOS 7 automatisch die Eigenschaft AdjustsScrollViewInsets . Die Tabellenansicht verfügt möglicherweise bereits über contentOffset, normalerweise (0, -64).

Der richtige Weg, um refreshControl anzuzeigen, nachdem Sie programmgesteuert mit dem Aktualisieren begonnen haben, besteht darin, die Höhe von refreshControl zu vorhandenem contentOffset hinzuzufügen.

 [self.refreshControl beginRefreshing];
 [self.tableView setContentOffset:CGPointMake(0, self.tableView.contentOffset.y-self.refreshControl.frame.size.height) animated:YES];
Ich habe es verstanden
quelle
Hallo, vielen Dank, ich bin auch neugierig auf den magischen Punkt (0, -64), den ich beim Debuggen getroffen habe.
Inix
2
@inix 20 für Statusleistenhöhe + 44 für Navigationsleistenhöhe
Igotit
1
Statt -64ist besser zu bedienen-self.topLayoutGuide.length
Diogo T
33

Hier ist eine Swift-Erweiterung mit den oben beschriebenen Strategien.

extension UIRefreshControl {
    func beginRefreshingManually() {
        if let scrollView = superview as? UIScrollView {
            scrollView.setContentOffset(CGPoint(x: 0, y: scrollView.contentOffset.y - frame.height), animated: true)
        }
        beginRefreshing()
    }
}
Chris Wagner
quelle
Funktioniert für UITableView.
Juan Boero
10
Ich würde empfehlen, sendActionsForControlEvents(UIControlEvents.ValueChanged)am Ende dieser Funktion zu setzen, da sonst die eigentliche Logik der Aktualisierungslogik nicht ausgeführt wird.
Colin Basnett
Dies ist bei weitem die eleganteste Art, dies zu tun. Einschließlich Colin Basnetts Kommentar für eine bessere Funktionalität. Es kann im gesamten Projekt verwendet werden, indem es einmal definiert wird!
JoeGalind
1
@ColinBasnett: Das Hinzufügen dieses Codes (der jetzt sendActions ist (für: UIControlEvents.valueChanged)) führt zu einer Endlosschleife ...
Kendall Helmstetter Gelner
15

Keine der anderen Antworten hat bei mir funktioniert. Sie würden den Spinner dazu bringen, sich zu zeigen und zu drehen, aber die Auffrischungsaktion selbst würde niemals stattfinden. Das funktioniert:

id target = self;
SEL selector = @selector(example);
// Assuming at some point prior to triggering the refresh, you call the following line:
[self.refreshControl addTarget:target action:selector forControlEvents:UIControlEventValueChanged];

// This line makes the spinner start spinning
[self.refreshControl beginRefreshing];
// This line makes the spinner visible by pushing the table view/collection view down
[self.tableView setContentOffset:CGPointMake(0, -1.0f * self.refreshControl.frame.size.height) animated:YES];
// This line is what actually triggers the refresh action/selector
[self.refreshControl sendActionsForControlEvents:UIControlEventValueChanged];

Beachten Sie, dass in diesem Beispiel eine Tabellenansicht verwendet wird, es könnte sich aber auch um eine Sammlungsansicht handeln.

Kyle Robson
quelle
Dies hat mein Problem gelöst. Aber jetzt benutze ich SVPullToRefresh, wie man es programmgesteuert herunterzieht?
Gank
1
@Gank Ich habe SVPullToRefresh noch nie benutzt. Haben Sie versucht, ihre Dokumente zu lesen? Anhand der Dokumente scheint es ziemlich offensichtlich, dass es programmgesteuert heruntergezogen werden kann: "Wenn Sie die Aktualisierung programmgesteuert auslösen möchten (zum Beispiel in viewDidAppear :), können Sie dies tun mit: [tableView triggerPullToRefresh];" Siehe: github.com/samvermette/ SVPullToRefresh
Kyle Robson
1
Perfekt! Es war seltsam für mich mit der Animation, also habe ich es einfach durchscrollView.contentOffset = CGPoint(x: 0, y: scrollView.contentOffset.y - frame.height)
user1366265
13

Der bereits erwähnte Ansatz:

[self.refreshControl beginRefreshing];
 [self.tableView setContentOffset:CGPointMake(0, self.tableView.contentOffset.y-self.refreshControl.frame.size.height) animated:YES];

würde den Spinner sichtbar machen. Aber es würde nicht animieren. Das einzige, was ich geändert habe, ist die Reihenfolge dieser beiden Methoden und alles hat funktioniert:

[self.tableView setContentOffset:CGPointMake(0, self.tableView.contentOffset.y-self.refreshControl.frame.size.height) animated:YES];
[self.refreshControl beginRefreshing];
ancajic
quelle
11

Für Swift 4 / 4.1

Eine Mischung aus vorhandenen Antworten erledigt den Job für mich:

refreshControl.beginRefreshing()
tableView.setContentOffset(CGPoint(x: 0, y: tableView.contentOffset.y - (refreshControl.frame.size.height)), animated: true)

Hoffe das hilft!

Isaac Bosca
quelle
7

Siehe auch diese Frage

UIRefreshControl wird beim Aufrufen von beginRefreshing nicht angezeigt, und contentOffset ist 0

Für mich sieht es nach einem Fehler aus, da er nur auftritt, wenn die contentOffset-Eigenschaft der tableView 0 ist

Ich habe das mit folgendem Code behoben (Methode für den UITableViewController):

- (void)beginRefreshingTableView {

    [self.refreshControl beginRefreshing];

    if (self.tableView.contentOffset.y == 0) {

        [UIView animateWithDuration:0.25 delay:0 options:UIViewAnimationOptionBeginFromCurrentState animations:^(void){

            self.tableView.contentOffset = CGPointMake(0, -self.refreshControl.frame.size.height);

        } completion:^(BOOL finished){

        }];

    }
}
Peter Lapisu
quelle
1
Dies ist nicht wahr, ich habe zwei verschiedene UIViewController, die beide bei viewDidLoad ein contentOffset von 0 haben, und einer von ihnen zieht das refreshControl beim Aufruf von [self.refreshControl beginRefreshing] korrekt herunter und der andere nicht: /
simonthumper
Die Dokumentation sagt nichts über die Anzeige des Steuerelements aus beginRefreshing, nur, dass sich sein Status ändert. Aus meiner Sicht soll verhindert werden, dass die Aktualisierungsaktion zweimal gestartet wird, sodass möglicherweise eine programmgesteuert aufgerufene Aktualisierung weiterhin ausgeführt wird und eine vom Benutzer initiierte Aktion keine weitere startet.
Koen.
Ich habe Probleme bei der Verwendung der animierten Methode setContentOffset: festgestellt, daher hat diese Lösung für mich funktioniert.
Marc Etcheverry
7

Hier ist Swift 3 und eine spätere Erweiterung, die sowohl Spinner als auch Animationen zeigt.

import UIKit
extension UIRefreshControl {

func beginRefreshingWithAnimation() {

    DispatchQueue.main.asyncAfter(deadline: .now() + 0.05) {

        if let scrollView = self.superview as? UIScrollView {
            scrollView.setContentOffset(CGPoint(x: 0, y: scrollView.contentOffset.y - self.frame.height), animated: true)
          }
        self.beginRefreshing()
      }
   }
}
Ghulam Rasool
quelle
2
Von allen obigen Antworten war dies die einzige, die ich unter iOS 11.1 / xcode 9.1
Bassebus
1
Das asyncAfterist eigentlich, was die Spinner-Animation funktioniert (iOS 12.3 / Xcode 10.2)
Francybiga
4

Es funktioniert perfekt für mich:

Swift 3:

self.tableView.setContentOffset(CGPoint(x: 0, y: -self.refreshControl!.frame.size.height - self.topLayoutGuide.length), animated: true)
Meilbn
quelle
4

Für Swift 5 fehlte mir nur der Anruf refreshControl.sendActions(.valueChanged). Ich habe eine Erweiterung gemacht, um es sauberer zu machen.

extension UIRefreshControl {

    func beginRefreshingManually() {
        if let scrollView = superview as? UIScrollView {
            scrollView.setContentOffset(CGPoint(x: 0, y: scrollView.contentOffset.y - frame.height), animated: false)
        }
        beginRefreshing()
        sendActions(for: .valueChanged)
    }

}
LukasHromadnik
quelle
3

Neben @Dymitry Shevchenko Lösung.

Ich habe eine gute Lösung für dieses Problem gefunden. Sie können eine Erweiterung für UIRefreshControldiese Überschreibungsmethode erstellen :

// Adds code forgotten by Apple, that changes content offset of parent scroll view (table view).
- (void)beginRefreshing
{
    [super beginRefreshing];

    if ([self.superview isKindOfClass:[UIScrollView class]]) {
        UIScrollView *view = (UIScrollView *)self.superview;
        [view setContentOffset:CGPointMake(0, view.contentOffset.y - self.frame.size.height) animated:YES];
    }
}

Sie können eine neue Klasse verwenden, indem Sie im Identitätsinspektor eine benutzerdefinierte Klasse für die Aktualisierungssteuerung in Interface Builder festlegen.

lyzkov
quelle
3

Fort Swift 2.2+

    self.tableView.setContentOffset(CGPoint(x: 0, y: -refreshControl.frame.size.height), animated: true)
Muhasturk
quelle
Ich habe dies mit der akzeptierten Antwort zusammengeführt, da es sich nur um ein Update handelt.
Tudor
3

Wenn Sie Rxswift für Swift 3.1 verwenden, können Sie Folgendes verwenden:

func manualRefresh() {
    if let refreshControl = self.tableView.refreshControl {
        self.tableView.setContentOffset(CGPoint(x: 0, y: -refreshControl.height), animated: true)
        self.tableView.refreshControl?.beginRefreshing()
        self.tableView.refreshControl?.sendActions(for: .valueChanged)
    }
}

Diese Arbeit für Swift 3.1, iOS 10.

jkyin
quelle
1
Es ist der sendActionsAuslöser rx, der diese Antwort in Bezug auf den RxSwiftFall macht, dass sich jemand auf den ersten Blick wundert
carbonr
Ich musste die refreshControl-Instanz aus dem tableViewController verwenden, nicht die tableView. Außerdem hat dieses setContentOffset für mich mit iOS10 nicht funktioniert. Dieser funktioniert jedoch: self.tableView.setContentOffset(CGPoint(x:0, y:self.tableView.contentOffset.y - (refreshControl.frame.size.height)), animated: true)
nmdias
1

getestet auf Swift 5

Verwenden Sie dies in viewDidLoad()

fileprivate func showRefreshLoader() {
    DispatchQueue.main.asyncAfter(deadline: .now() + 0.05) {
        self.tableView.setContentOffset(CGPoint(x: 0, y: self.tableView.contentOffset.y - (self.refreshControl.frame.size.height)), animated: true)
        self.refreshControl.beginRefreshing() 
    }
}
Pramodya Abeysinghe
quelle
0

Ich verwende die gleiche Technik, um dem Benutzer das visuelle Zeichen "Daten werden aktualisiert" anzuzeigen. Ein Ergebnis, das Benutzer aus dem Hintergrund mitbringen, und Feeds / Listen werden mit der Benutzeroberfläche aktualisiert, so wie Benutzer Tabellen ziehen, um sich selbst zu aktualisieren. Meine Version enthält 3 Dinge

1) Wer sendet "wach auf"

- (void)applicationDidBecomeActive:(UIApplication *)application {
    [[NSNotificationCenter defaultCenter] postNotificationName:kNotificationHaveToResetAllPages object:nil];
}

2) Beobachter in UIViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(forceUpdateData) name:kNotificationHaveToWakeUp:nil];
}

3) Das Protokoll

#pragma mark - ForcedDataUpdateProtocol

- (void)forceUpdateData {
    self.tableView.contentOffset = CGPointZero;

    if (self.refreshControl) {
        [self.refreshControl beginRefreshing];
        [self.tableView setContentOffset:CGPointMake(0, -self.refreshControl.frame.size.height) animated:YES];
        [self.refreshControl performSelector:@selector(endRefreshing) withObject:nil afterDelay:1];
    }
}

Ergebnis

WINSergey
quelle