Pull to refresh in Swift - ios

I'm trying to implement pull to refresh in my table view app. I've been looking around at people's examples and I've gathered that this is pretty much the gist of it:
var refreshControl:UIRefreshControl!
override func viewDidLoad()
{
super.viewDidLoad()
self.refreshControl = UIRefreshControl()
self.refreshControl.attributedTitle = NSAttributedString(string: "Pull to refresh")
self.refreshControl.addTarget(self, action: "refresh:", forControlEvents: UIControlEvents.ValueChanged)
self.tableView.addSubview(refreshControl)
}
func refresh(sender:AnyObject)
{
// Code to refresh table view
}
However the only examples I can find are from a while ago and I know the language has changed a lot since then! When I try to use the code above, I get the following error next to my declaration of refreshControl:
Cannot override with a stored property 'refresh control'
My first thought before reading other examples is that I would have to declare the variable like so:
var refreshControl:UIRefreshControl = UIRefreshControl()
Like I did with some other variables but I guess not.
Any ideas what the issue is?

I gather your class inherits UITableViewController. UITableViewController already declares the refreshControl property like this:
#availability(iOS, introduced=6.0)
var refreshControl: UIRefreshControl?
You don't need to override it. Just get rid of your var declaration and assign to the inherited property.
Since the inherited property is Optional, you need to use a ? or ! to unwrap it:
refreshControl = UIRefreshControl()
refreshControl!.attributedTitle = NSAttributedString(string: "Pull to refresh")
refreshControl!.addTarget(self, action: "refresh:", forControlEvents: UIControlEvents.ValueChanged)
tableView.addSubview(refreshControl!)

Just add this code in viewDidLoad
self.refreshControl = UIRefreshControl()
self.refreshControl!.attributedTitle = NSAttributedString(string: "Pull to refresh")
self.refreshControl!.addTarget(self, action: "refresh:", forControlEvents: UIControlEvents.ValueChanged)
self.tableView.addSubview(refreshControl!)
Works Fine :)

Related

#Selector as a parameter?

I want to create a function that adds a refreshControll to a UITableView.
The problem I have is the #selector:
extension UITableView{
func refreshTableView(callFunction: #selector()){
refreshControl = UIRefreshControl()
refreshControl?.attributedTitle = NSAttributedString(string: "Pull to refresh")
refreshControl?.addTarget(self, action: #selector(callFunction), for: .valueChanged)
self.addSubview(refreshControl!)
}
}
I want to specify which function to call when I use this function.
Is this even possible?
#selector(...) is an expression of type Selector, so your method should accept a Selector object and pass it directly to the action: parameter.
extension UITableView {
func refreshTableView(callFunction: Selector) {
// ...
refreshControl?.addTarget(self, action: callFunction, for: .valueChanged)
// ...
}
}
myTableView.refreshTableView(callFunction: #selector(whatever))

UIRefreshControl appears on top of collection view items instead of behind

I have a UICollectionView with the regular pull to refresh implementation, but somehow the spinner and the "pull to refresh" text appear above the collection view items;
How can I make it behind the items?
This is how I add the UIRefreshControll to the UICollectionView
let refreshControl = UIRefreshControl()
refreshControl.attributedTitle = NSAttributedString(string: "Pull down to refresh")
refreshControl.addTarget(self, action: #selector(pullToRefresh), for: UIControlEvents.valueChanged)
collectionView?.refreshControl = refreshControl
The way I figure this out is to change the refreshControll zPosition to be behind every view with the following:
refreshControl.layer.zPosition = -1
Hope this helps anyone further.
Try this :
#IBOutlet weak var collectionView: UICollectionView!
var refresher:UIRefreshControl!
override func viewDidLoad() {
super.viewDidLoad()
let refresher = UIRefreshControl()
self.collectionView!.alwaysBounceVertical = true
self.refresher.tintColor = UIColor.red
self.refresher.addTarget(self, action: #selector(loadData), for: .valueChanged)
self.collectionView!.addSubview(refresher)
}
func loadData() {
//code to execute during refresher
.
.
.
stopRefresher() //Call this to stop refresher
}
func stopRefresher() {
self.refresher.endRefreshing()
}

Pull to refresh not performing refreshing function?

I am coding an app and have a set up queryAndAppend functions that query a DB and append the info pulled to a set of arrays (which are then used to iterate through cells in a table view). The query functions work appropriately as they are called in the viewDidLoad(). I want a pull to refresh function to additionally call the functions (and thus update the table cells) but it is not working properly. Here is the relevant code:
var refreshControl = UIRefreshControl()
override func viewDidLoad() {
super.viewDidLoad()
myQueryandAppend(completion: {
self.tableView.reloadData()
})
upComingQueryandAppend(completion: {
self.tableView.reloadData()
})
refreshControl = UIRefreshControl()
refreshControl.attributedTitle = NSAttributedString(string: "Pull to refresh")
refreshControl.addTarget(self, action: #selector(MainPageVC.refresh(_:)), for: UIControlEvents.valueChanged)
}
func refresh(_ sender: AnyObject){
myQueryandAppend(completion: {
self.tableView.reloadData()
self.refreshControl.endRefreshing()
})
upComingQueryandAppend(completion:{
self.tableView.reloadData()
self.refreshControl.endRefreshing()
})
}
Not entirely sure why its not working as I have a very similar set up in a different view controller (with a different query/append function) that works properly with pull to refresh.
If the class is a UIViewController but not a UITableViewController, you need to add the refreshControl as a subview to the tableView manually:
tableView.addSubview(refreshControl)
So, viewDidLoad() should looks like this:
override func viewDidLoad() {
super.viewDidLoad()
myQueryandAppend(completion: {
self.tableView.reloadData()
})
upComingQueryandAppend(completion: {
self.tableView.reloadData()
})
refreshControl = UIRefreshControl()
refreshControl.attributedTitle = NSAttributedString(string: "Pull to refresh")
refreshControl.addTarget(self, action: #selector(MainPageVC.refresh(_:)), for: UIControlEvents.valueChanged)
// here is the extra job!
tableView.addSubview(refreshControl)
}
In iOS 10+ you can just assign the refresh control directly to a tableview / scrollview's .RefreshControl property
tableView.RefreshControl = refreshControl

Pull to refresh not working in iOS WebView

I've implemented a straight forward WKWebView in iOS.
var refreshControl = UIRefreshControl()
refreshControl.addTarget(self, action: Selector("refreshWebView"), forControlEvents: UIControlEvents.ValueChanged)
This is in viewDidLoad()
And the corresponding refreshWebView function. But when I run it on mobile or simulator, no matter how much I drag, there is no pull to refresh action. Nothing is triggered, and it almost feels like I am stuck to the upper bounds of the screen. Where can be going wrong?
Try to change your code like this, If you want to pull to refresh the webView you need to addSubView UIRefreshControl object in the ScrollView of webView like this
let refreshControl = UIRefreshControl()
refreshControl.addTarget(self, action: #selector(self.refreshWebView(_:)), forControlEvents: UIControlEvents.ValueChanged)
webView.scrollView.addSubview(refreshControl)
Add func like this
func refreshWebView(sender: UIRefreshControl) {
print("refersh")
webView.loadRequest(NSURLRequest(URL: NSURL(string: "https://google.com")!))
sender.endRefreshing()
}
Hope this will help you.
SWIFT 4 Solution (Based on Nirav D answer)
You can simply call this setup method in your viewDidLoad:
private func setupRefreshControl() {
let refreshControl = UIRefreshControl()
refreshControl.addTarget(self, action: #selector(refreshWebView(sender:)), for: UIControlEvents.valueChanged)
webView.scrollView.addSubview(refreshControl)
}
#objc
private func refreshWebView(sender: UIRefreshControl) {
print("refreshing...")
webView.load(URLRequest(url: url))
sender.endRefreshing()
}
Just to say looking at this in Swift 3 and the answer from JAck helped me solve this issue:
in loadView()/viewDidLoad()
webView.scrollView.bounces = true
let refreshControl = UIRefreshControl()
refreshControl.addTarget(self, action: #selector(ViewController.refreshWebView), for: UIControlEvents.valueChanged)
webView.scrollView.addSubview(refreshControl)
and then a separate function
func refreshWebView(sender: UIRefreshControl) {
print("refersh")
//
sender.endRefreshing()
}
And I'm now seeing the refresh being printed.
You have to enable bouncing for the scrollview of the webview:
webView.scrollView.bounces = true
This will let you drag the webview down which will trigger your UIRefreshControl.
Following code will put the refresh control in scrollview of webview.Initially it will load google.com. To see pull to refresh clearly I have set white color to the background of scroll view so it is clearly visible and on pull to refresh webview opens facebook page.
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
self.refreshControl = UIRefreshControl.init()
refreshControl!.addTarget(self, action:#selector(refreshControlClicked), for: UIControlEvents.valueChanged)
self.webView.scrollView.addSubview(self.refreshControl!)
self.webView.scrollView.backgroundColor = UIColor.white
self.webView.delegate = self
let url:URL = URL.init(string:"https://www.google.com")!
self.loadRequestWithUrl(URLRequest.init(url: url))
}
func webViewDidStartLoad(_ webView: UIWebView) {
NSLog("website loaded")
}
func loadRequestWithUrl(_ urlRequest : URLRequest?){
if(urlRequest != nil){
self.webView.loadRequest(urlRequest!)
}
}
func refreshControlClicked(){
let url:URL = URL.init(string:"https://www.facebook.com")!
self.loadRequestWithUrl(URLRequest.init(url: url))
}
Try to declare this like below.
refreshControl.addTarget(self, action: #selector(yourViewController.refreshWebView), forControlEvents: UIControlEvents.ValueChanged)
Hope this works..

Pull to refresh in UICollectionView in ViewController

I used the following code at a UICollectionViewController
override func viewDidLoad() {
self.collectionView!.alwaysBounceVertical = true
let refresher = UIRefreshControl()
refresher.addTarget(self, action: "refreshStream", forControlEvents: .ValueChanged)
refreshControl = refresher
collectionView!.addSubview(refreshControl!)
}
func refreshStream() {
print("refresh")
self.collectionView?.reloadData()
refreshControl?.endRefreshing()
}
Now I need it to work with a UICollectionView inside a UIViewController and I googled for an hour now but can't get it working.
I appreciate any help.
New swift code changed in calling action method you could do rewrite like this
#IBOutlet weak var collectionView: UICollectionView!
var refresher:UIRefreshControl!
override func viewDidLoad() {
super.viewDidLoad()
self.refresher = UIRefreshControl()
self.collectionView!.alwaysBounceVertical = true
self.refresher.tintColor = UIColor.red
self.refresher.addTarget(self, action: #selector(loadData), for: .valueChanged)
self.collectionView!.addSubview(refresher)
}
#objc func loadData() {
self.collectionView!.refreshControl.beginRefreshing()
//code to execute during refresher
.
.
.
stopRefresher() //Call this to stop refresher
}
func stopRefresher() {
self.collectionView!.refreshControl.endRefreshing()
}
Swift 5 solution
As #fishspy already mentioned, that's the way to put your collection view inside a view controller, but I'm gonna share also how to connect your refresh control to the collection view in a cleaner way.
Since iOS 10 there's a dedicated property for the refresh control. Apart of that, I'd also recommend to directly initialise your refresh control as a property, declaring it private and doing the following things:
#IBOutlet private weak var collectionView: UICollectionView!
private let refreshControl = UIRefreshControl()
override func viewDidLoad() {
super.viewDidLoad()
refreshControl.addTarget(self, action: #selector(didPullToRefresh(_:)), for: .valueChanged)
collectionView.alwaysBounceVertical = true
collectionView.refreshControl = refreshControl // iOS 10+
}
#objc
private func didPullToRefresh(_ sender: Any) {
// Do you your api calls in here, and then asynchronously remember to stop the
// refreshing when you've got a result (either positive or negative)
refreshControl.endRefreshing()
}
var refreshControl:UIRefreshControl!
override func viewDidLoad() {
super.viewDidLoad()
self.refreshControl = UIRefreshControl()
self.refreshControl.attributedTitle = NSAttributedString(string: "Pull to refresh")
self.refreshControl.addTarget(self, action: #selector(PricingPlansCollectionViewController.reload), for: .valueChanged)
collectionView!.addSubview(refreshControl)
}
func refresh(sender:AnyObject)
{
//DO
}
From the storyboard, you need to link the collection view to the appropriate controller file, using Ctrl + Drag from the collection view object. It needs to be linked via an #IBOutlet.
Also, you should Ctrl + Drag from the collection view to the view controller object on the storyboard and select Data Source and Delegate so that the collection view is correctly linked.
Let me know if this helps or if I have misunderstood your situation.
For me, i had:
func setupCollectionView(){
self.refreshControl = UIRefreshControl()
self.refreshControl.attributedTitle = NSAttributedString(string: "Refreshing content...")
self.refreshControl.addTarget(self, action: #selector(self.reload), for: .valueChanged)
collectionView!.addSubview(refreshControl)
}
It still wasn't working (I believe i had constraints preventing it somehow), so i added:
collectionView.alwaysBounceVertical = true
then all was good. Just in case someone else is facing the same issue.
For a better understanding, the full implementation
#IBOutlet private weak var collectionView: UICollectionView!
let refreshControl = UIRefreshControl()
override func viewDidLoad() {
super.viewDidLoad()
setupCollectionView()
}
func setupCollectionView(){
collectionView.delegate = self
collectionView.dataSource = self
collectionView.alwaysBounceVertical = true
self.refreshControl = UIRefreshControl()
self.refreshControl.attributedTitle = NSAttributedString(string: "Refreshing content...")
self.refreshControl.addTarget(self, action: #selector(self.refresh), for: .valueChanged)
collectionView!.addSubview(refreshControl)
}
#objc func refresh(_ sender:AnyObject) {
//do refreshing stuff
}
private let refreshControl = UIRefreshControl()
refreshControl.addTarget(self, action: #selector(youFunction), for: .valueChanged)
refreshControl.attributedTitle = NSAttributedString(string: "Fetching Data ...", attributes: nil)
DispatchQueue.main.async {
self.refreshControl.endRefreshing()
yourCollectionViewOrTableView.reloadData()
}

Resources