UIRefreshControl ohne UITableViewController

308

Nur neugierig, da es nicht sofort möglich erscheint, aber gibt es eine hinterhältige Möglichkeit, die neue iOS 6- UIRefreshControlKlasse ohne Verwendung einer UITableViewControllerUnterklasse zu nutzen?

Ich benutze oft ein UIViewControllermit einer UITableViewUnteransicht und passe mich an UITableViewDataSourceund UITableViewDelegateanstatt ein UITableViewControllerdirektes zu verwenden.

Keller
quelle
6
@ DaveDeLong: Nein, Dave, was soll er tun? Später auf dieser Seite sagen Sie, dass seine Lösung nicht unterstützt wird. Was ist also die richtige Lösung?
Matt
3
@matt sollte er a verwenden UITableViewControllerund einen Fehler melden , der die API auffordert, a UIRefreshControlmit a UITableViewdirekt zu verwenden.
Dave DeLong
31
UITableViewController hatte (und hat) zu viele obskure und Nischenfehler und Probleme mit nicht trivialen Ansichtshierarchien ... die alle auf magische Weise verschwinden, wenn Sie zur Verwendung einer Standard-VC mit einer TableView-Unteransicht wechseln. "Use UITVC" ist meiner Meinung nach ein schlechter Start für jede Lösung.
Adam
@Adam Treten diese Fehler auf, wenn der 'UITableViewController' als untergeordneter Ansichtscontroller verwendet wird (wodurch der Zugriff auf die Ansichtsanpassung ermöglicht wird, ohne in der tableViewControllers-Hierarchie herumzuspielen)? Ich habe noch nie Probleme bei der Verwendung auf diese Weise festgestellt.
Memmons

Antworten:

388

Aus Ahnung und basierend auf DrummerBs Inspiration habe ich versucht, einfach eine UIRefreshControlInstanz als Unteransicht zu meiner hinzuzufügen UITableView. Und es funktioniert auf magische Weise!

UIRefreshControl *refreshControl = [[UIRefreshControl alloc] init];
[refreshControl addTarget:self action:@selector(handleRefresh:) forControlEvents:UIControlEventValueChanged];
[self.myTableView addSubview:refreshControl];

Dies fügt eine UIRefreshControlüber Ihrer Tabellenansicht hinzu und funktioniert wie erwartet, ohne ein UITableViewController:) verwenden zu müssen


BEARBEITEN: Dies funktioniert immer noch, aber wie einige darauf hingewiesen haben, gibt es ein leichtes "Stottern", wenn das UIRefreshControl auf diese Weise hinzugefügt wird. Eine Lösung hierfür besteht darin, einen UITableViewController zu instanziieren und dann Ihr UIRefreshControl und UITableView darauf einzustellen, dh:

UITableViewController *tableViewController = [[UITableViewController alloc] init];
tableViewController.tableView = self.myTableView;

self.refreshControl = [[UIRefreshControl alloc] init];
[self.refreshControl addTarget:self action:@selector(getConnections) forControlEvents:UIControlEventValueChanged];
tableViewController.refreshControl = self.refreshControl;
Keller
quelle
48
Zu Ihrer Information, dies ist ein nicht unterstütztes Verhalten und kann sich in Zukunft ändern. Die einzige Garantie, die Apple gibt, ist, dass die Verwendung gemäß der bereitgestellten API (in diesem Fall -[UITableViewController setRefreshControl:]) weiterhin funktioniert.
Dave DeLong
18
Bei dieser Implementierung scheint sich unmittelbar vor dem Auslösen handleRefresh:für den Bruchteil contentInsetseiner Sekunde eine unerwartete Wertänderung der Bildlaufansicht zu ergeben . Hat dies noch jemand erlebt oder eine Lösung dafür gefunden? (Ja, ich weiß, dass dies überhaupt nicht unterstützt wird!)
Tim
6
Sie sollten hierfür einfach eine Containeransicht verwenden. Setzen Sie einfach die Klasse des View Controllers auf Ihre benutzerdefinierte Unterklasse von UITableViewControllerund Sie werden "es richtig machen".
Eugene
3
In iOS7 (unbekannt für iOS6) funktioniert die bearbeitete Version einwandfrei, außer wenn Sie die App schließen und erneut öffnen. Die Animation wird erst beim zweiten Aktualisieren abgespielt. Beim ersten Aktualisieren nach dem erneuten Öffnen der App wird nur ein vollständiger Kreis anstelle des inkrementierenden Kreises angezeigt, je nachdem, wie weit Sie die App nach unten ziehen. Gibt es eine Lösung?
David2391
30
Um zu vermeiden, dass der oben erwähnte "Slutter" auftritt, während der UITableViewController weiterhin umgangen wird, fügen Sie einfach das Aktualisierungssteuerelement unter der Tabellenansicht wie folgt hinzu : [_tableView insertSubview:_refreshControl atIndex:0];. Getestet mit iOS 7 und 8;)
ptitvinou
95

Um das durch die akzeptierte Antwort verursachte Ruckeln zu beseitigen, können Sie Ihr UITableViewa zuweisen UITableViewController.

_tableViewController = [[UITableViewController alloc]initWithStyle:UITableViewStylePlain];
[self addChildViewController:_tableViewController];

_tableViewController.refreshControl = [UIRefreshControl new];
[_tableViewController.refreshControl addTarget:self action:@selector(loadStream) forControlEvents:UIControlEventValueChanged];

_theTableView = _tableViewController.tableView;

BEARBEITEN:

Eine Möglichkeit, ein UIRefreshControlNein UITableViewControllerohne Stottern hinzuzufügen und die schöne Animation nach dem Aktualisieren der Daten in der Tabellenansicht beizubehalten.

UIRefreshControl *refreshControl = [UIRefreshControl new];
[refreshControl addTarget:self action:@selector(handleRefresh:) forControlEvents:UIControlEventValueChanged];
[self.theTableView addSubview:refreshControl];
[self.theTableView sendSubviewToBack:refreshControl];

Später beim Umgang mit den aktualisierten Daten ...

- (void)handleRefresh:(UIRefreshControl *)refreshControl {
    [self.theTableView reloadData];
    [self.theTableView layoutIfNeeded];
    [refreshControl endRefreshing];
}
Piotr Tomasik
quelle
7
Sie müssen nicht einmal eine neue UITableView erstellen. Sagen Sie einfach initWithStyle: existentTableView.style - und führen Sie anschließend newTableViewController.tableView = existentTableView aus und weisen Sie refreshControl zu. Reduziert dies auf drei Zeilen.
Trenskow
Vergessen Sie nicht, [_tablewViewController didMoveToParentViewController:self];nach dem Hinzufügen der Unteransicht anzurufen.
Qix
4
self.tableView = _tableViewController.tableView;sollte sein_tableViewController.tableView = self.tableView
Ali
@Linus warum ist das nötig? Ich habe _tableViewController.view als Unteransicht in die viewDidLoad-Methode meines benutzerdefinierten viewController eingefügt, und das schien den Trick zu tun. Ich musste nicht einmal _tableViewController.tableView
taber
Der Grund, warum ich keinen UITableViewController verwenden kann und einen UIViewController mit einer darin enthaltenen Tabellenansicht verwende. stackoverflow.com/questions/18900428/…
lostintranslation
21

Sie würden versuchen, die Containeransicht in dem von Ihnen verwendeten ViewController zu verwenden. Sie können eine saubere UITableViewController-Unterklasse mit dedizierter Tabellenansicht definieren und diese in den ViewController einfügen.

Tomohisa Takaoka
quelle
2
Genau genommen scheint der sauberste Weg darin zu bestehen, einen untergeordneten Ansichts-Controller vom Typ UITableViewController hinzuzufügen.
Zdenek
Sie können dann den UITableViewController mit abrufen self.childViewControllers.firstObject.
cbh2000
^ Möglicherweise möchten Sie auch verwenden prepareForSegue, um einen Verweis auf das UITableViewController-Objekt in einer Containeransicht abzurufen, da dies bei der Instanziierung aus dem Storyboard sofort als Segue-Ereignis ausgelöst wird.
Crarho
18

Nun, UIRefreshControl ist eine UIView-Unterklasse, sodass Sie sie alleine verwenden können. Ich bin mir allerdings nicht sicher, wie es sich selbst wiedergibt. Das Rendern kann einfach vom Frame abhängen, aber auch von einem UIScrollView oder dem UITableViewController.

In jedem Fall wird es eher ein Hack als eine elegante Lösung sein. Ich empfehle Ihnen, sich einen der verfügbaren Klone von Drittanbietern anzusehen oder einen eigenen zu schreiben.

ODRefreshControl

Geben Sie hier die Bildbeschreibung ein

SlimeRefresh

Geben Sie hier die Bildbeschreibung ein

SchlagzeugerB
quelle
1
Vielen Dank für Ihre Antwort, aber nicht genau das, wonach ich gesucht habe. Der erste Satz Ihres Beitrags hat mich jedoch dazu inspiriert, zu versuchen, was letztendlich die Lösung war!
Keller
7
ODRefreshControl ist großartig - danke für den Tipp! Sehr einfach und macht den Job. Und natürlich viel flexibler als bei Apple. Ich habe nie verstanden, warum jemand UITableViewController verwenden würde, IMO die schlechteste Klasse in der gesamten API. Es macht (fast) nichts, macht aber Ihre Ansichten unflexibel.
n13
Es hängt tatsächlich von Ihrem Projekt ab, was Sie dort verwenden. Die Verwendung einer uitableview in meinem Fall erhöhte die Komplexität des Projekts und jetzt wechselte ich zu uitableviewcontroller, der meinen Code in zwei Segmente aufteilte. Ich bin froh, dass ich 2 kleinere Dateien habe, die das können verwendet werden, um anstelle einer großen Datei zu debuggen ..
Kush
@ n13 Hallo, ein guter Grund für die Verwendung von UITableViewController ist das Entwerfen mit statischen Zellen. Leider nicht verfügbar in einer tableView, die sich in einem UIViewController befindet.
Jean Le Moignan
@JeanLeMoignanDie iOS-Tools und -Bibliotheken ändern sich schnell, sodass ein 3 Jahre alter Kommentar von mir nicht mehr gültig ist. Ich habe auf das Erstellen aller Benutzeroberflächen in Code mit SnapKit umgestellt und ehrlich gesagt ist es enorm effizienter, als im Interface Builder zehntausend Mal zu klicken. Auch das Ändern eines UITableViewControllers in einen UIViewController ist einfach und dauert nur eine Sekunde, während es bei IB ein großer Schmerz ist.
n13
7

Versuchen Sie, den Aufruf der refreshControl- -endRefreshMethode um einen Bruchteil einer Sekunde zu verzögern, nachdem die tableView ihren Inhalt neu geladen hat, entweder mithilfe von NSObjects -performSelector:withObject:afterDelay:oder GCDs dispatch_after.

Ich habe dafür eine Kategorie auf UIRefreshControl erstellt:

@implementation UIRefreshControl (Delay)

- (void)endRefreshingAfterDelay:(NSTimeInterval)delay {
    dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delay * NSEC_PER_SEC));
    dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
        [self endRefreshing];
    });
}

@end

Ich habe es getestet und dies funktioniert auch in Sammlungsansichten. Mir ist aufgefallen, dass eine Verzögerung von nur 0,01 Sekunden ausreicht:

// My data refresh process here while the refresh control 'isRefreshing'
[self.tableView reloadData];
[self.refreshControl endRefreshingAfterDelay:.01];
Boliva
quelle
4
Verzögerungen und Wartezeiten sind wirklich schlechter Stil.
Orkoden
2
@orkoden Ich stimme zu. Dies ist eine Problemumgehung für eine bereits nicht unterstützte Verwendung von UIRefreshControl.
Boliva
Sie können eine Methode mit einer Verzögerung viel einfacher aufrufen. [self performSelector:@selector(endRefreshing) withObject:nil afterDelay:0.01]
Orkoden
2
Der Endeffekt ist genau der gleiche und macht den "Hack" nicht mehr / weniger elegant. Ob dies -performSelector:withObject:afterDelay:oder GCD verwendet wird, hängt vom persönlichen Geschmack ab.
Boliva
7

IOS 10 Swift 3.0

Es ist einfach

import UIKit

class ViewControllerA: UIViewController, UITableViewDataSource, UITableViewDelegate {

    @IBOutlet weak var myTableView: UITableView!

    override func viewDidLoad() {
        super.viewDidLoad()

        myTableView.delegate = self
        myTableView.dataSource = self

        if #available(iOS 10.0, *) {
            let refreshControl = UIRefreshControl()
            let title = NSLocalizedString("PullToRefresh", comment: "Pull to refresh")
            refreshControl.attributedTitle = NSAttributedString(string: title)
            refreshControl.addTarget(self,
                                     action: #selector(refreshOptions(sender:)),
                                     for: .valueChanged)
            myTableView.refreshControl = refreshControl
        }
    }

    @objc private func refreshOptions(sender: UIRefreshControl) {
        // Perform actions to refresh the content
        // ...
        // and then dismiss the control
        sender.endRefreshing()
    }

    // MARK: - Table view data source

    func numberOfSections(in tableView: UITableView) -> Int {
        return 1
    }

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return 12
    }


    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "reuseIdentifier", for: indexPath)

        cell.textLabel?.text = "Cell \(String(indexPath.row))"
        return cell
    }

}

Geben Sie hier die Bildbeschreibung ein

Wenn Sie mehr über iOS 10 UIRefreshControl erfahren möchten, lesen Sie hier .

Ashok R.
quelle
3

Durch Hinzufügen des Aktualisierungssteuerelements als Unteransicht wird ein leerer Bereich über den Abschnittsüberschriften erstellt.

Stattdessen habe ich einen UITableViewController in meinen UIViewController eingebettet und dann meine tableViewEigenschaft so geändert , dass sie auf die eingebettete zeigt, und Viola! Minimale Codeänderungen. :-)

Schritte:

  1. Erstellen Sie einen neuen UITableViewController im Storyboard und binden Sie ihn in Ihren ursprünglichen UIViewController ein
  2. Ersetzen Sie ihn @IBOutlet weak var tableView: UITableView!durch den neu eingebetteten UITableViewController (siehe unten)

class MyViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
    weak var tableView: UITableView!

    override func viewDidLoad() {
        super.viewDidLoad()

        let tableViewController = self.childViewControllers.first as! UITableViewController
        tableView = tableViewController.tableView
        tableView.dataSource = self
        tableView.delegate = self

        // Now we can (properly) add the refresh control
        let refreshControl = UIRefreshControl()
        refreshControl.addTarget(self, action: "handleRefresh:", forControlEvents: .ValueChanged)
        tableViewController.refreshControl = refreshControl
    }

    ...
}
cbh2000
quelle
2

Für Swift 2.2.

Machen Sie zuerst UIRefreshControl ().

var refreshControl : UIRefreshControl!

Fügen Sie in Ihrer viewDidLoad () -Methode Folgendes hinzu:

refreshControl = UIRefreshControl()
    refreshControl.attributedTitle = NSAttributedString(string: "Refreshing..")
    refreshControl.addTarget(self, action: #selector(YourUIViewController.refresh(_:)), forControlEvents: UIControlEvents.ValueChanged)
    self.tableView.addSubview(refreshControl)

Und Aktualisierungsfunktion erstellen

func refresh(refreshControl: UIRefreshControl) {

    // do something ...

    // reload tableView
    self.tableView.reloadData()

    // End refreshing
    refreshControl.endRefreshing()
}
stakahop
quelle
0

Hier ist eine andere Lösung, die etwas anders ist.

Ich musste es aufgrund einiger Probleme mit der Ansichtshierarchie verwenden: Ich erstellte einige Funktionen, die das Weitergeben von Ansichten an verschiedene Stellen in der Ansichtshierarchie erforderten, was bei Verwendung der Tabellenansicht eines UITableViewController fehlerhaft war. B / c Die Tabellenansicht ist die Stammansicht des UITableViewController ( self.view) und nicht nur eine reguläre Ansicht, sondern inkonsistente Controller- / Ansichtshierarchien erstellt und einen Absturz verursacht.

Erstellen Sie im Grunde eine eigene Unterklasse von UITableViewController und überschreiben Sie loadView, um self.view eine andere Ansicht zuzuweisen, und überschreiben Sie die tableView-Eigenschaft, um eine separate Tabellenansicht zurückzugeben.

beispielsweise:

@interface MyTableVC : UITableViewController
@end

@interface MyTableVC ()
@property (nonatomic, strong) UITableView *separateTableView;
@end

@implementation MyTableVC

- (void)loadView {
    self.view = [[UIView alloc] initWithFrame:CGRectZero];
}

- (UITableView *)tableView {
    return self.separateTableView;
}

- (void)setTableView:(UITableView *)tableView {
    self.separateTableView = tableView;
}

@end

In Kombination mit Kellers Lösung ist dies robuster in dem Sinne, dass die tableView jetzt eine reguläre Ansicht ist, nicht die Stammansicht eines VC, und robuster gegen sich ändernde Ansichtshierarchien ist. Beispiel für die Verwendung auf diese Weise:

MyTableVC *tableViewController = [[MyTableVC alloc] init];
tableViewController.tableView = self.myTableView;

self.refreshControl = [[UIRefreshControl alloc] init];
[self.refreshControl addTarget:self action:@selector(getConnections) forControlEvents:UIControlEventValueChanged];
tableViewController.refreshControl = self.refreshControl;

Es gibt eine andere mögliche Verwendung dafür:

Da die Unterklasse auf diese Weise self.view von self.tableView trennt, ist es jetzt möglich, diesen UITableViewController eher als regulären Controller zu verwenden und self.view andere Unteransichten hinzuzufügen, ohne dass UITableView seltsamerweise Unteransichten hinzugefügt werden muss Zeigen Sie Controller direkt als Unterklasse von UITableViewController an, anstatt UITableViewController-Kinder zu haben.

Einige Dinge, auf die Sie achten sollten:

Da wir die tableView-Eigenschaft überschreiben, ohne super aufzurufen, müssen möglicherweise einige Dinge beachtet werden, die bei Bedarf behandelt werden sollten. Wenn Sie beispielsweise die Tabellenansicht in meinem obigen Beispiel festlegen, wird die Tabellenansicht nicht zu self.view hinzugefügt und der Rahmen, den Sie möglicherweise ausführen möchten, nicht festgelegt. In dieser Implementierung wird Ihnen auch keine Standard-Tabellenansicht gegeben, wenn die Klasse instanziiert wird. Dies können Sie auch hinzufügen. Ich füge es hier nicht ein, weil das von Fall zu Fall ist und diese Lösung tatsächlich gut zu Kellers Lösung passt.

Psilencer
quelle
1
Die Frage lautet "ohne Verwendung einer UITableViewController-Unterklasse", und diese Antwort bezieht sich auf eine UITableViewController-Unterklasse.
Brian
0

Versuche dies,

Die oben genannten Lösungen sind in Ordnung, aber tableView.refreshControl ist nur für UITableViewController bis iOS 9.x verfügbar und ab iOS 10.x in UITableView verfügbar.

Geschrieben in Swift 3 -

let refreshControl = UIRefreshControl()
refreshControl.addTarget(self, action: #selector(FeedViewController.loadNewData), for: UIControlEvents.valueChanged)
// Fix for the RefreshControl Not appearing in background
tableView?.addSubview(refreshControl)
tableView.sendSubview(toBack: refreshControl)
Ankit Kumar Gupta
quelle
Beste Lösung bisher
Karthik KM
-1

Kellers erster Vorschlag verursacht einen seltsamen Fehler in iOS 7, bei dem der Einschub der Tabelle vergrößert wird, nachdem der View Controller wieder angezeigt wird. Der Wechsel zur zweiten Antwort mit dem uitableviewcontroller hat die Dinge für mich behoben.

Mark Bridges
quelle
-6

Es stellt sich heraus, dass Sie Folgendes verwenden können, wenn Sie einen UIViewController mit einer UITableView-Unteransicht verwenden und UITableViewDataSource und UITableViewDelegate entsprechen:

self.refreshControl = [[UIRefreshControl alloc]init];
[self.refreshControl addTarget:self action:@selector(refresh:) forControlEvents:UIControlEventValueChanged];
Clint Pick
quelle
9
Nicht zustimmen. self.refreshControlist eine Eigenschaft für a UITableViewController, nicht für a UIViewController.
Rickster
NICHT FUNKTIONIEREND, referenzhControl ist nicht für uiviewcontroller definiert
OMGPOP