others-how to solve Cannot override mutable property 'refreshControl' of type 'UIRefreshControl?' with covariant type 'UIRefreshControl' when trying to add refresh control to your tableviewcontroller ?

1. Purpose

In this post, I would demo how to solve the below error when trying to add customized refresh control to UITableViewController with swift in iOS developement:

When we add this line to the class:

class MyTableViewController: UITableViewController {
    lazy var refreshControl = UIRefreshControl()
    ....
}

We got this error from xcode

Cannot override mutable property 'refreshControl' of type 'UIRefreshControl?' with covariant type 'UIRefreshControl'

2. Environment

xcode 12

swift 5

3. The reason

When we add our own refreshControll to our viewcontroller, it conflicts with the original refreshControl in UITableViewController. If we want to define another control, then change the name… refreshControl is a UIScrollView/UITableViewController internal variable since iOS10/iOS6 [respectively]. We’re trying to redefine it and that is what causing the error.

Or we can just redefine the variable like this:

self.refreshControl = UIRefreshControl()

You can find that the field refreshControl is already in of UITableViewController :

image-20210425110809349

4. The Solution

We can do this job as follows:

class MyTableViewController: UITableViewController {
    
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        self.refreshControl = UIRefreshControl() //key point

        if let refreshControl = self.refreshControl {
            refreshControl.attributedTitle = NSAttributedString(string: "Pull to refresh")
            refreshControl.addTarget(self, action: "refresh:", for: .valueChanged)
            
            tableView.addSubview(refreshControl)
        }
        
    }
    
    @objc func refresh(_ sender: AnyObject) {
        print("refrehed")
        DispatchQueue.global().async {
            sleep(2)
            DispatchQueue.main.async {
                self.refreshControl?.endRefreshing()
            }
        }
        
    }
    ....
}

In the above code, we did the following things:

  • We created our own refreshControl to replace the inner refresh control of UITableViewController:
self.refreshControl = UIRefreshControl() //key point
  • We add our customized Refresh Control to the UITableViewController, and set its attributes like text and action responder:
if let refreshControl = self.refreshControl {
  refreshControl.attributedTitle = NSAttributedString(string: "Pull to refresh")
  refreshControl.addTarget(self, action: "refresh:", for: .valueChanged)

  tableView.addSubview(refreshControl)
}
  • We define a function to action to the RefreshControl
@objc func refresh(_ sender: AnyObject) 
  • At last we simuate the long running process , run it in the seperate thread by using DispatchQueue.global()
@objc func refresh(_ sender: AnyObject) {
  print("refrehed")
  DispatchQueue.global().async {
    sleep(2)
    DispatchQueue.main.async {
      self.refreshControl?.endRefreshing()
    }
  }
}

In the above code, we use the DispathQueue to run the heavy-load task, what is a DispatchQueue?

A DispatchQueue is an abstraction layer on top of the GCD queue that allows you to perform tasks asynchronously and concurrently in your application. Tasks are always executed in the order they’re added to the queue.

Then we sleeped for 2 seconds in the seperate thread, and used the DispatchQueue.main.async to update the UI.

DispatchQueue. main (the main queue) is a serial queue. sync and async do not determine serialization or currency of a queue, but instead refer to how the task is handled. … Asynchronous function returns control on the current queue right after task has been sent to be performed on the different queue

To summarize, we can run heavy-loading tasks in DispatchQueue.global(), and then update the UI in DispatchQueue.main.

Now it works!

4. Summary

In this post, I demonstrated how to solve the conflit of adding customized refresh control to UITableViewController.