UIRefreshControl-当UITableViewController位于UINavigationController内部时,beginRefreshing不起作用


120

我已经在UITableViewController(位于UINavigationController内部)中设置了UIRefreshControl,并且它按预期工作(即,下拉触发正确的事件)。但是,如果我以编程beginRefreshing方式在刷新控件上调用实例方法,例如:

[self.refreshControl beginRefreshing];

没发生什么事。它应该向下动画并显示微调框。endRefreshing在刷新后调用该方法时,该方法可以正常工作。

我用这种行为鞭打了一个基本的原型项目,当我的UITableViewController直接添加到应用程序委托的根视图控制器时,它可以正常工作,例如:

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

但是,如果我tableViewController先将添加到UINavigationController,然后将导航控制器添加为rootViewController,则该beginRefreshing方法将不再起作用。例如

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

我的感觉是,这与导航控制器中的嵌套视图层次结构与刷新控件的效果不佳有关-有任何建议吗?

谢谢

Answers:


195

看来,如果您开始以编程方式刷新,则必须自己滚动表格视图,例如,通过更改contentoffset

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

我猜这是因为当用户位于表视图的中间/底部时,滚动到刷新控件可能不受欢迎吗?

@muhasturk的Swift 2.2版本

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

简而言之,要保持此便携式性,请添加此扩展名

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
奇怪的是,在我的测试中,endRefreshing根据需要调整偏移量
Dmitry Shevchenko

9
顺便说一句,如果您使用的是自动布局,则可以将答案中的行替换为:[self.tableView setContentOffset:CGPointMake(0,-self.topLayoutGuide.length)animation:YES];
埃里克·贝克

4
@EricBaker我相信不会。并非所有的UITableViewControllers都显示导航栏。这将导致topLayoutGuide的长度为20,并且偏移量太小。
法比奥·奥利维拉

3
@EricBaker您可以使用:[self.tableView setContentOffset:CGPointMake(0,self.topLayoutGuide.length -self.refreshControl.frame.size.height)animation:YES];
2014年

1
复制粘贴对我来说很棒。我已经厌倦了Apple的故事板构建器。
okysabeni 2015年

78

在iOS 7之后,UITableViewController具有自动的 AdjustsScrollViewInsets 属性。表视图可能已经具有contentOffset,通常为(0,-64)。

因此,以编程方式开始刷新后显示refreshControl的正确方法是将refreshControl的高度添加到现有的contentOffset中。

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

嗨,非常感谢您,我也对调试时遇到的魔点(0,-64)感到好奇。
2015年

2
@inix 20用于状态栏高度+ 44用于导航栏高度
Igotit

1
而不是-64更好地使用-self.topLayoutGuide.length
Diogo T

33

这是使用上述策略的Swift扩展。

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

适用于UITableView。
Juan Boero'3

10
我建议将其放在sendActionsForControlEvents(UIControlEvents.ValueChanged)此函数的末尾,否则实际的刷新逻辑将不会运行。
Colin Basnett,2016年

到目前为止,这是最优雅的方法。包括Colin Basnett的评论以获得更好的功能。只需定义一次就可以在整个项目中使用它!
JoeGalind

1
@ColinBasnett:添加该代码(现在为sendActions(for:UIControlEvents.valueChanged)),将导致无限循环...
Kendall Helmstetter Gelner

15

没有其他答案对我有用。它们会导致微调器显示和旋转,但是刷新动作本身永远不会发生。这有效:

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

注意,此示例使用表格视图,但它也可以是集合视图。


这确实解决了我的问题。但是现在我正在使用SVPullToRefresh,如何以编程方式将其下拉?
甘克2014年

1
@Gank我从未使用过SVPullToRefresh。您是否尝试过阅读他们的文档?根据文档看来,可以通过编程将其拉下是很明显的:“如果您想以编程方式触发刷新(例如,在viewDidAppear:中),则可以使用以下方法来做到这一点: [tableView triggerPullToRefresh];”参见:github.com/samvermette/ SVPullToRefresh
凯尔·罗布森

1
完善!这对我演技生疏与动画了,所以我只是用它代替scrollView.contentOffset = CGPoint(x: 0, y: scrollView.contentOffset.y - frame.height)
user1366265

13

已经提到的方法:

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

将使微调框可见。但这不会激活。我更改的一件事是这两种方法的顺序,并且一切正常:

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

11

对于Swift 4 / 4.1

现有答案的混合为我完成了工作:

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

希望这可以帮助!


7

另请参阅此问题

调用beginRefreshing并且contentOffset为0时,UIRefreshControl不显示多刺

对我来说,这似乎是个错误,因为它仅在tableView的contentOffset属性为0时发生

我使用以下代码(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
这是不正确的,我有两个不同的UIViewController,当viewDidLoad时,它们的contentOffset都为0,其中一个在调用[self.refreshControl beginRefreshing]时正确拉低了refreshControl,而另一个则没有:/
simonthumper

文档没有说明在上显示控件beginRefreshing,只说控件的状态发生了变化。如我所见,这是为了防止两次启动刷新操作,以免以编程方式称为刷新的操作仍在运行,而用户启动的操作将不会启动另一个操作。
科恩

我已经注意到使用setContentOffset:animated方法存在的问题,因此该解决方案对我有用。
Marc Etcheverry

7

这是Swift 3及更高版本,显示了微调器并为其设置了动画。

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
在以上所有答案中,这是我唯一可以使用iOS 11.1 / xcode 9.1的
答案

1
asyncAfter实际上是使微调器动画工作(iOS 12.3 / Xcode 10.2)的原因
francybiga

4

对我来说很完美:

斯威夫特3:

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

4

对于Swift 5,对我而言,唯一缺少的就是打电话refreshControl.sendActions(.valueChanged)。我进行了扩展,使其更清洁。

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

除了@Dymitry Shevchenko解决方案。

我找到了解决此问题的好方法。您可以UIRefreshControl为该覆盖方法创建扩展:

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

您可以通过在Identity Inspector中设置自定义类来使用新类,以在Interface Builder中进行刷新控制。


3

斯威夫特堡2.2+

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

我已经将此与可接受的答案合并,因为这只是更新。

3

如果您将Rxswift用于swift 3.1,则可以使用以下命令:

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

适用于Swift 3.1,iOS 10。


1
它的sendActions触发条件rx使该答案与RxSwift万一任何人
都想

我必须使用tableViewController中的refreshControl实例,而不是tableView。此外,该setContentOffset不适用于以iOS10为目标的我。但是,这一方法有效: self.tableView.setContentOffset(CGPoint(x:0, y:self.tableView.contentOffset.y - (refreshControl.frame.size.height)), animated: true)
nmdias

1

在Swift 5上测试

用这个 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

我使用相同的技术为用户显示“数据正在更新”视觉标志。结果是用户从后台带来应用程序,并且提要/列表将通过UI进行更新,就像用户拉表刷新自己一样。我的版本包含3件事

1)谁发送“唤醒”

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

2)UIViewController中的观察者

- (void)viewDidLoad {
    [super viewDidLoad];

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

3)协议

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

结果

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.