UIRefreshControl bez UITableViewController


308

Ciekawe, ponieważ nie wydaje się to od razu możliwe, ale czy istnieje sprytny sposób na wykorzystanie nowej UIRefreshControlklasy iOS 6 bez korzystania z UITableViewControllerpodklasy?

I często używać UIViewControllerz UITableViewpodrzędny i zgodne UITableViewDataSourcei UITableViewDelegatezamiast używać UITableViewControllerwprost.


6
@DaveDeLong: Nie, Dave, co powinien zrobić? Później na tej stronie powiesz, że jego rozwiązanie nie jest obsługiwane, więc jakie jest właściwe rozwiązanie?
mat.

3
@matt powinien używać UITableViewControlleri zgłosić błąd żądania API używanie UIRefreshControlz UITableViewbezpośrednio.
Dave DeLong,

31
UITableViewController miał (i nadal ma) zbyt wiele niejasnych i niszowych błędów i problemów z nietrywialnymi hierarchiami widoków ... które wszystkie magicznie znikają po przejściu na używanie standardowej VC z widokiem podrzędnym TableView. „Użyj UITVC” to słaby początek każdego rozwiązania, IMHO.
Adam

@Adam Czy te błędy pojawiają się, gdy używasz „UITableViewController” jako kontrolera widoku potomnego (dając w ten sposób dostęp do dostosowywania widoku bez chowania się w hierarchii tableViewControllers)? Nigdy nie spotkałem się z problemami podczas używania go w ten sposób.
memmons

Odpowiedzi:


388

Na przeczucie i na podstawie inspiracji DrummerB, próbowałem po prostu dodać UIRefreshControlinstancję jako subview do mojego UITableView. I to magicznie po prostu działa!

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

To dodaje UIRefreshControlwidok powyżej tabeli i działa zgodnie z oczekiwaniami bez konieczności używania UITableViewController:)


EDYCJA: Powyższe nadal działa, ale jak niektórzy zauważyli, istnieje niewielkie „zacinanie się” podczas dodawania UIRefreshControl w ten sposób. Rozwiązaniem tego jest utworzenie instancji UITableViewController, a następnie ustawienie na to UIRefreshControl i UITableView, tj .:

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;

48
Do Twojej wiadomości jest to nieobsługiwane zachowanie, które może ulec zmianie w przyszłości. Jedyną gwarancją Apple jest to, że korzystanie z niej zgodnie z dostarczonym interfejsem API (w tym przypadku -[UITableViewController setRefreshControl:]) będzie nadal działać.
Dave DeLong,

18
Dzięki tej implementacji wydaje się, że tuż przed uruchomieniem handleRefresh:następuje nieoczekiwana zmiana wartości widoków przewijania contentInsetsna ułamek sekundy. Czy ktoś jeszcze tego doświadczył lub ma na to jakieś rozwiązanie? (tak, wiem, że nie jest to obsługiwane!)
Tim

6
Naprawdę powinieneś po prostu użyć do tego widoku kontenera. Po prostu ustaw klasę kontrolera widoku na niestandardową podklasę, UITableViewControllera będziesz „robić to dobrze”.
Eugene

3
W iOS7 (nieznane dla iOS6) edytowana wersja działa dobrze, z wyjątkiem sytuacji, gdy zamkniesz i ponownie otworzysz aplikację. Animacja jest odtwarzana dopiero po drugim odświeżeniu. Po pierwszym odświeżeniu po ponownym otwarciu aplikacji jest to po prostu pełny okrąg zamiast przyrostowego koła w zależności od tego, jak daleko go ściągasz. Czy jest jakaś poprawka?
david2391

30
Aby uniknąć konieczności „slutter”, jak wspomniano powyżej, a jednocześnie omijając UITableViewController, wystarczy dodać pod kontrolę odświeżania widoku tabeli tak: [_tableView insertSubview:_refreshControl atIndex:0];. Testowane zarówno z iOS 7, jak i 8;)
ptitvinou

95

Aby wyeliminować jąkanie spowodowane zaakceptowaną odpowiedzią, możesz przypisać ją UITableViewdo 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;

EDYTOWAĆ:

Sposób na dodanie opcji „ UIRefreshControlbez” UITableViewControllerbez zacinania się i zachowanie ładnej animacji po odświeżeniu danych w widoku tabeli.

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

Później podczas obsługi odświeżonych danych ...

- (void)handleRefresh:(UIRefreshControl *)refreshControl {
    [self.theTableView reloadData];
    [self.theTableView layoutIfNeeded];
    [refreshControl endRefreshing];
}

7
Nie musisz nawet tworzyć nowego UITableView. Powiedz po prostu initWithStyle: istniejącyTableView.style - a następnie wykonaj newTableViewController.tableView = istniejącyTableView i przypisz refreshControl. Sprowadza się to do trzech linii.
Trenskow

Nie zapomnij zadzwonić [_tablewViewController didMoveToParentViewController:self];po dodaniu widoku podrzędnego.
qix

4
self.tableView = _tableViewController.tableView;powinno być_tableViewController.tableView = self.tableView
Ali,

@Linus, dlaczego jest to konieczne? Dodałem _tableViewController.view jako subView w mojej niestandardowej metodzie viewDidLoad viewController i to chyba załatwiło sprawę. Nie musiałem nawet ustawiać _tableViewController.tableView
taber

Powód, dla którego nie mogę korzystać z UITableViewController i używam UIViewController z widokiem tabeli wewnątrz. stackoverflow.com/questions/18900428/…
lostintranslation

21

Spróbuj użyć widoku kontenera wewnątrz używanego ViewController. możesz zdefiniować czystą podklasę UITableViewController z dedykowanym widokiem tabeli i umieścić ją w ViewController.


2
Właśnie najczystszym sposobem wydaje się dodanie kontrolera widoku potomnego typu UITableViewController.
Zdenek,

Następnie możesz pobrać UITableViewController za pomocą self.childViewControllers.firstObject.
cbh2000,

^ możesz także rozważyć użycie, prepareForSegueaby uzyskać odwołanie do obiektu UITableViewController w widoku kontenera, ponieważ jest on natychmiast wyzwalany jako zdarzenie segue po utworzeniu wystąpienia z storyboardu.
crarho

18

Cóż, UIRefreshControl jest podklasą UIView, więc możesz jej używać samodzielnie. Nie jestem jednak pewien, jak to się renderuje. Renderowanie może po prostu zależeć od ramki, ale może również zależeć od UIScrollView lub UITableViewController.

Tak czy inaczej, będzie to bardziej hack niż eleganckie rozwiązanie. Polecam zajrzeć do jednego z dostępnych klonów stron trzecich lub napisać własny.

ODRefreshControl

wprowadź opis zdjęcia tutaj

SlimeRefresh

wprowadź opis zdjęcia tutaj


1
Dzięki za odpowiedź, ale nie do końca to, czego szukałem. Jednak pierwsze zdanie Twojego postu zainspirowało mnie do wypróbowania rozwiązania, które ostatecznie okazało się rozwiązaniem!
Keller

7
ODRefreshControl jest niesamowity - dzięki za wskazówkę! Bardzo proste i działa. I oczywiście o wiele bardziej elastyczny niż Apple. Nigdy nie zrozumiałem, dlaczego ktoś miałby używać UITableViewController, IMO najgorszej klasy w całym API. Nie robi (obok) nic, ale czyni twoje poglądy nieelastycznymi.
n13 17.04.13

Właściwie to zależy od twojego projektu, czego tam użyjesz .. użycie uitableview w moim przypadku zwiększało złożoność projektu, a teraz przełączyłem się na uitableviewcontroller, który w pewnym sensie podzielił mój kod na dwa segmenty. Cieszę się, że mam 2 mniejsze pliki, które mogą służy do debugowania zamiast jednego dużego pliku ..
kush

@ n13 Witaj, dobrym powodem do użycia UITableViewController jest możliwość projektowania za pomocą komórek statycznych. Niestety niedostępne w tableView rezydującym w UIViewController.
Jean Le Moignan,

@JeanLeMoignan Narzędzia i biblioteki iOS szybko się zmieniają, więc mój 3-letni komentarz nie jest już ważny. Przerzuciłem się na tworzenie wszystkich interfejsów użytkownika w kodzie za pomocą SnapKit i szczerze mówiąc, jest to znacznie bardziej wydajne niż klikanie 10 tysięcy razy w narzędziu do tworzenia interfejsów. Również zmiana UITableViewController na UIViewController jest łatwa i zajmuje tylko sekundę, podczas gdy w IB to duży ból.
n13

7

Spróbuj opóźnić wywołanie -endRefreshmetody refreshControl o ułamek sekundy po ponownym załadowaniu zawartości tableView przez użycie NSObject -performSelector:withObject:afterDelay:lub GCD dispatch_after.

W tym celu utworzyłem kategorię na UIRefreshControl:

@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

Przetestowałem to i działa to również w widokach kolekcji. Zauważyłem, że opóźnienie tak małe jak 0,01 sekundy wystarczy:

// My data refresh process here while the refresh control 'isRefreshing'
[self.tableView reloadData];
[self.refreshControl endRefreshingAfterDelay:.01];

4
Opóźnienia i oczekiwania są naprawdę kiepskim stylem.
orkoden

2
@orkoden Zgadzam się. Jest to obejście dla już nieobsługiwanego użycia UIRefreshControl.
boliva

Możesz znacznie łatwiej wywołać metodę z opóźnieniem. [self performSelector:@selector(endRefreshing) withObject:nil afterDelay:0.01]
orkoden

2
Efekt końcowy jest dokładnie taki sam i nie powoduje, że „hack” jest bardziej / mniej elegancki. To, czy użyć do tego celu, -performSelector:withObject:afterDelay:czy GCD, zależy od osobistego gustu.
boliva

7

IOS 10 Swift 3.0

To proste

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
    }

}

wprowadź opis zdjęcia tutaj

Jeśli chcesz dowiedzieć się o iOS 10 UIRefreshControl przeczytaj tutaj .


3

Dodanie kontrolki odświeżania jako widoku podrzędnego tworzy pustą przestrzeń nad nagłówkami sekcji.

Zamiast tego osadziłem UITableViewController w moim UIViewController, a następnie zmieniłem moją tableViewwłaściwość, aby wskazywała na osadzoną i viola! Minimalne zmiany kodu. :-)

Kroki:

  1. Utwórz nowy UITableViewController w Storyboard i osadz go w oryginalnym UIViewController
  2. Zamień @IBOutlet weak var tableView: UITableView!na nowy z wbudowanego UITableViewController, jak pokazano poniżej

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
    }

    ...
}

2

Dla Swift 2.2.

Najpierw wykonaj UIRefreshControl ().

var refreshControl : UIRefreshControl!

W metodzie viewDidLoad () dodaj:

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

I wykonaj funkcję odświeżania

func refresh(refreshControl: UIRefreshControl) {

    // do something ...

    // reload tableView
    self.tableView.reloadData()

    // End refreshing
    refreshControl.endRefreshing()
}

0

Oto inne rozwiązanie, które jest trochę inne.

Musiałem go użyć z powodu pewnych problemów z hierarchią widoków, które miałem: tworzyłem funkcjonalność, która wymagała przekazywania widoków do różnych miejsc w hierarchii widoków, które uległy uszkodzeniu podczas korzystania z widoku tabeli UITableViewController b / c tableView to widok główny UITableViewController ( self.view), a nie tylko zwykły widok, stworzył niespójne hierarchie kontrolera / widoku i spowodował awarię.

Zasadniczo utwórz własną podklasę UITableViewController i przesłonić loadView, aby przypisać self.view inny widok i przesłonić właściwość tableView, aby zwrócić osobny widok tabeli.

na przykład:

@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

W połączeniu z rozwiązaniem Kellera będzie to bardziej niezawodne w tym sensie, że tableView jest teraz zwykłym widokiem, a nie widokiem głównym VC, i będzie bardziej odporny na zmianę hierarchii widoków. Przykład użycia w ten sposób:

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;

Istnieje inne możliwe zastosowanie:

Ponieważ podklasowanie w ten sposób oddziela self.view od self.tableView, możliwe jest teraz używanie tego UITableViewController jako bardziej zwykłego kontrolera i dodawanie innych widoków podrzędnych do self.view bez osobliwości dodawania widoków podrzędnych do UITableView, więc można rozważyć zrobienie ich przeglądaj kontrolery bezpośrednio podklasę UITableViewController zamiast mieć potomne UITableViewController.

Niektóre rzeczy, na które należy uważać:

Ponieważ zastępujemy właściwość tableView bez wywoływania super, mogą być pewne rzeczy, na które należy uważać i w razie potrzeby należy je obsłużyć. Na przykład ustawienie widoku tabeli w powyższym przykładzie nie doda widoku tabeli do self.view i nie ustawi ramki, którą możesz chcieć zrobić. Ponadto w tej implementacji nie ma domyślnego widoku tabeli podczas tworzenia instancji klasy, co można również rozważyć dodanie. Nie uwzględniam go tutaj, ponieważ tak jest w każdym przypadku, a to rozwiązanie faktycznie dobrze pasuje do rozwiązania Kellera.


1
W pytaniu określono „bez użycia podklasy UITableViewController”, a ta odpowiedź dotyczy podklasy UITableViewController.
Brian

0

Spróbuj tego,

Powyższe rozwiązania są w porządku, ale tableView.refreshControl jest dostępny tylko dla UITableViewController, aż do iOS 9.xi jest dostępny w UITableView od iOS 10.x.

Napisane w 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)

Najlepsze rozwiązanie do tej pory
Karthik KM

-1

Pierwsza sugestia Kellera powoduje dziwny błąd w iOS 7, w którym wstawka tabeli jest zwiększana po ponownym pojawieniu się kontrolera widoku. Przejście na drugą odpowiedź za pomocą kontrolera uitableview naprawiło dla mnie pewne rzeczy.


-6

Okazuje się, że można użyć następujących opcji, gdy używa się UIViewController z widokiem podrzędnym UITableView i jest on zgodny z UITableViewDataSource i UITableViewDelegate:

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

9
Nie zgadzać się. self.refreshControljest własnością, a UITableViewControllernie UIViewController.
Rickster

NIE DZIAŁA, referhControl nie jest zdefiniowany dla uiviewcontroller
OMGPOP
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.