316

I am building an RSS reader using swift and need to implement pull to reload functionality.

Here is how i am trying to do it.

class FirstViewController: UIViewController,
    UITableViewDelegate, UITableViewDataSource {

   @IBOutlet var refresh: UIScreenEdgePanGestureRecognizer
   @IBOutlet var newsCollect: UITableView

   var activityIndicator:UIActivityIndicatorView? = nil

   override func viewDidLoad() {
       super.viewDidLoad()
       self.newsCollect.scrollEnabled = true
      // Do any additional setup after loading the view, typically from a nib.

      if nCollect.news.count <= 2{
          self.collectNews()
       }
      else{
          self.removeActivityIndicator()
       }
      view.addGestureRecognizer(refresh)
   }



@IBAction func reload(sender: UIScreenEdgePanGestureRecognizer) {
    nCollect.news = News[]()
    return newsCollect.reloadData()
}

I am getting :

Property 'self.refresh' not initialized at super.init call

Please help me to understand the behaviour of Gesture recognisers. A working sample code will be a great help.

Thanks.

Ahmad F
  • 30,560
  • 17
  • 97
  • 143
xrage
  • 4,690
  • 4
  • 25
  • 31
  • You want pull to refresh in tableview some thing like http://www.techrepublic.com/blog/software-engineer/better-code-implement-pull-to-refresh-in-your-ios-apps/ – Anil Varghese Jun 29 '14 at 11:52
  • Yes, i need this functionality only but i have no clue of ObjC. Want to implement in swift. – xrage Jun 29 '14 at 11:57

19 Answers19

738

Pull to refresh is built in iOS. You could do this in swift like

let refreshControl = UIRefreshControl()

override func viewDidLoad() {
   super.viewDidLoad()

   refreshControl.attributedTitle = NSAttributedString(string: "Pull to refresh")
   refreshControl.addTarget(self, action: #selector(self.refresh(_:)), for: .valueChanged)
   tableView.addSubview(refreshControl) // not required when using UITableViewController
}

@objc func refresh(_ sender: AnyObject) {
   // Code to refresh table view  
}

At some point you could end refreshing.

refreshControl.endRefreshing()
Twitter khuong291
  • 11,328
  • 15
  • 80
  • 116
Anil Varghese
  • 42,757
  • 9
  • 93
  • 110
  • 21
    Thanks! I used this. Just some tweaking, for lazy loading. I would do: "lazy var refreshControl = UIRefreshControl()" I find it good practice to avoid forced unwrapping of variables, as it feels like it defeats the language safety. – nmdias Nov 16 '14 at 15:40
  • 12
    Just a quick note, you don't need to even add the refreshControl to the tableview. That's handled implicitly. – Kendrick Ledet Dec 14 '14 at 05:24
  • 1
    @KendrickLedet even if you don't use a UITableViewController? – Van Du Tran Feb 06 '15 at 22:51
  • 1
    I use this solution and the "Pull to refresh" text and spinner is above my table view. Also, there seems to be a ui glitch when pulling the table.. it seems to jump in the y position a bit.. – Van Du Tran Feb 06 '15 at 23:03
  • 4
    How would I be able to use this solution for the top of the tableview and at the bottom? – AustinT Mar 03 '15 at 00:22
  • 1
    Really not understanding the scenario, the Application crash when pulling too long while functioning perfectly while pull short and release. – A J Jun 02 '15 at 05:59
  • It works but causes the first section to have a weird offset if scrolling down during the refresh. – Leverin Feb 04 '16 at 12:59
  • 1
    What the point of lazy loading something you need right away? – scord Feb 04 '16 at 17:16
  • @scord is that question to me? didnt get you – Anil Varghese Feb 04 '16 at 18:22
  • Works well for collection views too, though with all the UI glitches described in previous comments (a weird offset and a jump in y position). – Vitalii May 19 '16 at 13:38
  • private func _installPullToRefresh() { _refreshControl = UIRefreshControl() _refreshControl?.addTarget(self, action: #selector(_pullToRefreshActivated), for: .valueChanged) if #available(iOS 10.0, *) { self.tableView.refreshControl = _refreshControl } else { self.tableView.addSubview(_refreshControl!) } } Update your answer so that it will be aligned with latest of iOS – T. Pasichnyk Sep 12 '17 at 11:46
  • 4
    Swift 4 update: refreshControl.addTarget(self, action: "refresh:", for: .valueChanged) – akseli Feb 19 '18 at 18:58
  • How Can I use it at the bottom of a collectionview instead of top. Since when I add refresh control as subview to my collectionview it adds to the top. – ranjith Gampa Jul 18 '18 at 14:11
  • Might be better to name it refreshTable or something else since the name refreshControl is a UIScrollView/UITableViewController ivar since iOS10/iOS6 – Saad Rehman Dec 28 '19 at 09:17
  • @AnilVarghese I want to add an arrow on table view it shows only when the user starts to pull to refresh – coceki Apr 12 '21 at 08:07
  • @AnilVarghese can you please check this question --> https://stackoverflow.com/q/67056902/15466427 – coceki Apr 13 '21 at 05:50
  • Hey there can I change the frame of the activity indicator that is shown in refresher?? Or can I change y position of refresher ??? – Rj19 May 18 '21 at 10:23
  • Rather than `tableView.addSubview(refreshControl)`, I would suggest `tableView. refreshControl = refreshControl`. – Rob Sep 23 '21 at 22:57
167

A solution with storyboard and Swift:

  1. Open your .storyboard file, select a TableViewController in your storyboard and "Enable" the Table View Controller: Refreshing feature in the Utilities.

    Inspector

  2. Open the associated UITableViewController class and add the following Swift 5 line into the viewDidLoad method.

    self.refreshControl?.addTarget(self, action: #selector(refresh), for: UIControl.Event.valueChanged)
    
  3. Add the following method above the viewDidLoad method

    func refresh(sender:AnyObject)
    {
        // Updating your data here...
    
        self.tableView.reloadData()
        self.refreshControl?.endRefreshing()
    }
    
grg
  • 5,023
  • 3
  • 34
  • 50
Blank
  • 4,872
  • 2
  • 28
  • 25
  • 8
    also, you can add refresh action with storyboard by control-dragging from Refresh Control in storyboard to your ViewController – zato Jan 18 '15 at 06:26
  • If my loading data is asynchronous, should I `put self.tableView.reloadData()` and `self.refreshControl?.endRefreshing()` in the callback? –  Dec 06 '15 at 19:58
  • Exactly! And put the `reloadData()` into the main queue to refresh your UI instantly: `dispatch_async(dispatch_get_main_queue(),{ self.tableView.reloadData() });` – Blank Dec 07 '15 at 09:40
  • No matter what, when using Swift 2.2 I can't get the refresh control to work in the storyboard only hard coded. – OhadM Apr 14 '16 at 06:55
  • I just updated the answer. Now it should work with Swift 2.2. – Blank Apr 14 '16 at 09:31
  • 1
    This way was preferred over the accepted answer for the reason that there is then no layout bugs (at least for me using swift 2+) – Ryan Walton Jul 23 '16 at 20:05
  • 1
    This answer should have the most votes and be the correct answer – krummens Aug 21 '16 at 03:21
  • can you please check this question -->https://stackoverflow.com/q/67056902/15466427 – coceki Apr 13 '21 at 05:58
117

UIRefreshControl is directly supported in each of UICollectionView, UITableView and UIScrollView (requires iOS 10+)!

Each one of these views has a refreshControl instance property, which means that there is no longer a need to add it as a subview in your scroll view, all you have to do is:

@IBOutlet weak var collectionView: UICollectionView!

override func viewDidLoad() {
    super.viewDidLoad()
    
    let refreshControl = UIRefreshControl()
    refreshControl.addTarget(self, action: #selector(doSomething), for: .valueChanged)
    
    // this is the replacement of implementing: "collectionView.addSubview(refreshControl)"
    collectionView.refreshControl = refreshControl
}

@objc func doSomething(refreshControl: UIRefreshControl) {
    print("Hello World!")
    
    // somewhere in your code you might need to call:
    refreshControl.endRefreshing()
}

Personally, I find it more natural to treat it as a property for scroll view more than adding it as a subview, especially because the only appropriate view to be as a superview for a UIRefreshControl is a scrollview, i.e the functionality of using UIRefreshControl is only useful when working with a scroll view; That's why this approach should be more obvious to set up the refresh control view.

However, you still have the option of using the addSubview based on the iOS version:

if #available(iOS 10.0, *) {
  collectionView.refreshControl = refreshControl
} else {
  collectionView.addSubview(refreshControl)
}
Ahmad F
  • 30,560
  • 17
  • 97
  • 143
55

Swift 4

var refreshControl: UIRefreshControl!

override func viewDidLoad() {
    super.viewDidLoad()

    refreshControl = UIRefreshControl()
    refreshControl.attributedTitle = NSAttributedString(string: "Pull to refresh")
    refreshControl.addTarget(self, action: #selector(refresh), for: .valueChanged)
    tableView.addSubview(refreshControl) 
}

@objc func refresh(_ sender: Any) {
    //  your code to reload tableView
}

And you could stop refreshing with:

refreshControl.endRefreshing()
Gilad Brunfman
  • 3,452
  • 1
  • 29
  • 29
  • Don't forget to mention `tableView.reloadData()` in your example ! – Konstantinos Natsios Mar 10 '17 at 20:41
  • Hi I create my refreshcontrol by that way: `var refControl: UIRefreshControl{ let rfControl = UIRefreshControl() rfControl.attributedTitle = NSAttributedString(string: "") rfControl.tintColor = UIColor(red:0.16, green:0.68, blue:0.9, alpha:1) rfControl.addTarget(self, action: #selector(getNewMessageList), for: .valueChanged) return rfControl }`. Then call endRefreshing(), it's not work(the refreshcontrol still show there), but follow your way, it worked, please let me know why? – lee Mar 17 '17 at 10:07
  • @lee Because rfControl is local and has no access from out. Try to initialize rfControl as a global variable to the VC: var rfControl = UIRefreshControl(), and stop with rfControl.endRefreshing() – Gilad Brunfman Mar 17 '17 at 12:21
  • A question: where the `.valueChanged` event is triggered? I don't understand this connection. – llanfair Jul 12 '17 at 18:58
  • when the UIRefreshControl is triggered, as you see when you addTarget: refreshControl.addTarget(self, action: #selector(self.refresh), for: UIControlEvents.valueChanged) – Gilad Brunfman Jul 12 '17 at 19:02
13

Swift 5

private var pullControl = UIRefreshControl()

pullControl.attributedTitle = NSAttributedString(string: "Pull to refresh")
        pullControl.addTarget(self, action: #selector(refreshListData(_:)), for: .valueChanged)
        if #available(iOS 10.0, *) {
            tableView.refreshControl = pullControl
        } else {
            tableView.addSubview(pullControl)
        }
// Actions
@objc private func refreshListData(_ sender: Any) {
        self.pullControl.endRefreshing() // You can stop after API Call
        // Call API
    }
Gurjinder Singh
  • 9,221
  • 1
  • 66
  • 58
9

In Swift use this,

If you wants to have pull to refresh in WebView,

So try this code:

override func viewDidLoad() {
    super.viewDidLoad()
    addPullToRefreshToWebView()
}

func addPullToRefreshToWebView(){
    var refreshController:UIRefreshControl = UIRefreshControl()

    refreshController.bounds = CGRectMake(0, 50, refreshController.bounds.size.width, refreshController.bounds.size.height) // Change position of refresh view
    refreshController.addTarget(self, action: Selector("refreshWebView:"), forControlEvents: UIControlEvents.ValueChanged)
    refreshController.attributedTitle = NSAttributedString(string: "Pull down to refresh...")
    YourWebView.scrollView.addSubview(refreshController)

}

func refreshWebView(refresh:UIRefreshControl){
    YourWebView.reload()
    refresh.endRefreshing()
}
Mohammad Zaid Pathan
  • 16,304
  • 7
  • 99
  • 130
5

Anhil's answer helped me a lot.

However, after experimenting further I noticed that the solution suggested sometimes causes a not-so-pretty UI glitch.

Instead, going for this approach* did the trick for me.

*Swift 2.1

//Create an instance of a UITableViewController. This will host your UITableView.
private let tableViewController = UITableViewController()

//Add tableViewController as a childViewController and set its tableView property to your UITableView.
self.addChildViewController(self.tableViewController)
self.tableViewController.tableView = self.tableView
self.refreshControl.addTarget(self, action: "refreshData:", forControlEvents: .ValueChanged)
self.tableViewController.refreshControl = self.refreshControl
Community
  • 1
  • 1
Leverin
  • 739
  • 8
  • 24
5

Details

  • Xcode Version 10.3 (10G8), Swift 5

Features

  • Ability to make "pull to refresh" programmatically
  • Protection from multi- "pull to refresh" events
  • Ability to continue animating of the activity indicator when view controller switched (e.g. in case of TabController)

Solution

import UIKit

class RefreshControl: UIRefreshControl {

    private weak var actionTarget: AnyObject?
    private var actionSelector: Selector?
    override init() { super.init() }

    convenience init(actionTarget: AnyObject?, actionSelector: Selector) {
        self.init()
        self.actionTarget = actionTarget
        self.actionSelector = actionSelector
        addTarget()
    }

    private func addTarget() {
        guard let actionTarget = actionTarget, let actionSelector = actionSelector else { return }
        addTarget(actionTarget, action: actionSelector, for: .valueChanged)
    }

    required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) }

    func endRefreshing(deadline: DispatchTime? = nil) {
        guard let deadline = deadline else { endRefreshing(); return }
        DispatchQueue.global(qos: .default).asyncAfter(deadline: deadline) { [weak self] in
            DispatchQueue.main.async { self?.endRefreshing() }
        }
    }

    func refreshActivityIndicatorView() {
        guard let selector = actionSelector else { return }
        let _isRefreshing = isRefreshing
        removeTarget(actionTarget, action: selector, for: .valueChanged)
        endRefreshing()
        if _isRefreshing { beginRefreshing() }
        addTarget()
    }

    func generateRefreshEvent() {
        beginRefreshing()
        sendActions(for: .valueChanged)
    }
}

public extension UIScrollView {

    private var _refreshControl: RefreshControl? { return refreshControl as? RefreshControl }

    func addRefreshControll(actionTarget: AnyObject?, action: Selector, replaceIfExist: Bool = false) {
        if !replaceIfExist && refreshControl != nil { return }
        refreshControl = RefreshControl(actionTarget: actionTarget, actionSelector: action)
    }

    func scrollToTopAndShowRunningRefreshControl(changeContentOffsetWithAnimation: Bool = false) {
        _refreshControl?.refreshActivityIndicatorView()
        guard   let refreshControl = refreshControl,
                contentOffset.y != -refreshControl.frame.height else { return }
        setContentOffset(CGPoint(x: 0, y: -refreshControl.frame.height), animated: changeContentOffsetWithAnimation)
    }

    private var canStartRefreshing: Bool {
        guard let refreshControl = refreshControl, !refreshControl.isRefreshing else { return false }
        return true
    }

    func startRefreshing() {
        guard canStartRefreshing else { return }
        _refreshControl?.generateRefreshEvent()
    }

    func pullAndRefresh() {
        guard canStartRefreshing else { return }
        scrollToTopAndShowRunningRefreshControl(changeContentOffsetWithAnimation: true)
        _refreshControl?.generateRefreshEvent()
    }

    func endRefreshing(deadline: DispatchTime? = nil) { _refreshControl?.endRefreshing(deadline: deadline) }
}

Usage

// Add refresh control to UICollectionView / UITableView / UIScrollView
private func setupTableView() {
    let tableView = UITableView()
    // ...
    tableView.addRefreshControll(actionTarget: self, action: #selector(refreshData))
}

@objc func refreshData(_ refreshControl: UIRefreshControl) {
    tableView?.endRefreshing(deadline: .now() + .seconds(3))
}

// Stop refreshing in UICollectionView / UITableView / UIScrollView
tableView.endRefreshing()

// Simulate pull to refresh in UICollectionView / UITableView / UIScrollView
tableView.pullAndRefresh()

Full Sample

Do not forget to add the solution code here

import UIKit

class ViewController: UIViewController {

    private weak var tableView: UITableView?

    override func viewDidLoad() {
        super.viewDidLoad()
        setupTableView()
    }

    private func setupTableView() {
        let tableView = UITableView()
        view.addSubview(tableView)
        tableView.translatesAutoresizingMaskIntoConstraints = false
        tableView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
        tableView.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
        tableView.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true
        tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
        tableView.dataSource = self
        tableView.delegate = self
        tableView.addRefreshControll(actionTarget: self, action: #selector(refreshData))
        self.tableView = tableView
    }
}

extension ViewController {
    @objc func refreshData(_ refreshControl: UIRefreshControl) {
        print("refreshing")
        tableView?.endRefreshing(deadline: .now() + .seconds(3))
    }
}

extension ViewController: UITableViewDataSource {
    func numberOfSections(in tableView: UITableView) -> Int { return 1 }
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return 20 }
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = UITableViewCell()
        cell.textLabel?.text = "\(indexPath)"
        return cell
    }
}

extension ViewController: UITableViewDelegate {
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        tableView.pullAndRefresh()
    }
}
Vasily Bodnarchuk
  • 24,482
  • 9
  • 132
  • 127
2

What the error is telling you, is that refresh isn't initialized. Note that you chose to make refresh not optional, which in Swift means that it has to have a value before you call super.init (or it's implicitly called, which seems to be your case). Either make refresh optional (probably what you want) or initialize it in some way.

I would suggest reading the Swift introductory documentation again, which covers this in great length.

One last thing, not part of the answer, as pointed out by @Anil, there is a built in pull to refresh control in iOS called UIRefresControl, which might be something worth looking into.

JustSid
  • 25,168
  • 7
  • 79
  • 97
2

I built a RSS feed app in which I have a Pull To refresh feature that originally had some of the problems listed above.

But to add to the users answers above, I was looking everywhere for my use case and could not find it. I was downloading data from the web (RSSFeed) and I wanted to pull down on my tableView of stories to refresh.

What is mentioned above cover the right areas but with some of the problems people are having, here is what I did and it works a treat:

I took @Blankarsch 's approach and went to my main.storyboard and select the table view to use refresh, then what wasn't mentioned is creating IBOutlet and IBAction to use the refresh efficiently

//Created from main.storyboard cntrl+drag refresh from left scene to assistant editor
@IBOutlet weak var refreshButton: UIRefreshControl

override func viewDidLoad() {
  ...... 
  ......
  //Include your code
  ......
  ......
  //Is the function called below, make sure to put this in your viewDidLoad 
  //method or not data will be visible when running the app
  getFeedData()
}

//Function the gets my data/parse my data from the web (if you havnt already put this in a similar function)
//remembering it returns nothing, hence return type is "-> Void"
func getFeedData() -> Void{
  .....
  .....
}

//From main.storyboard cntrl+drag to assistant editor and this time create an action instead of outlet and 
//make sure arguments are set to none and note sender
@IBAction func refresh() {
  //getting our data by calling the function which gets our data/parse our data
  getFeedData()

  //note: refreshControl doesnt need to be declared it is already initailized. Got to love xcode
  refreshControl?.endRefreshing()
}

Hope this helps anyone in same situation as me

Dom Bryan
  • 1,238
  • 2
  • 19
  • 39
2
func pullToRefresh(){

    let refresh = UIRefreshControl()
    refresh.addTarget(self, action: #selector(handleTopRefresh(_:)), for: .valueChanged )
    refresh.tintColor = UIColor.appBlack
    self.tblAddressBook.addSubview(refresh)

}
@objc func handleTopRefresh(_ sender:UIRefreshControl){
    self.callAddressBookListApi(isLoaderRequired: false)
    sender.endRefreshing()
}
Naresh
  • 16,698
  • 6
  • 112
  • 113
Hiren
  • 260
  • 3
  • 3
1

I suggest to make an Extension of pull To Refresh to use in every class.

1) Make an empty swift file : File - New - File - Swift File.

2) Add the Following

    //  AppExtensions.swift

    import Foundation
    import UIKit    

    var tableRefreshControl:UIRefreshControl = UIRefreshControl()    

    //MARK:- VIEWCONTROLLER EXTENSION METHODS
    public extension UIViewController
    {
        func makePullToRefreshToTableView(tableName: UITableView,triggerToMethodName: String){

            tableRefreshControl.attributedTitle = NSAttributedString(string: "TEST: Pull to refresh")
            tableRefreshControl.backgroundColor = UIColor.whiteColor()
            tableRefreshControl.addTarget(self, action: Selector(triggerToMethodName), forControlEvents: UIControlEvents.ValueChanged)
            tableName.addSubview(tableRefreshControl)
        }
        func makePullToRefreshEndRefreshing (tableName: String)
        {
            tableRefreshControl.endRefreshing()
//additional codes

        }
    }    

3) In Your View Controller call these methods as :

  override func viewWillAppear(animated: Bool) {

self.makePullToRefreshToTableView(bidderListTable, triggerToMethodName: "pullToRefreshBidderTable")
}

4) At some point you wanted to end refreshing:

  func pullToRefreshBidderTable() {
self.makePullToRefreshEndRefreshing("bidderListTable")    
//Code What to do here.
}
OR    
self.makePullToRefreshEndRefreshing("bidderListTable")
Alvin George
  • 14,148
  • 92
  • 64
1

For the pull to refresh i am using

DGElasticPullToRefresh

https://github.com/gontovnik/DGElasticPullToRefresh

Installation

pod 'DGElasticPullToRefresh'

import DGElasticPullToRefresh

and put this function into your swift file and call this funtion from your

override func viewWillAppear(_ animated: Bool)

     func Refresher() {
      let loadingView = DGElasticPullToRefreshLoadingViewCircle()
      loadingView.tintColor = UIColor(red: 255.0/255.0, green: 255.0/255.0, blue: 255.0/255.0, alpha: 1.0)
      self.table.dg_addPullToRefreshWithActionHandler({ [weak self] () -> Void in

          //Completion block you can perfrom your code here.

           print("Stack Overflow")

           self?.table.dg_stopLoading()
           }, loadingView: loadingView)
      self.table.dg_setPullToRefreshFillColor(UIColor(red: 255.0/255.0, green: 57.0/255.0, blue: 66.0/255.0, alpha: 1))
      self.table.dg_setPullToRefreshBackgroundColor(self.table.backgroundColor!)
 }

And dont forget to remove reference while view will get dissapear

to remove pull to refresh put this code in to your

override func viewDidDisappear(_ animated: Bool)

override func viewDidDisappear(_ animated: Bool) {
      table.dg_removePullToRefresh()

 }

And it will looks like

enter image description here

Happy coding :)

Azharhussain Shaikh
  • 1,654
  • 14
  • 17
1

You can achieve this by using few lines of code. So why you are going to stuck in third party library or UI. Pull to refresh is built in iOS. You could do this in swift like

enter image description here

var pullControl = UIRefreshControl()

override func viewDidLoad() {
   super.viewDidLoad()

   pullControl.attributedTitle = NSAttributedString(string: "Pull to refresh")
   pullControl.addTarget(self, action: #selector(pulledRefreshControl(_:)), for: UIControl.Event.valueChanged)
   tableView.addSubview(pullControl) // not required when using UITableViewController
}

@objc func pulledRefreshControl(sender:AnyObject) {
   // Code to refresh table view  
}

Mr.Javed Multani
  • 12,549
  • 4
  • 53
  • 52
0

you can use this subclass of tableView:

import UIKit

protocol PullToRefreshTableViewDelegate : class {
    func tableViewDidStartRefreshing(tableView: PullToRefreshTableView)
}

class PullToRefreshTableView: UITableView {

    @IBOutlet weak var pullToRefreshDelegate: AnyObject?
    private var refreshControl: UIRefreshControl!
    private var isFirstLoad = true

    override func willMoveToSuperview(newSuperview: UIView?) {
        super.willMoveToSuperview(newSuperview)

        if (isFirstLoad) {
            addRefreshControl()
            isFirstLoad = false
        }
    }

    private func addRefreshControl() {
        refreshControl = UIRefreshControl()
        refreshControl.attributedTitle = NSAttributedString(string: "Pull to refresh")
        refreshControl.addTarget(self, action: "refresh", forControlEvents: .ValueChanged)
        self.addSubview(refreshControl)
    }

    @objc private func refresh() {
       (pullToRefreshDelegate as? PullToRefreshTableViewDelegate)?.tableViewDidStartRefreshing(self)
    }

    func endRefreshing() {
        refreshControl.endRefreshing()
    }

}

1 - in interface builder change the class of your tableView to PullToRefreshTableView or create a PullToRefreshTableView programmatically

2 - implement the PullToRefreshTableViewDelegate in your view controller

3 - tableViewDidStartRefreshing(tableView: PullToRefreshTableView) will be called in your view controller when the table view starts refreshing

4 - call yourTableView.endRefreshing() to finish the refreshing

Chuy47
  • 2,391
  • 1
  • 30
  • 29
0

This is how I made it work using Xcode 7.2 which I think is a major bug. I'm using it inside my UITableViewController inside my viewWillAppear

refreshControl = UIRefreshControl()
refreshControl!.addTarget(self, action: "configureMessages", forControlEvents: .ValueChanged)
refreshControl!.beginRefreshing()

configureMessages()

func configureMessages() {
    // configuring messages logic here

    self.refreshControl!.endRefreshing()
}

As you can see, I literally have to call the configureMessage() method after setting up my UIRefreshControl then after that, subsequent refreshes will work fine.

jaytrixz
  • 4,059
  • 7
  • 38
  • 57
0

For 2023, this simple

override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)
    
    your data source = latest ...
    table.reloadData()
    
    table.refreshControl = UIRefreshControl()
    table.refreshControl?.addTarget(self,
       action: #selector(pulldown), for: .valueChanged)
    table.refreshControl?.tintColor = .clear
}

@objc func pulldown() {
    your data source = latest ...
    table.reloadData()
    DispatchQueue.main.async { self.table.refreshControl?.endRefreshing() }
}

Very often, you don't want the "spinner". This line is the easiest way to hide the spinner:

    table.refreshControl?.tintColor = .clear

That's it.


If (for some obscure reason) you truly want to subclass UIRefreshControl, there's an excellent recent answer here that shows how to do that.

Fattie
  • 27,874
  • 70
  • 431
  • 719
-1

Others Answers Are Correct But for More Detail check this Post Pull to Refresh

Enable refreshing in Storyboard

When you’re working with a UITableViewController, the solution is fairly simple: First, Select the table view controller in your storyboard, open the attributes inspector, and enable refreshing:

A UITableViewController comes outfitted with a reference to a UIRefreshControl out of the box. You simply need to wire up a few things to initiate and complete the refresh when the user pulls down.

Override viewDidLoad()

In your override of viewDidLoad(), add a target to handle the refresh as follows:

override func viewDidLoad() {
    super.viewDidLoad()
    // Do any additional setup after loading the view, typically from a nib.
        
    self.refreshControl?.addTarget(self, action: "handleRefresh:", forControlEvents: UIControlEvents.ValueChanged)
}
  1. Since I’ve specified “handleRefresh:” (note the colon!) as the action argument, I need to define a function in this UITableViewController class with the same name. Additionally, the function should take one argument
  2. We’d like this action to be called for the UIControlEvent called ValueChanged
  3. Don't forget to call refreshControl.endRefreshing()

For more information Please go to mention Link and all credit goes to that post

Community
  • 1
  • 1
Singhak
  • 8,508
  • 2
  • 31
  • 34
  • Whilst this may theoretically answer the question, [it would be preferable](//meta.stackoverflow.com/q/8259) to include the essential parts of the answer here, and provide the link for reference. – Tunaki Feb 08 '16 at 08:28
-1

Due to less customisability, code duplication and bugs which come with pull to refresh control, I created a library PullToRefreshDSL which uses DSL pattern just like SnapKit

// You only have to add the callback, rest is taken care of
tableView.ptr.headerCallback = { [weak self] in // weakify self to avoid strong reference
    DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(2)) { // your network call
        self?.tableView.ptr.isLoadingHeader = false // setting false will hide the view
    }
}

You only have to add magical keyword ptr after any UIScrollView subclass i.e. UITableView/UICollectionView

You dont have to download the library, you can explore and modify the source code, I am just pointing towards a possible implementation of pull to refresh for iOS

Salmaan
  • 3,543
  • 8
  • 33
  • 59