没有UITableViewController的UIRefreshControl

308

有点好奇,因为似乎不太可能,但是是否有一种隐蔽的方法可以利用新的iOS 6 UIRefreshControl类而不使用UITableViewController子类呢?

我经常使用一个带有UITableView子视图的UIViewController,并符合UITableViewDataSourceUITableViewDelegate,而不是直接使用UITableViewController


6
@DaveDeLong :不,Dave,他应该怎么做?在这个页面的后面,你说他的解决方案不受支持,那正确的解决方案是什么? - matt
3
他应该使用一个 UITableViewController,并提交一个bug请求API直接在UITableView中使用UIRefreshControl - Dave DeLong
33
UITableViewController存在太多晦涩和专业问题,尤其是在处理非常规视图层次结构时。但如果使用标准的带有TableView子视图的VC,这些问题都会神奇地消失。因此,在解决问题时仅简单地建议“使用UITVC”并不是一个好的起点,以我个人的看法。 - Adam
@Adam 当使用“UITableViewController”作为子视图控制器时(从而可以访问视图自定义而不会在tableViewControllers层次结构中混乱),这些错误是否会出现?我从未在使用此方法时遇到过问题。 - memmons
12个回答

388

基于DrummerB的启发,我有一种直觉,尝试将UIRefreshControl实例简单地作为UITableView的子视图添加。神奇的是,它就工作了!

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

这会在你的表视图上方添加一个UIRefreshControl,而无需使用UITableViewController即可正常工作 :)


编辑:上述方法仍然有效,但是正如一些人指出的那样,在以这种方式添加UIRefreshControl时可能会出现轻微的“卡顿”。解决方法是实例化一个UITableViewController,然后将您的UIRefreshControlUITableView设置到该控制器中,即:

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
请注意,这是不受支持的行为,未来可能会发生变化。苹果唯一保证的是,按照提供的 API(在这种情况下是 -[UITableViewController setRefreshControl:])使用它将继续正常运行。 - Dave DeLong
18
使用这个实现时,似乎在触发 handleRefresh: 前,滚动视图的 contentInsets 会在一瞬间发生意外变化。还有其他人遇到过这种情况或者知道如何解决吗?(是的,我知道这本来就不被支持!) - Tim
6
你可以简单地使用一个容器视图来完成这个任务。只需将其视图控制器的类设置为你自定义的 UITableViewController 子类,就能够“正确地”实现它。 - Eugene
3
在 iOS7 中(iOS6 未知),编辑版的功能表现良好,除非您关闭并重新打开应用程序。在第一次刷新应用程序后重新打开时,动画不会播放,直到第二次刷新为止。第一次刷新后,它只是一个完整的圆圈,而不是根据您向下拖拉的距离逐渐增加的圆圈。有解决方法吗? - david2391
30
为了避免上述提到的“滚动条”同时仍然避开UITableViewController,只需将刷新控件添加到表格视图下方,如下所示:[_tableView insertSubview:_refreshControl atIndex:0];。在iOS 7和8上进行了测试;) - Stephen Vinouze
显示剩余13条评论

94
为消除被接受答案所引起的口吃问题,你可以将UITableView分配给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;

编辑:

一种在没有UITableViewController的情况下添加UIRefreshControl的方法,没有卡顿,并且在刷新表视图数据后保留良好的动画效果。

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

稍后在处理刷新后的数据时...

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

7
你甚至不需要创建一个新的UITableView。只需调用initWithStyle:existingTableView.style方法,之后将newTableViewController.tableView赋值为existingTableView并添加refreshControl即可。将此简化为三行代码。 - Trenskow
在添加子视图后,不要忘记调用[_tablewViewController didMoveToParentViewController:self]; - qix
4
应该改为_tableViewController.tableView = self.tableView;,这行代码的作用是将_tableViewController对象中的tableView属性设置为当前对象(self)中的tableView属性。 - Ali
@Linus,为什么需要这样做?我在自定义视图控制器的viewDidLoad方法中添加了_tableViewController.view作为子视图,这似乎解决了问题。我甚至不需要设置_tableViewController.tableView。 - taber
这个解决方案已经接近完成了 - 但是对我来说,在刷新被触发的那一点上,tableView会在屏幕上向下跳动约40像素。 - Ben Clayton
显示剩余3条评论

21
你可以尝试在使用的 ViewController 中使用容器视图。你可以定义一个干净的 UITableViewController 子类,其中包含专用的 tableview 并将其放置在 ViewController 中。

2
准确地说,最干净的方法似乎是添加UITableViewController类型的子视图控制器。 - Zdenek
你可以使用 self.childViewControllers.firstObject 获取 UITableViewController。 - cbh2000
你可能还想考虑使用 prepareForSegue 方法来获取容器视图中的 UITableViewController 对象的引用,因为它会在从故事板实例化时立即触发作为一个 segue 事件。 - crarho

18

UIRefreshControl 是 UIView 的子类,因此您可以单独使用它。但我不确定它是如何渲染自己的。渲染可能仅取决于框架,但也可能依赖于 UIScrollView 或 UITableViewController。

无论哪种方式,都更像是一种 hack 而不是优雅的解决方案。建议您使用可用的第三方克隆之一或编写自己的刷新控件。

ODRefreshControl

enter image description here

SlimeRefresh

enter image description here


1
谢谢您的回复,但不完全是我想要的。不过,您帖子的第一句话确实启发了我尝试最终解决方案! - Keller
7
ODRefreshControl非常棒 - 感谢您的提示!非常简单,而且功能齐全。当然比苹果的更加灵活。我从来不理解为什么有人会使用UITableViewController,我认为这是整个API中最糟糕的类。它几乎什么都做不了,但却使您的视图变得不灵活。 - n13
实际上这取决于你的项目需要使用什么...在我的情况下,使用uitableview增加了项目的复杂性,现在我转而使用uitableviewcontroller,将我的代码分成了两个部分。我很高兴我有两个较小的文件可以用来调试,而不是一个巨大的文件。 - kush
@n13 你好,使用UITableViewController的一个很好的理由是可以使用静态单元格进行设计。不幸的是,在UIViewController中的tableView中不可用。 - Jean Le Moignan
@JeanLeMoignan iOS 工具和库变化很快,所以我发表的三年前的评论已经不再适用了。我已经转而使用 SnapKit 在代码中创建所有用户界面,老实说,这比在接口生成器中点击十万次要高效得多。而且将 UITableViewController 更改为 UIViewController 很容易,只需要一秒钟,而在 IB 中就很麻烦。 - n13
哦,而且现在RefreshControl已经不再是问题了...不再需要第三方库。 - n13

7
尝试在tableView重新加载其内容后,将对refreshControl的调用延迟几分之一秒,可以使用NSObject的-performSelector:withObject:afterDelay:或GCD的dispatch_after来实现。以下是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

我测试过,这也适用于集合视图。我注意到,即使是0.01秒的延迟就足够了:

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

4
延迟和等待是很糟糕的风格。 - orkoden
2
@orkoden 我同意。这是对已经不再支持的 UIRefreshControl 使用的一种解决方法。 - boliva
您可以使用 [self performSelector:@selector(endRefreshing) withObject:nil afterDelay:0.01] 更轻松地延迟调用一个方法。 - orkoden
2
最终效果完全相同,不会使“hack”更/ less优雅。使用-performSelector:withObject:afterDelay:还是GCD取决于个人口味。 - boliva

7

iOS 10 Swift 3.0

这是一个简单的事情

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
    }

}

在此输入图片描述

如果您想了解iOS 10 UIRefreshControl的相关内容,请阅读这里


3
将刷新控件添加为子视图会在节标题上方创建一个空白空间。相反,我将UITableViewController嵌入到我的UIViewController中,然后将我的tableView属性更改为指向嵌入的那个,完美!代码改动最小:)
步骤:
1. 在Storyboard中创建一个新的UITableViewController并将其嵌入到您原始的UIViewController中。 2. 将@IBOutlet weak var tableView: UITableView!替换为新嵌入UITableViewController中的内容,如下所示。
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

针对Swift 2.2版本。

首先创建UIRefreshControl()。

var refreshControl : UIRefreshControl!

在你的viewDidLoad()方法中添加以下内容:
refreshControl = UIRefreshControl()
    refreshControl.attributedTitle = NSAttributedString(string: "Refreshing..")
    refreshControl.addTarget(self, action: #selector(YourUIViewController.refresh(_:)), forControlEvents: UIControlEvents.ValueChanged)
    self.tableView.addSubview(refreshControl)

并创建刷新函数
func refresh(refreshControl: UIRefreshControl) {

    // do something ...

    // reload tableView
    self.tableView.reloadData()

    // End refreshing
    refreshControl.endRefreshing()
}

0

这里有另一种略微不同的解决方案。

我不得不使用它,因为我遇到了一些视图层次结构问题:我正在创建一些需要将视图传递到视图层次结构中不同位置的功能,在使用UITableViewController的tableview时会出现问题,因为tableView是UITableViewController的根视图(self.view),而不仅仅是一个普通的视图,它创建了不一致的控制器/视图层次结构并导致崩溃。

基本上,创建自己的UITableViewController子类,并覆盖loadView以分配不同的视图给self.view,并覆盖tableView属性以返回单独的tableview。

例如:

@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

当与Keller的解决方案结合使用时,这将更加强大,因为tableView现在是一个常规视图,而不是VC的根视图,并且更能够抵御视图层次结构的变化。以下是使用它的示例:

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;

这里还有另一个可能的用途:

由于以这种方式进行子类化将self.view与self.tableView分离,因此现在可以将这个UITableViewController用作更常规的控制器,并向self.view添加其他子视图,而不必添加到UITableView中的奇怪性,因此可以考虑直接将视图控制器创建为UITableViewController的子类,而不是使用UITableViewController子类。

需要注意的一些事项:

由于我们覆盖了tableView属性而没有调用super,因此可能需要注意一些事项,并在必要时进行处理。例如,在上面的示例中设置tableview将不会将tableview添加到self.view并设置其框架,您可能希望这样做。此外,在此实现中,当类被实例化时,没有默认的tableView提供给您,这也是您可能要考虑添加的内容。我没有在这里包括它,因为那是具体情况,而且这个解决方案实际上与Keller的解决方案非常匹配。


1
问题指定“不使用UITableViewController子类”,而此答案是针对UITableViewController子类的。 - Brian

0

试试这个:

上面的解决方案不错,但是tableView.refreshControl只适用于UITableViewController,在iOS 9.x之前可用,在iOS 10.x及以后版本的UITableView中才可用。

使用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)

目前为止最佳解决方案 - Karthik K M

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接