UIRefreshControl without UITableViewController

Objective CIosUitableviewIos6Uirefreshcontrol

Objective C Problem Overview


Just curious, as it doesn't immediately seem possible, but is there a sneaky way to leverage the new iOS 6 UIRefreshControl class without using a UITableViewController subclass?

I often use a UIViewController with a UITableView subview and conform to UITableViewDataSource and UITableViewDelegate rather than using a UITableViewController outright.

Objective C Solutions


Solution 1 - Objective C

On a hunch, and based on DrummerB's inspiration, I tried simply adding a UIRefreshControl instance as a subview to my UITableView. And it magically just works!

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

This adds a UIRefreshControl above your table view and works as expected without having to use a UITableViewController :)


EDIT: This above still works but as a few have pointed out, there is a slight "stutter" when adding the UIRefreshControl in this manner. A solution to that is to instantiate a UITableViewController, and then setting your UIRefreshControl and UITableView to that, i.e.:

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;

Solution 2 - Objective C

To eliminate the stutter that is caused by the accepted answer, you can assign your UITableView to a 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;

EDIT:

A way to add a UIRefreshControl with no UITableViewController with no stutter and retain the nice animation after refreshing data on the tableview.

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

Later when handling the refreshed data...

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

Solution 3 - Objective C

What you would try is use container view inside ViewController you are using. you can define clean UITableViewController subclass with dedicated tableview and place that in the ViewController.

Solution 4 - Objective C

Well UIRefreshControl is a UIView subclass, so you can use it on it's own. I'm not sure though how it renders itself. The rendering could simply depend on the frame, but it also could rely on a UIScrollView or the UITableViewController.

Either way, it's going to be more of a hack than an elegant solution. I recommend you look into one of the available 3rd party clones or write your own.

ODRefreshControl

enter image description here

SlimeRefresh

enter image description here

Solution 5 - Objective C

Try delaying the call to the refreshControl -endRefresh method by a fraction of a second after the tableView reloads its contents, either by using NSObject's -performSelector:withObject:afterDelay: or GCD's dispatch_after.

I created a category on UIRefreshControl for this:

@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

I tested it and this also works on Collection Views. I've noticed that a delay as small as 0.01 seconds is enough:

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

Solution 6 - Objective C

> IOS 10 Swift 3.0

it's simple

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

enter image description here

If you want to learn about iOS 10 UIRefreshControl read **here**.

Solution 7 - Objective C

Adding the refresh control as a subview creates an empty space above section headers.

Instead, I embedded a UITableViewController into my UIViewController, then changed my tableView property to point towards the embedded one, and viola! Minimal code changes. :-)

Steps:

  1. Create a new UITableViewController in Storyboard and embed it into your original UIViewController
  2. Replace @IBOutlet weak var tableView: UITableView! with the one from the newly embedded UITableViewController, as shown below

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
    }

    ...
}

Solution 8 - Objective C

For Swift 2.2 .

First make UIRefreshControl() .

var refreshControl : UIRefreshControl!

In your viewDidLoad() method add:

refreshControl = UIRefreshControl()
    refreshControl.attributedTitle = NSAttributedString(string: "Refreshing..")
    refreshControl.addTarget(self, action: #selector(YourUIViewController.refresh(_:)), forControlEvents: UIControlEvents.ValueChanged)
    self.tableView.addSubview(refreshControl)

And make refresh function

func refresh(refreshControl: UIRefreshControl) {

    // do something ...

    // reload tableView
    self.tableView.reloadData()

    // End refreshing
    refreshControl.endRefreshing()
}

Solution 9 - Objective C

Here's another solution which is a little different.

I had to use it because of some view hierarchy issues I had: I was creating some functionality that required passing views around to different places in the view hierarchy, which broken when using a UITableViewController's tableview b/c the tableView is the UITableViewController's root view (self.view) and not just a regular view, it created inconsistent controller / view hierarchies and caused a crash.

Basically create your own subclass of UITableViewController and override loadView to assign self.view a different view, and override the tableView property to return a separate tableview.

for example:

@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

When combined with Keller's solution this will more robust in the sense that the tableView is now a regular view, not a VC's root view, and be more robust against changing view hierarchies. Example of using it this way:

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;

There is another possible use for this:

Since subclassing this way separates self.view from self.tableView, it's possible now to use this UITableViewController as more of a regular controller, and add other subviews to self.view without the oddities of adding subviews to UITableView, so one may considering making their view controllers directly a subclass of UITableViewController instead of having UITableViewController children.

Some things to watch out for:

Since we're overriding the tableView property without calling super, there may be some things to watch out for and should handle where necessary. For example, setting the tableview in my above example will not add the tableview to self.view and not set the frame which you may want to do. Also, in this implementation there is no default tableView given to you when the class is instantiated, which is also something you may consider adding. I don't include it here because that is case by case, and this solution actually fits well with Keller's solution.

Solution 10 - Objective C

Try this,

The Above solutions are fine but tableView.refreshControl is available for UITableViewController only, till iOS 9.x and comes in UITableView from iOS 10.x onwards.

Written in 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)

Solution 11 - Objective C

Keller's first suggestion causes a strange bug in iOS 7 where the inset of the table is increased after the view controller reappears. Changing to the second answer, using the uitableviewcontroller, fixed things for me.

Solution 12 - Objective C

It turns out you can use the following when you use a UIViewController with a UITableView subview and conform to UITableViewDataSource and UITableViewDelegate:

self.refreshControl = [[UIRefreshControl alloc]init];
[self.refreshControl addTarget:self action:@selector(refresh:) forControlEvents:UIControlEventValueChanged];

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionKellerView Question on Stackoverflow
Solution 1 - Objective CKellerView Answer on Stackoverflow
Solution 2 - Objective CPiotr TomasikView Answer on Stackoverflow
Solution 3 - Objective CTomohisa TakaokaView Answer on Stackoverflow
Solution 4 - Objective CDrummerBView Answer on Stackoverflow
Solution 5 - Objective CbolivaView Answer on Stackoverflow
Solution 6 - Objective CAshok RView Answer on Stackoverflow
Solution 7 - Objective Ccbh2000View Answer on Stackoverflow
Solution 8 - Objective CstakahopView Answer on Stackoverflow
Solution 9 - Objective CpsilencerView Answer on Stackoverflow
Solution 10 - Objective CAnkit Kumar GuptaView Answer on Stackoverflow
Solution 11 - Objective CMark BridgesView Answer on Stackoverflow
Solution 12 - Objective CClint PickView Answer on Stackoverflow