UIRefreshControl - beginRefreshing nie działa, gdy UITableViewController znajduje się wewnątrz UINavigationController

120

Skonfigurowałem UIRefreshControl w moim UITableViewController (który znajduje się wewnątrz UINavigationController) i działa zgodnie z oczekiwaniami (tj. Pull down uruchamia prawidłowe zdarzenie). Jeśli jednak programowo wywołam beginRefreshingmetodę wystąpienia na kontrolce odświeżania, na przykład:

[self.refreshControl beginRefreshing];

Nic się nie dzieje. Powinien się animować i pokazywać przędzarkę. endRefreshingMetoda działa prawidłowo, gdy zgłoszę, że po odświeżeniu.

Przygotowałem podstawowy projekt prototypowy z tym zachowaniem i działa on poprawnie, gdy mój UITableViewController jest dodawany bezpośrednio do głównego kontrolera widoku delegata aplikacji, np:

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

Ale jeśli tableViewControllernajpierw dodam do UINavigationController, a następnie dodam kontroler nawigacji jako rootViewController, beginRefreshingmetoda przestanie działać. Na przykład

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

Mam wrażenie, że ma to coś wspólnego z zagnieżdżonymi hierarchiami widoków w kontrolerze nawigacyjnym, które nie działają dobrze z kontrolką odświeżającą - jakieś sugestie?

Dzięki

wows
źródło

Odpowiedzi:

195

Wygląda na to, że jeśli zaczniesz odświeżać programowo, musisz samodzielnie przewinąć widok tabeli, powiedzmy, zmieniając przesunięcie zawartości

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

Domyślam się, że powodem tego może być niepożądane przewijanie do kontrolki odświeżania, gdy użytkownik znajduje się pośrodku / u dołu widoku tabeli?

Wersja Swift 2.2 autorstwa @muhasturk

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

Krótko mówiąc, aby zachować to przenośne, dodaj to rozszerzenie

UIRefreshControl + ProgramaticallyBeginRefresh.swift

extension UIRefreshControl {
    func programaticallyBeginRefreshing(in tableView: UITableView) {
        beginRefreshing()
        let offsetPoint = CGPoint.init(x: 0, y: -frame.size.height)
        tableView.setContentOffset(offsetPoint, animated: true)        
    }
}
Dmitrij Szewczenko
źródło
6
To dziwne, w moich testach endRefreshing dostosowuje przesunięcie w razie potrzeby
Dmitrij Szewczenko
9
BTW, jeśli używasz automatycznego układu, możesz zamienić wiersz w odpowiedzi na: [self.tableView setContentOffset: CGPointMake (0, -self.topLayoutGuide.length) animated: YES];
Eric Baker
4
@EricBaker Uważam, że to nie wystarczy. Nie wszystkie UITableViewControllers pokazują paski nawigacji. Doprowadziłoby to do topLayoutGuide o długości 20 i zbyt małego przesunięcia.
Fábio Oliveira
3
@EricBaker możesz użyć: [self.tableView setContentOffset: CGPointMake (0, self.topLayoutGuide.length -self.refreshControl.frame.size.height) animated: YES];
ZYiOS
1
Kopiuj wklej działa dla mnie, co jest niesamowite. Byłem zmęczony tworzeniem scenariuszy firmy Apple.
okysabeni
78

UITableViewController ma właściwość AutomaticAdjustsScrollViewInsets po iOS 7. Widok tabeli może już mieć contentOffset, zwykle (0, -64).

Tak więc właściwym sposobem wyświetlenia refreshControl po programowym rozpoczęciu odświeżania jest dodanie wysokości refreshControl do istniejącej zawartości contentOffset.

 [self.refreshControl beginRefreshing];
 [self.tableView setContentOffset:CGPointMake(0, self.tableView.contentOffset.y-self.refreshControl.frame.size.height) animated:YES];
Mam to
źródło
cześć, dziękuję bardzo, jestem również ciekawy magicznego punktu (0, -64), który spotkałem podczas debugowania.
inix
2
@inix 20 dla wysokości paska stanu + 44 dla wysokości paska nawigacji
Igotit
1
Zamiast tego -64lepiej użyć-self.topLayoutGuide.length
Diogo T
33

Oto rozszerzenie Swift korzystające ze strategii opisanych powyżej.

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
źródło
Działa dla UITableView.
Juan Boero
10
Zalecałbym umieszczenie sendActionsForControlEvents(UIControlEvents.ValueChanged)na końcu tej funkcji, w przeciwnym razie rzeczywista logika odświeżania nie zostanie uruchomiona.
Colin Basnett
To zdecydowanie najbardziej elegancki sposób na zrobienie tego. Zawiera komentarz Colina Basnetta dla lepszej funkcjonalności. można go używać w całym projekcie, definiując go raz!
JoeGalind,
1
@ColinBasnett: Dodanie tego kodu (który jest teraz sendActions (for: UIControlEvents.valueChanged)) powoduje nieskończoną pętlę ...
Kendall Helmstetter Gelner
15

Żadna z pozostałych odpowiedzi nie działała dla mnie. Spowodowałyby, że przędzarka pokazywała się i obracała, ale sama akcja odświeżania nigdy by się nie wydarzyła. To działa:

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

Uwaga, w tym przykładzie użyto widoku tabeli, ale równie dobrze mógłby to być widok kolekcji.

Kyle Robson
źródło
To rozwiązało mój problem. Ale teraz używam SVPullToRefresh, jak to programowo ściągnąć?
Gank
1
@Gank Nigdy nie korzystałem z SVPullToRefresh. Czy próbowałeś czytać ich dokumenty? Na podstawie dokumentacji wydaje się dość oczywiste, że można to pobrać programowo: „Jeśli chcesz programowo uruchomić odświeżanie (na przykład w viewDidAppear :), możesz to zrobić za pomocą: [tableView triggerPullToRefresh];” Zobacz: github.com/samvermette/ SVPullToRefresh
Kyle Robson
1
Idealny! Dziwnie działało to dla mnie z animacją, więc po prostu zastąpiłem jąscrollView.contentOffset = CGPoint(x: 0, y: scrollView.contentOffset.y - frame.height)
user1366265
13

Wspomniane już podejście:

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

sprawi, że przędzarka będzie widoczna. Ale to się nie animowało. Zmieniłem tylko kolejność tych dwóch metod i wszystko działało:

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

Dla Swift 4 / 4.1

Mieszanka istniejących odpowiedzi działa za mnie:

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

Mam nadzieję że to pomoże!

Isaac Bosca
źródło
7

Zobacz także to pytanie

UIRefreshControl nie wyświetla spiny podczas wywoływania funkcji beginRefreshing i contentOffset równej 0

Dla mnie wygląda to na błąd, ponieważ występuje tylko wtedy, gdy właściwość contentOffset tableView ma wartość 0

Naprawiłem to za pomocą następującego kodu (metoda dla 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
źródło
1
To nieprawda, mam dwa różne kontrolery UIView, z których oba mają wartość contentOffset równą 0 po wyświetleniuDidLoad i jeden z nich poprawnie pobiera kontrolkę refreshControl po wywołaniu funkcji [self.refreshControl beginRefreshing], a drugi nie: /
simonthumper
Dokumentacja nie mówi nic o wyświetlaniu kontrolki beginRefreshing, tylko że zmienia się jej stan. Jak widzę, ma to na celu zapobieżenie dwukrotnemu zainicjowaniu akcji odświeżania, tak aby programowo nazywany odświeżanie nadal działał, akcja zainicjowana przez użytkownika nie rozpocznie kolejnej.
Koen.
Zauważyłem problemy z używaniem metody setContentOffset: animated, więc to rozwiązanie zadziałało.
Marc Etcheverry
7

Oto rozszerzenie Swift 3 i nowsze, które pokazuje spinner, a także jego animację.

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
źródło
2
Ze wszystkich odpowiedzi powyżej, była to jedyna, którą mogłem uzyskać pracując na iOS 11.1 / xcode 9.1
Bassebus
1
To asyncAfterwłaśnie sprawia, że ​​animacja spinner działa (iOS 12.3 / Xcode 10.2)
francybiga
4

U mnie działa idealnie:

Swift 3:

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

W przypadku Swift 5 brakowało mi tylko połączenia refreshControl.sendActions(.valueChanged). Zrobiłem rozszerzenie, aby było bardziej przejrzyste.

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
źródło
3

Oprócz rozwiązania @Dymitry Shevchenko.

Znalazłem dobre obejście tego problemu. Możesz utworzyć rozszerzenie UIRefreshControltej metody nadpisywania:

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

Możesz użyć nowej klasy, ustawiając klasę niestandardową w Inspektorze tożsamości do sterowania odświeżaniem w programie Interface Builder.

lyzkov
źródło
3

Fort Swift 2.2+

    self.tableView.setContentOffset(CGPoint(x: 0, y: -refreshControl.frame.size.height), animated: true)
muhasturk
źródło
Połączyłem to z zaakceptowaną odpowiedzią, ponieważ to tylko aktualizacja.
Tudor
3

Jeśli używasz Rxswift dla Swift 3.1, możesz użyć poniżej:

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

Ta praca dla Swift 3.1, iOS 10.

jkyin
źródło
1
To sendActionswyzwalacz rxsprawia, że ​​ta odpowiedź jest związana z przypadkiem, gdy RxSwiftktoś się zastanawia na pierwszy rzut oka
carbonr
Musiałem użyć instancji refreshControl z tableViewController, a nie z tableView. Ponadto ten setContentOffset nie działał w moim przypadku na iOS10. Ten jednak działa: self.tableView.setContentOffset(CGPoint(x:0, y:self.tableView.contentOffset.y - (refreshControl.frame.size.height)), animated: true)
nmdias
1

testowane na Swift 5

użyj tego w 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
źródło
0

Używam tej samej techniki, aby pokazać użytkownikowi wizualny znak „dane są aktualizacją”. Wynikowy użytkownik przeniesie aplikację z tła, a kanały / listy zostaną zaktualizowane za pomocą interfejsu użytkownika, tak jak użytkownicy pobierają tabele, aby odświeżyć się. Moja wersja zawiera 3 rzeczy

1) Kto wysyła „obudź się”

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

2) Obserwator w UIViewController

- (void)viewDidLoad {
    [super viewDidLoad];

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

3) Protokół

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

wynik

WINSergey
źródło