UIRefreshControl - beginRefreshing tidak berfungsi ketika UITableViewController ada di dalam UINavigationController

120

Saya telah menyiapkan UIRefreshControl di UITableViewController saya (yang ada di dalam UINavigationController) dan berfungsi seperti yang diharapkan (yaitu, tarik ke bawah mengaktifkan peristiwa yang benar). Namun, jika saya secara terprogram menjalankan beginRefreshingmetode instance pada kontrol penyegaran seperti:

[self.refreshControl beginRefreshing];

Tidak ada yang terjadi. Ini harus bergerak ke bawah dan menampilkan pemintal. The endRefreshingMetode bekerja dengan baik ketika saya sebut bahwa setelah refresh.

Saya membuat proyek prototipe dasar dengan perilaku ini dan berfungsi dengan baik ketika UITableViewController saya ditambahkan langsung ke pengontrol tampilan root delegasi aplikasi, misalnya:

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

Tetapi jika saya menambahkan tableViewControllerke UINavigationController terlebih dahulu, kemudian menambahkan pengontrol navigasi sebagai rootViewController, beginRefreshingmetode tersebut tidak lagi berfungsi. Misalnya

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

Perasaan saya apakah ini ada hubungannya dengan hierarki tampilan bersarang dalam pengontrol navigasi yang tidak berfungsi baik dengan kontrol penyegar - ada saran?

Terima kasih

wow
sumber

Jawaban:

195

Tampaknya jika Anda mulai menyegarkan secara terprogram, Anda harus menggulir sendiri tampilan tabel, misalnya, dengan mengubah contentoffset

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

Saya akan menebak alasan untuk ini adalah bahwa mungkin tidak diinginkan untuk menggulir ke kontrol penyegaran saat pengguna berada di tengah / bawah tampilan tabel?

Versi Swift 2.2 oleh @muhasturk

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

Singkatnya, untuk menjaga portabel ini tambahkan ekstensi ini

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)        
    }
}
Dmitry Shevchenko
sumber
6
Aneh, dalam pengujian saya, endRefreshing menyesuaikan offset sesuai kebutuhan
Dmitry Shevchenko
9
BTW, jika Anda menggunakan tata letak otomatis, Anda dapat mengganti baris dalam jawaban dengan ini: [self.tableView setContentOffset: CGPointMake (0, -self.topLayoutGuide.length) animated: YES];
Eric Baker
4
@ElBaker Saya percaya itu tidak akan berhasil. Tidak semua UITableViewControllers menampilkan bilah navigasi. Ini akan menyebabkan topLayoutGuide dengan panjang 20 dan offset terlalu kecil.
Fábio Oliveira
3
@EricBaker Anda dapat menggunakan: [self.tableView setContentOffset: CGPointMake (0, self.topLayoutGuide.length -self.refreshControl.frame.size.height) animasi: YES];
ZYiOS
1
Salin tempel bekerja untuk saya yang luar biasa. Saya mulai bosan dengan hal-hal pembuat papan cerita Apple.
okysabeni
78

UITableViewController memiliki properti automaticAdjustsScrollViewInsets setelah iOS 7. Tampilan tabel mungkin sudah memiliki contentOffset, biasanya (0, -64).

Jadi, cara yang tepat untuk menampilkan refreshControl setelah pemrograman mulai memuat ulang adalah menambahkan tinggi refreshControl ke contentOffset yang ada.

 [self.refreshControl beginRefreshing];
 [self.tableView setContentOffset:CGPointMake(0, self.tableView.contentOffset.y-self.refreshControl.frame.size.height) animated:YES];
Saya mendapatkannya
sumber
hai, Terima kasih banyak, saya juga ingin tahu tentang titik ajaib (0, -64) yang saya temui saat debugging.
inix
2
@inix 20 untuk tinggi bilah status + 44 untuk tinggi bilah navigasi
Igotit
1
Alih-alih -64lebih baik digunakan-self.topLayoutGuide.length
Diogo T
33

Berikut ekstensi Swift menggunakan strategi yang dijelaskan di atas.

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
sumber
Bekerja untuk UITableView.
Juan Boero
10
Saya akan merekomendasikan untuk meletakkan sendActionsForControlEvents(UIControlEvents.ValueChanged)di akhir fungsi ini, jika tidak, logika logika penyegaran yang sebenarnya tidak akan dijalankan.
Colin Basnett
Sejauh ini, ini adalah cara paling elegan untuk melakukannya. Termasuk komentar Colin Basnett untuk fungsionalitas yang lebih baik. itu dapat digunakan di seluruh proyek dengan mendefinisikannya sekali!
JoeGalind
1
@ColinBasnett: Menambahkan kode itu (yang sekarang menjadi sendActions (untuk: UIControlEvents.valueChanged)), menghasilkan loop tak terbatas ...
Kendall Helmstetter Gelner
15

Tidak ada jawaban lain yang berhasil untuk saya. Mereka akan menyebabkan pemintal muncul dan berputar, tetapi tindakan penyegaran itu sendiri tidak akan pernah terjadi. Ini bekerja:

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

Perhatikan, contoh ini menggunakan tampilan tabel, tetapi bisa juga merupakan tampilan koleksi.

Kyle Robson
sumber
Ini memecahkan masalah saya. Tapi sekarang saya menggunakan SVPullToRefresh, bagaimana cara menariknya ke bawah secara terprogram?
Gank
1
@Gank Saya tidak pernah menggunakan SVPullToRefresh. Sudahkah Anda mencoba membaca dokumen mereka? Tampaknya cukup jelas berdasarkan dokumen yang dapat ditarik ke bawah secara terprogram: "Jika Anda ingin memicu penyegaran secara terprogram (misalnya di viewDidAppear :), Anda dapat melakukannya dengan: [tableView triggerPullToRefresh];" Lihat: github.com/samvermette/ SVPullToRefresh
Kyle Robson
1
Sempurna! Itu bertingkah aneh bagi saya dengan animasinya, jadi saya hanya menggantinya denganscrollView.contentOffset = CGPoint(x: 0, y: scrollView.contentOffset.y - frame.height)
user1366265
13

Pendekatan yang sudah disebutkan:

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

akan membuat pemintal terlihat. Tapi itu tidak akan bernyawa. Satu hal yang saya ubah adalah urutan kedua metode ini dan semuanya berfungsi:

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

Untuk Swift 4 / 4.1

Campuran jawaban yang ada melakukan pekerjaan untuk saya:

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

Semoga ini membantu!

Isaac Bosca
sumber
7

Lihat juga pertanyaan ini

UIRefreshControl tidak menampilkan spiny saat memanggil beginRefreshing dan contentOffset adalah 0

Sepertinya bug bagi saya, karena ini hanya terjadi ketika properti contentOffset dari tableView adalah 0

Saya memperbaikinya dengan kode berikut (metode untuk 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
sumber
1
Ini tidak benar, saya memiliki dua UIViewControllers yang berbeda yang keduanya memiliki contentOffset 0 pada viewDidLoad dan salah satunya dengan benar menarik refreshControl saat memanggil [self.refreshControl beginRefreshing] dan yang lainnya tidak: /
simonthumper
Dokumentasi tidak mengatakan apa pun tentang menampilkan kontrol beginRefreshing, hanya statusnya yang berubah. Seperti yang saya lihat, ini mencegah untuk memulai tindakan penyegaran dua kali, sehingga mungkin pemuatan ulang yang disebut secara terprogram akan tetap berjalan, tindakan yang dimulai pengguna tidak akan memulai lagi.
Koen.
Saya telah mencatat masalah dengan menggunakan metode setContentOffset: animasi, jadi solusi ini berhasil untuk saya.
Marc Etcheverry
7

Ini adalah Swift 3 dan ekstensi yang lebih baru yang menampilkan spinner serta menganimasikannya.

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
sumber
2
Dari semua jawaban di atas, ini adalah satu-satunya jawaban yang bisa saya gunakan di iOS 11.1 / xcode 9.1
Bassebus
1
Inilah asyncAfteryang sebenarnya membuat animasi spinner bekerja (iOS 12.3 / Xcode 10.2)
francybiga
4

Ini bekerja sempurna untuk saya:

Cepat 3:

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

Untuk Swift 5, bagi saya satu-satunya hal yang hilang adalah menelepon refreshControl.sendActions(.valueChanged). Saya membuat ekstensi agar lebih bersih.

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
sumber
3

Selain solusi @Dymitry Shevchenko.

Saya menemukan solusi yang bagus untuk masalah ini. Anda dapat membuat ekstensi untuk UIRefreshControlmetode timpa itu:

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

Anda dapat menggunakan kelas baru dengan menyetel kelas khusus di Identity Inspector untuk kontrol penyegaran di Interface Builder.

lyzkov.dll
sumber
3

Fort Swift 2.2+

    self.tableView.setContentOffset(CGPoint(x: 0, y: -refreshControl.frame.size.height), animated: true)
muhasturk.dll
sumber
Saya telah menggabungkan ini dengan jawaban yang diterima karena ini hanya pembaruan.
Tudor
3

Jika Anda menggunakan Rxswift untuk swift 3.1, bisa menggunakan di bawah ini:

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

Ini berfungsi untuk swift 3.1, iOS 10.

jkyin
sumber
1
Ini adalah sendActionspemicu rxyang membuat jawaban ini terkait dengan RxSwiftjika ada yang bertanya-tanya pada pandangan pertama
carbonr
Saya harus menggunakan instance refreshControl dari tableViewController, bukan tableView. Selain itu, setContentOffset itu tidak berfungsi untuk saya yang menargetkan iOS10. Namun yang satu ini berfungsi: self.tableView.setContentOffset(CGPoint(x:0, y:self.tableView.contentOffset.y - (refreshControl.frame.size.height)), animated: true)
nmdias
1

diuji di Swift 5

gunakan ini di 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
sumber
0

Saya menggunakan teknik yang sama untuk tanda visual show user "data is update". Pengguna yang membawa aplikasi dari latar belakang dan umpan / daftar akan diperbarui dengan UI seperti pengguna menarik tabel untuk menyegarkan dirinya sendiri. Versi saya berisi 3 hal

1) Siapa yang mengirim "bangun"

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

2) Observer di UIViewController

- (void)viewDidLoad {
    [super viewDidLoad];

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

3) Protokol

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

hasil

WINSergey
sumber