UIRefreshControl - 当 UITableViewController 在 UINavigationController 中时,beginRefreshing 不起作用

Posted

技术标签:

【中文标题】UIRefreshControl - 当 UITableViewController 在 UINavigationController 中时,beginRefreshing 不起作用【英文标题】:UIRefreshControl - beginRefreshing not working when UITableViewController is inside UINavigationController 【发布时间】:2013-01-21 01:09:03 【问题描述】:

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

[self.refreshControl beginRefreshing];

什么都没有发生。它应该动画下来并显示微调器。 endRefreshing 方法在我刷新后调用时可以正常工作。

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

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

但如果我先将tableViewController 添加到UINavigationController,然后将导航控制器添加为rootViewControllerbeginRefreshing 方法将不再有效。例如

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

我的感觉是这与导航控制器中的嵌套视图层次结构与刷新控件不兼容有关 - 有什么建议吗?

谢谢

【问题讨论】:

【参考方案1】:

如果您开始以编程方式刷新,您似乎必须自己滚动表格视图,例如,通过更改 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)        
    

【讨论】:

谢谢 - 达到了我想要的效果!刷新完成后,我必须将内容偏移设置回 0、0。 这很奇怪,在我的测试中,endRefreshing 会根据需要调整偏移量 顺便说一句,如果您使用自动布局,则可以将答案中的行替换为: [self.tableView setContentOffset:CGPointMake(0, -self.topLayoutGuide.length) animated:YES] ; @EricBaker 我相信那是行不通的。并非所有 UITableViewController 都显示导航栏。这会导致 topLayoutGuide 的长度为 20 并且偏移量太小。 @EricBaker 你可以使用:[self.tableView setContentOffset:CGPointMake(0, self.topLayoutGuide.length -self.refreshControl.frame.size.height) animated:YES];【参考方案2】:

UITableViewController 在 ios 7 之后有 automaticallyAdjustsScrollViewInsets 属性。table view 可能已经有 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)。 @inix 20 状态栏高度 + 44 导航栏高度 而不是-64 最好使用-self.topLayoutGuide.length【参考方案3】:

这是一个使用上述策略的 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。 我建议把sendActionsForControlEvents(UIControlEvents.ValueChanged)放在这个函数的最后,否则实际的刷新逻辑逻辑不会运行。 这是迄今为止最优雅的方式。包括 Colin Basnett 的评论以获得更好的功能。定义一次就可以在整个项目中使用! @ColinBasnett :添加该代码(现在是 sendActions(for: UIControlEvents.valueChanged))会导致无限循环...【参考方案4】:

其他答案都不适合我。它们会导致微调器显示和旋转,但刷新操作本身永远不会发生。这有效:

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,如何以编程方式将其拉下来? @Gank 我从未使用过 SVPullToRefresh。您是否尝试过阅读他们的文档?根据文档,它似乎很明显可以通过编程方式下拉:“如果您想以编程方式触发刷新(例如在 viewDidAppear 中:),您可以这样做:[tableView triggerPullToRefresh];”参见:github.com/samvermette/SVPullToRefresh 完美!不过动画对我来说很奇怪,所以我简单地将其替换为scrollView.contentOffset = CGPoint(x: 0, y: scrollView.contentOffset.y - frame.height)【参考方案5】:

已经提到的方法:

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

【讨论】:

【参考方案6】:

对于 Swift 4/4.1

现有答案的组合为我完成了这项工作:

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

希望这会有所帮助!

【讨论】:

【参考方案7】:

另请参阅此问题

UIRefreshControl not showing spiny when calling beginRefreshing and contentOffset is 0

这对我来说似乎是一个错误,因为它仅在 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)

        ];

    

【讨论】:

这不是真的,我有两个不同的 UIViewController,它们在 viewDidLoad 时的 contentOffset 都为 0,其中一个在调用 [self.refreshControl beginRefreshing] 时正确拉下 refreshControl,另一个则没有:// 文档没有说明在beginRefreshing 上显示控件,只是它的状态发生了变化。正如我所看到的,这是为了防止两次启动刷新操作,这样可能以编程方式调用的刷新仍会运行,用户启动的操作不会启动另一个。 我注意到使用 setContentOffset:animated 方法的问题,所以这个解决方案对我有用。【参考方案8】:

这是 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()
      
   

【讨论】:

在以上所有答案中,这是我唯一能在 iOS 11.1 / xcode 9.1 上工作的答案 asyncAfter 实际上是使微调器动画工作的原因(iOS 12.3 / Xcode 10.2)【参考方案9】:

对于 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)
    


【讨论】:

调用发送操作方法是它为我工作的唯一方法! 触发无限循环【参考方案10】:

这对我来说很完美:

斯威夫特 3:

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

【讨论】:

【参考方案11】:

除了@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 中进行刷新控制。

【讨论】:

【参考方案12】:

斯威夫特堡 2.2+

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

【讨论】:

我已将此与接受的答案合并,因为它只是一个更新。【参考方案13】:

如果你在 swift 3.1 中使用 Rxswift,可以在下面使用:

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。

【讨论】:

它的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)【参考方案14】:

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

【讨论】:

【参考方案15】:

我使用相同的技术来显示用户“数据正在更新”视觉标志。结果用户从后台带来应用程序,并且提要/列表将使用 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];
    

【讨论】:

以上是关于UIRefreshControl - 当 UITableViewController 在 UINavigationController 中时,beginRefreshing 不起作用的主要内容,如果未能解决你的问题,请参考以下文章

当 tableview 有很多行时,uirefreshcontrol 不显示

UIRefreshControl - 当UITableViewController在UINavigationController中时,beginRefreshing无效

UIRefreshControl endRefresh 在启用大标题时跳转

下面的 UIRefreshControl 块视图

ECSlidingViewController 冻结 UIRefreshControl

在uitableview中使用UIRefreshControl