UIRefreshControl sans UITableViewController

308

Juste curieux, car cela ne semble pas immédiatement possible, mais existe-t-il un moyen sournois de tirer parti de la nouvelle UIRefreshControlclasse iOS 6 sans utiliser de UITableViewControllersous-classe?

J'utilise souvent un UIViewControlleravec une sous- UITableViewvue et je me conforme UITableViewDataSourceet UITableViewDelegateplutôt que d'utiliser UITableViewControllercarrément un .

Keller
la source
6
@DaveDeLong: Non, Dave, que doit-il faire? Plus loin sur cette page, vous dites que sa solution n'est pas prise en charge, alors quelle est la bonne solution?
mat
3
@matt il devrait utiliser un UITableViewControllerfichier et déposer un bug demandant une API pour utiliser un UIRefreshControlavec un UITableViewdirectement.
Dave DeLong
31
UITableViewController a eu (et continue d'avoir) trop de bugs obscurs et de niche et de problèmes avec les hiérarchies de vues non triviales ... qui disparaissent tous comme par magie lorsque vous passez à l'utilisation d'un VC standard avec une sous-vue TableView. "Utiliser UITVC" est un mauvais début à toute solution, à mon humble avis.
Adam
@Adam Ces bogues apparaissent-ils lors de l'utilisation de 'UITableViewController' comme contrôleur de vue enfant (donnant ainsi accès à la personnalisation de la vue sans contourner la hiérarchie de tableViewControllers)? Je n'ai jamais rencontré de problèmes lors de son utilisation de cette façon.
memmons
Copie
James Moore

Réponses:

388

Sur une intuition, et basé sur l'inspiration de DrummerB, j'ai essayé d'ajouter simplement une UIRefreshControlinstance comme sous-vue à mon UITableView. Et ça fonctionne comme par magie!

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

Cela ajoute une vue UIRefreshControlau - dessus de votre table et fonctionne comme prévu sans avoir à utiliser un UITableViewController:)


EDIT: Ceci ci-dessus fonctionne toujours mais comme certains l'ont souligné, il y a un léger "bégaiement" lors de l'ajout de l'UIRefreshControl de cette manière. Une solution à cela consiste à instancier un UITableViewController, puis à définir votre UIRefreshControl et UITableView à cela, c'est-à-dire:

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
la source
48
Pour info, ce comportement n'est pas pris en charge et peut changer à l'avenir. La seule garantie offerte par Apple est que son utilisation conformément à l'API fournie (dans ce cas -[UITableViewController setRefreshControl:]) continuera de fonctionner.
Dave DeLong
18
Avec cette implémentation, il semble que juste avant le déclenchement, handleRefresh:il y ait un changement inattendu de la valeur de la vue de défilement contentInsetspendant une fraction de seconde. Quelqu'un d'autre en fait l'expérience ou a-t-il une solution? (yup, je sais que ce n'est pas pris en charge en premier lieu!)
Tim
6
Vous devriez vraiment utiliser simplement une vue de conteneur pour cela. Définissez simplement la classe de son contrôleur de vue sur votre sous-classe personnalisée UITableViewControlleret vous "le ferez bien".
Eugene
3
Dans iOS7 (inconnu pour iOS6), la version modifiée fonctionne correctement, sauf lorsque vous fermez et rouvrez l'application. L'animation n'est pas lue jusqu'à la 2e fois que vous actualisez. La première fois que vous avez actualisé l'application après la réouverture, c'est juste un cercle complet au lieu du cercle d'incrémentation en fonction de la distance à laquelle vous la tirez. Y a-t-il une solution?
david2391
30
Pour éviter d' avoir la « Slutter » , comme mentionné ci - dessus tout en contournant encore le UITableViewController, ajoutez simplement le contrôle de rafraîchissement sous la vue de la table comme ceci: [_tableView insertSubview:_refreshControl atIndex:0];. Testé avec iOS 7 et 8;)
ptitvinou
95

Pour éliminer le bégaiement provoqué par la réponse acceptée, vous pouvez attribuer votre UITableViewà a 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;

ÉDITER:

Une façon d'ajouter un UIRefreshControlsans UITableViewControlleravec aucun bégaiement et de conserver la belle animation après avoir actualisé les données sur la table.

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

Plus tard lors de la manipulation des données actualisées ...

- (void)handleRefresh:(UIRefreshControl *)refreshControl {
    [self.theTableView reloadData];
    [self.theTableView layoutIfNeeded];
    [refreshControl endRefreshing];
}
Piotr Tomasik
la source
7
Vous n'avez même pas besoin de créer une nouvelle UITableView. Dites simplement initWithStyle: existingTableView.style - et ensuite faites newTableViewController.tableView = existingTableView et affectez refreshControl. Cela se résume à trois lignes.
Trenskow
N'oubliez pas d'appeler [_tablewViewController didMoveToParentViewController:self];après avoir ajouté la sous-vue.
qix
4
self.tableView = _tableViewController.tableView;devrait être_tableViewController.tableView = self.tableView
Ali
@Linus pourquoi est-ce nécessaire? J'ai ajouté _tableViewController.view en tant que sous-vue dans la méthode viewDidLoad de mon viewController personnalisé et cela semblait faire l'affaire. Je n'ai même pas eu besoin de définir _tableViewController.tableView
taber
La raison pour laquelle je ne peux pas utiliser un UITableViewController et que j'utilise un UIViewController avec une vue de table à l'intérieur. stackoverflow.com/questions/18900428/…
lostintranslation
21

Ce que vous essayez d'utiliser, c'est d'utiliser la vue conteneur dans ViewController que vous utilisez. vous pouvez définir une sous-classe UITableViewController propre avec une vue de table dédiée et la placer dans ViewController.

Tomohisa Takaoka
la source
2
Précisément, la manière la plus propre semble être d'ajouter un contrôleur de vue enfant de type UITableViewController.
Zdenek
Vous pouvez ensuite récupérer le UITableViewController à l'aide de self.childViewControllers.firstObject.
cbh2000
^ vous pouvez également envisager d'utiliser prepareForSeguepour obtenir une référence à l'objet UITableViewController dans une vue de conteneur, car il est immédiatement déclenché en tant qu'événement de séquence lors de l'instanciation du storyboard.
crarho
18

Eh bien, UIRefreshControl est une sous-classe UIView, vous pouvez donc l'utiliser seule. Je ne sais pas trop comment cela se rend. Le rendu pourrait simplement dépendre de la trame, mais il pourrait également s'appuyer sur un UIScrollView ou le UITableViewController.

Quoi qu'il en soit, cela va être plus un hack qu'une solution élégante. Je vous recommande de regarder dans l'un des clones tiers disponibles ou d'écrire le vôtre.

ODRefreshControl

entrez la description de l'image ici

SlimeRefresh

entrez la description de l'image ici

DrummerB
la source
1
Merci pour votre réponse, mais pas exactement ce que je cherchais. La première phrase de votre message m'a cependant inspiré à essayer ce qui a finalement été la solution!
Keller
7
ODRefreshControl est génial - merci pour le conseil! Très simple et fait le travail. Et bien sûr beaucoup plus flexible que celui d'Apple. Je n'ai jamais compris pourquoi quelqu'un utiliserait UITableViewController, IMO la pire classe de toute l'API. Cela ne fait (presque) rien, mais rend vos points de vue inflexibles.
n13
Cela dépend en fait de votre projet, de ce que vous utilisez là-bas. L'utilisation d'une uitableview dans mon cas augmentait la complexité du projet et maintenant je suis passé à uitableviewcontroller qui sorte de diviser mon code en deux segments je suis heureux d'avoir 2 fichiers plus petits qui peuvent être utilisé pour déboguer au lieu d'un énorme fichier ..
kush
@ n13 Bonjour, une bonne raison d'utiliser UITableViewController est de pouvoir concevoir avec des cellules statiques. Malheureusement indisponible dans une tableView résidant dans un UIViewController.
Jean Le Moignan
@JeanLeMoignanLes outils et les bibliothèques iOS changent rapidement, donc un de mes commentaires de 3 ans n'est plus valide. Je suis passé à la création de toutes les interfaces utilisateur en code avec SnapKit et honnêtement, c'est extrêmement plus efficace que de cliquer 10 000 fois dans le générateur d'interface. Changer également UITableViewController en UIViewController est facile et ne prend qu'une seconde, alors qu'en IB, c'est très difficile.
n13
7

Essayez de retarder l'appel à la -endRefreshméthode refreshControl d'une fraction de seconde après que la tableView a rechargé son contenu, en utilisant NSObject -performSelector:withObject:afterDelay:ou GCD.dispatch_after .

J'ai créé une catégorie sur UIRefreshControl pour cela:

@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

Je l'ai testé et cela fonctionne également sur les vues de collection. J'ai remarqué qu'un délai aussi petit que 0,01 seconde suffit:

// My data refresh process here while the refresh control 'isRefreshing'
[self.tableView reloadData];
[self.refreshControl endRefreshingAfterDelay:.01];
boliva
la source
4
Les retards et les attentes sont vraiment un mauvais style.
orkoden du
2
@orkoden Je suis d'accord. Il s'agit d'une solution de contournement pour une utilisation déjà non prise en charge de UIRefreshControl.
boliva
Vous pouvez appeler une méthode avec un retard beaucoup plus facilement avec. [self performSelector:@selector(endRefreshing) withObject:nil afterDelay:0.01]
orkoden
2
L'effet final est exactement le même et ne rend pas le «hack» plus / moins élégant. Que ce soit pour utiliser -performSelector:withObject:afterDelay:ou GCD, c'est une question de goût personnel.
boliva
7

IOS 10 Swift 3.0

c'est simple

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
    }

}

entrez la description de l'image ici

Si vous souhaitez en savoir plus sur iOS 10 UIRefreshControl, lisez ici .

Ashok R
la source
3

L'ajout du contrôle d'actualisation en tant que sous-vue crée un espace vide au-dessus des en-têtes de section.

Au lieu de cela, j'ai incorporé un UITableViewController dans mon UIViewController, puis j'ai changé mon tableView propriété pour pointer vers celle intégrée, et l'alto! Modifications minimales du code. :-)

Pas:

  1. Créez un nouveau UITableViewController dans Storyboard et intégrez-le dans votre UIViewController d'origine
  2. Remplacez-le @IBOutlet weak var tableView: UITableView!par celui du UITableViewController nouvellement intégré, comme indiqué ci-dessous

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
la source
2

Pour Swift 2.2.

Créez d'abord UIRefreshControl ().

var refreshControl : UIRefreshControl!

Dans votre méthode viewDidLoad (), ajoutez:

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

Et faire une fonction de rafraîchissement

func refresh(refreshControl: UIRefreshControl) {

    // do something ...

    // reload tableView
    self.tableView.reloadData()

    // End refreshing
    refreshControl.endRefreshing()
}
stakahop
la source
0

Voici une autre solution qui est un peu différente.

J'ai dû l'utiliser à cause de certains problèmes de hiérarchie de vues que j'avais: je créais des fonctionnalités qui nécessitaient de passer des vues à différents endroits de la hiérarchie de vues, qui se cassaient lors de l'utilisation d'une tableview UITableViewController b / c la tableView est la vue racine de UITableViewController ( self.view) et pas seulement une vue régulière, elle a créé des hiérarchies contrôleur / vue incohérentes et a provoqué un plantage.

Fondamentalement, créez votre propre sous-classe de UITableViewController et redéfinissez loadView pour affecter self.view une autre vue, et redéfinissez la propriété tableView pour renvoyer une autre table.

par exemple:

@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

Lorsqu'il est combiné avec la solution de Keller, cela sera plus robuste dans le sens où la tableView est maintenant une vue régulière, et non une vue racine de VC, et sera plus robuste contre les hiérarchies de vue changeantes. Exemple d'utilisation de cette façon:

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;

Il existe une autre utilisation possible pour cela:

Étant donné que le sous-classement de cette façon sépare self.view de self.tableView, il est maintenant possible d'utiliser ce UITableViewController comme un contrôleur plus régulier et d'ajouter d'autres sous-vues à self.view sans les bizarreries d'ajouter des sous-vues à UITableView, donc on peut envisager de faire leur afficher les contrôleurs directement une sous-classe de UITableViewController au lieu d'avoir des enfants UITableViewController.

Quelques points à surveiller:

Étant donné que nous remplaçons la propriété tableView sans appeler super, il peut y avoir des choses à surveiller et à gérer si nécessaire. Par exemple, définir la table dans mon exemple ci-dessus n'ajoutera pas la table à self.view et ne définira pas le cadre que vous voudrez peut-être faire. De plus, dans cette implémentation, aucune tableView par défaut ne vous est donnée lorsque la classe est instanciée, ce que vous pouvez également envisager d'ajouter. Je ne l'inclus pas ici car c'est au cas par cas, et cette solution correspond en fait bien à la solution de Keller.

psilencer
la source
1
La question spécifiait "sans utiliser une sous-classe UITableViewController", et cette réponse est pour une sous-classe UITableViewController.
Brian
0

Essaye ça,

Les solutions ci-dessus sont correctes mais tableView.refreshControl est disponible pour UITableViewController uniquement, jusqu'à iOS 9.x et est disponible dans UITableView à partir d'iOS 10.x.

Écrit dans 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
la source
Meilleure solution jusqu'à présent
Karthik KM
-1

La première suggestion de Keller provoque un étrange bug dans iOS 7 où l'encart de la table est augmenté après la réapparition du contrôleur de vue. Passer à la deuxième réponse, en utilisant le uitableviewcontroller, a corrigé les choses pour moi.

Mark Bridges
la source
-6

Il s'avère que vous pouvez utiliser les éléments suivants lorsque vous utilisez un UIViewController avec une sous-vue UITableView et conforme à UITableViewDataSource et UITableViewDelegate:

self.refreshControl = [[UIRefreshControl alloc]init];
[self.refreshControl addTarget:self action:@selector(refresh:) forControlEvents:UIControlEventValueChanged];
Clint Pick
la source
9
Être en désaccord. self.refreshControlest une propriété pour un UITableViewController, pas un UIViewController.
Rickster
NE FONCTIONNE PAS, referhControl n'est pas défini pour uiviewcontroller
OMGPOP