web-dev-qa-db-fra.com

UIRefreshControl - beginRefreshing ne fonctionne pas lorsque UITableViewController est dans UINavigationController

J'ai configuré un UIRefreshControl dans mon UITableViewController (qui se trouve à l'intérieur d'un UINavigationController) et il fonctionne comme prévu (c'est-à-dire que le fait de tirer le bas déclenche l'événement correct). Cependant, si j'appelle par programme la méthode d'instance beginRefreshing sur le contrôle d'actualisation, comme suit:

[self.refreshControl beginRefreshing];

Rien ne se passe. Il devrait animer et montrer le spinner. La méthode endRefreshing fonctionne correctement lorsque je l’appelle après l’actualisation.

J'ai conçu un prototype de projet de base avec ce comportement et il fonctionne correctement lorsque mon UITableViewController est ajouté directement au contrôleur de vue racine du délégué de l'application, par exemple:

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

Mais si j'ajoute d'abord la tableViewController à un UINavigationController, puis le contrôleur de navigation en tant que rootViewController, la méthode beginRefreshing ne fonctionne plus. Par exemple.

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

Mon sentiment est que cela a quelque chose à voir avec les hiérarchies de vues imbriquées dans le contrôleur de navigation ne jouant pas à Nice avec le contrôle de rafraîchissement - des suggestions?

Merci 

105
wows

Il semble que si vous commencez à actualiser par programme, vous devez faire défiler la vue du tableau vous-même, par exemple, en modifiant contentoffset

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

J'imagine que la raison en est qu'il ne serait pas souhaitable de faire défiler le contrôle d'actualisation lorsque l'utilisateur est au milieu/en bas de la vue du tableau.

Swift 2.2 version de @muhasturk

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

En un mot, pour garder ce portable, ajoutez cette extension

UIRefreshControl + ProgramicallyBeginRefresh.Swift

extension UIRefreshControl {
    func programaticallyBeginRefreshing(in tableView: UITableView) {
        beginRefreshing()
        let offsetPoint = CGPoint.init(x: 0, y: -frame.size.height)
        tableView.setContentOffset(offsetPoint, animated: true)        
    }
}
167
Dmitry Shevchenko

UITableViewController a automatiquement les propriétés /AdjustsScrollViewInsets après iOS 7. La vue de table peut déjà avoir contentOffset, généralement (0, -64).

Par conséquent, la meilleure façon d’afficher refreshControl après l’actualisation de la programmation consiste à ajouter la hauteur de refreshControl à contentOffset existant.

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

Voici une extension Swift utilisant les stratégies décrites ci-dessus.

extension UIRefreshControl {
    func beginRefreshingManually() {
        if let scrollView = superview as? UIScrollView {
            scrollView.setContentOffset(CGPoint(x: 0, y: scrollView.contentOffset.y - frame.height), animated: true)
        }
        beginRefreshing()
    }
}
27
Chris Wagner

Aucune des autres réponses n'a fonctionné pour moi. Cela ferait voir le fileur apparaître et tourner, mais l'action de rafraîchissement elle-même ne se produirait jamais. Cela marche:

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

Notez que cet exemple utilise une vue de tableau, mais il aurait tout aussi bien pu s'agir d'une vue de collection.

12
Kyle Robson

L'approche déjà mentionnée:

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

rendrait la spinner visible. Mais ça n'animerait pas. La seule chose que j'ai changée est l'ordre de ces deux méthodes et tout a fonctionné:

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

Voir aussi cette question 

UIRefreshControl ne s'affiche pas lorsque vous appelez beginRefreshing et contentOffset a la valeur 0

Cela ressemble à un bogue pour moi, car il ne se produit que lorsque la propriété contentOffset de la table est 0

J'ai corrigé cela avec le code suivant (méthode pour 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){

        }];

    }
}
7
Peter Lapisu

Pour Swift 4/4.1

Un mélange de réponses existantes fait le travail pour moi:

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

J'espère que cela t'aides!

5
Isaac Bosca

Cela fonctionne parfaitement pour moi:

Swift 3:

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

Voici l'extension Swift 3 qui montre spinner et l'anime.

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()
      }
   }
}
4
Ghulam Rasool

En plus de la solution @Dymitry Shevchenko.

J'ai trouvé une solution de contournement de Nice à ce problème. Vous pouvez créer une extension à UIRefreshControl qui écrase la méthode:

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

Vous pouvez utiliser une nouvelle classe en définissant une classe personnalisée dans Inspecteur d'identité pour l'actualisation du contrôle dans Interface Builder.

3
lyzkov

Fort Swift 2.2+

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

Si vous utilisez Rxswift pour Swift 3.1, vous pouvez utiliser ci-dessous:

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

Ce travail pour Swift 3.1, iOS 10.

3
jkyin

Pour Swift 5, la seule chose qui me manquait était d'appeler refreshControl.sendActions(.valueChanged). J'ai fait une extension pour le rendre plus propre.

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

}
0
LukasHromadnik

J'utilise la même technique pour montrer l'utilisateur "les données sont mises à jour" signe visuel. Un utilisateur résultant apporte une application de l’arrière-plan et les flux/listes seront mis à jour avec une interface utilisateur, comme les utilisateurs extraient des tables pour se rafraîchir. Ma version contient 3 choses

1) Qui envoie "réveille-toi"

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

2) Observateur dans UIViewController

- (void)viewDidLoad {
    [super viewDidLoad];

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

3) le protocole

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

 result

0
WINSergey