UIRefreshControl - beginRefreshing không hoạt động khi UITableViewController nằm bên trong UINavigationController


120

Tôi đã thiết lập một UIRefreshControl trong UITableViewController của mình (nằm trong UINavigationController) và nó hoạt động như mong đợi (tức là kéo xuống kích hoạt sự kiện chính xác). Tuy nhiên, nếu tôi lập trình gọi beginRefreshingphương thức phiên bản trên điều khiển làm mới như:

[self.refreshControl beginRefreshing];

Chẳng có gì xảy ra. Nó sẽ hoạt hình xuống và hiển thị con quay. Các endRefreshingphương pháp làm việc đúng khi tôi gọi đó là sau khi làm mới.

Tôi đã hoàn thành một dự án nguyên mẫu cơ bản với hành vi này và nó hoạt động bình thường khi UITableViewController của tôi được thêm trực tiếp vào bộ điều khiển chế độ xem gốc của đại biểu ứng dụng, ví dụ:

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

Nhưng nếu tôi thêm tableViewControllervào một UINavigationController đầu tiên, sau đó thêm các điều khiển chuyển hướng như rootViewController, các beginRefreshingphương pháp công trình không còn. Ví dụ

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

Cảm giác của tôi là điều này có liên quan đến cấu trúc phân cấp chế độ xem lồng nhau trong bộ điều khiển điều hướng không hoạt động tốt với điều khiển làm mới - bất kỳ đề xuất nào?

Cảm ơn

Câu trả lời:


195

Có vẻ như nếu bạn bắt đầu làm mới theo chương trình, bạn phải tự cuộn chế độ xem bảng, chẳng hạn như bằng cách thay đổi bộ nội dung

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

Tôi đoán lý do cho điều này là có thể không mong muốn cuộn đến điều khiển làm mới khi người dùng đang ở giữa / dưới cùng của chế độ xem bảng?

Phiên bản Swift 2.2 của @muhasturk

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

Tóm lại, để giữ cho tính di động này, hãy thêm phần mở rộng này

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

6
Đó là kỳ lạ, trong các thử nghiệm của tôi, điều chỉnh endRefreshing bù đắp khi cần thiết
Dmitry Shevchenko

9
BTW, nếu bạn đang sử dụng bố cục tự động, bạn có thể thay thế dòng trong câu trả lời bằng dòng này: [self.tableView setContentOffset: CGPointMake (0, -self.topLayoutGuide.length) animation: YES];
Eric Baker

4
@EricBaker Tôi tin rằng điều đó sẽ không làm được. Không phải tất cả UITableViewControllers đều hiển thị thanh điều hướng. Điều này sẽ dẫn đến topLayoutGuide có độ dài 20 và phần bù quá nhỏ.
Fábio Oliveira

3
@EricBaker bạn có thể sử dụng: [self.tableView setContentOffset: CGPointMake (0, self.topLayoutGuide.length -self.refreshControl.frame.size.height) hoạt hình: CÓ];
ZYiOS

1
Sao chép dán hoạt động đối với tôi, điều đó thật tuyệt vời. Tôi đã cảm thấy mệt mỏi với công cụ xây dựng bảng phân cảnh của Apple.
okysabeni

78

UITableViewController có thuộc tính AutomaticAdjustsScrollViewInsets sau iOS 7. Chế độ xem bảng có thể đã có contentOffset, thường là (0, -64).

Vì vậy, cách phù hợp để hiển thị refreshControl sau khi bắt đầu làm mới theo chương trình là thêm chiều cao của refreshControl vào contentOffset hiện có.

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

chào, Cảm ơn bạn rất nhiều, tôi cũng tò mò về điểm kỳ diệu (0, -64) tôi đã gặp khi gỡ lỗi.
inix

2
@inix 20 cho chiều cao thanh trạng thái + 44 cho chiều cao thanh điều hướng
Igotit

1
Thay vì -64tốt hơn là sử dụng-self.topLayoutGuide.length
Diogo T

33

Đây là một tiện ích mở rộng Swift sử dụng các chiến lược được mô tả ở trên.

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

Hoạt động cho UITableView.
Juan Boero

10
Tôi khuyên bạn nên đặt sendActionsForControlEvents(UIControlEvents.ValueChanged)ở cuối hàm này, nếu không logic logic làm mới thực tế sẽ không được chạy.
Colin Basnett

Đây là cách thanh lịch nhất để làm điều đó. Bao gồm cả bình luận của Colin Basnett để có chức năng tốt hơn. nó có thể được sử dụng trên toàn bộ dự án bằng cách xác định nó một lần!
JoeGalind

1
@ColinBasnett: Thêm mã (mà bây giờ là sendActions (đối với: UIControlEvents.valueChanged)), kết quả trong một vòng lặp vô hạn ...
Kendall Helmstetter Gelner

15

Không có câu trả lời nào khác phù hợp với tôi. Chúng sẽ khiến con quay hiển thị và quay, nhưng bản thân hành động làm mới sẽ không bao giờ xảy ra. Những công việc này:

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

Lưu ý, ví dụ này sử dụng dạng xem bảng, nhưng nó cũng có thể là dạng xem bộ sưu tập.


Điều này đã giải quyết vấn đề của tôi. Nhưng bây giờ tôi đang sử dụng SVPullToRefresh, làm thế nào để kéo nó xuống theo chương trình?
Gank

1
@Gank Tôi chưa bao giờ sử dụng SVPullToRefresh. Bạn đã thử đọc tài liệu của họ chưa? Có vẻ như khá rõ ràng dựa trên các tài liệu rằng nó có thể được kéo xuống theo chương trình: "Nếu bạn muốn kích hoạt làm mới theo chương trình (ví dụ: trong viewDidAppear :), bạn có thể làm như vậy với: [tableView triggerPullToRefresh];" Xem: github.com/samvermette/ SVPullToRefresh
Kyle Robson

1
Hoàn hảo! Nó đã hành động kỳ lạ đối với tôi với hình ảnh động mặc dù, vì vậy tôi chỉ đơn giản là thay thế nó bằngscrollView.contentOffset = CGPoint(x: 0, y: scrollView.contentOffset.y - frame.height)
user1366265

13

Cách tiếp cận đã được đề cập:

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

sẽ hiển thị con quay. Nhưng nó sẽ không hoạt hình. Một điều tôi đã thay đổi là thứ tự của hai phương pháp này và mọi thứ đều hoạt động:

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

11

Đối với Swift 4 / 4.1

Một kết hợp các câu trả lời hiện có thực hiện công việc cho tôi:

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

Hi vọng điêu nay co ich!


7

Xem thêm câu hỏi này

UIRefreshControl không hiển thị gai khi gọi beginRefreshing và contentOffset bằng 0

Nó giống như một lỗi đối với tôi, vì nó chỉ xảy ra khi thuộc tính contentOffset của tableView là 0

Tôi đã sửa điều đó bằng mã sau (phương thức cho 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){

        }];

    }
}

1
Điều này không đúng, tôi có hai UIViewControllers khác nhau, cả hai đều có contentOffset bằng 0 khi viewDidLoad và một trong số chúng kéo xuống refreshControl một cách chính xác khi gọi [self.refreshControl beginRefreshing] và cái kia thì không: /
simonthumper

Tài liệu không nói gì về việc bật điều khiển beginRefreshing, chỉ là trạng thái của nó thay đổi. Như tôi thấy, đó là ngăn việc bắt đầu hành động làm mới hai lần, vì vậy, một hành động được gọi là làm mới theo chương trình có thể vẫn đang chạy, một hành động do người dùng thực hiện sẽ không bắt đầu một hành động khác.
Koen.

Tôi đã ghi nhận các vấn đề khi sử dụng phương thức setContentOffset: animation, vì vậy giải pháp này phù hợp với tôi.
Marc Etcheverry

7

Đây là tiện ích mở rộng Swift 3 trở lên hiển thị spinner cũng như tạo hoạt ảnh cho nó.

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

2
Trong số tất cả các câu trả lời ở trên, đây là câu trả lời duy nhất tôi có thể làm việc trên iOS 11.1 / xcode 9.1
Bassebus

1
Các asyncAfterthực sự là những gì làm cho công việc spinner hình ảnh động (iOS 12,3 / Xcode 10.2)
francybiga

4

Nó hoạt động hoàn hảo với tôi:

Swift 3:

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

4

Đối với Swift 5, điều duy nhất còn thiếu là gọi điện refreshControl.sendActions(.valueChanged). Tôi đã thực hiện một phần mở rộng để làm cho nó sạch sẽ hơn.

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

}

3

Ngoài giải pháp @Dymitry Shevchenko.

Tôi tìm thấy giải pháp tốt cho vấn đề này. Bạn có thể tạo phần mở rộng cho UIRefreshControlphương thức ghi đè đó:

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

Bạn có thể sử dụng lớp mới bằng cách đặt lớp tùy chỉnh trong Trình kiểm tra danh tính để kiểm soát làm mới trong Trình tạo giao diện.


3

Fort Swift 2.2+

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

Tôi đã hợp nhất câu trả lời này với câu trả lời được chấp nhận vì nó chỉ là một bản cập nhật.
Tudor

3

Nếu bạn sử dụng Rxswift cho swift 3.1, có thể sử dụng bên dưới:

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

Điều này hoạt động cho nhanh chóng 3.1, iOS 10.


1
Đó là lý do sendActionsđể kích hoạt rxcâu trả lời này liên quan đến việc RxSwiftbất kỳ ai cũng thắc mắc ngay từ cái nhìn đầu tiên
carbonr

Tôi đã phải sử dụng cá thể refreshControl từ tableViewController, không phải tableView. Ngoài ra, setContentOffset đó không hoạt động đối với tôi nhắm mục tiêu iOS10. Cái này làm việc tuy nhiên: self.tableView.setContentOffset(CGPoint(x:0, y:self.tableView.contentOffset.y - (refreshControl.frame.size.height)), animated: true)
nmdias

1

đã thử nghiệm trên Swift 5

sử dụng cái này trong 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() 
    }
}

0

Tôi sử dụng kỹ thuật tương tự để hiển thị dấu hiệu trực quan cho người dùng "data is update". Kết quả là người dùng đưa ứng dụng từ nền và nguồn cấp dữ liệu / danh sách sẽ được cập nhật với giao diện người dùng giống như người dùng kéo bảng để làm mới chính mình. Phiên bản của tôi có 3 thứ

1) Ai gửi "thức dậy"

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

2) Người quan sát trong UIViewController

- (void)viewDidLoad {
    [super viewDidLoad];

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

3) Giao thức

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

kết quả

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.