UIRefreshControl overlapping headerview in uitableview - uitableview

I have a UITableView within my UIViewController and have added a UIRefreshControl as so:
lazy var refreshControl: UIRefreshControl = {
let refreshControl = UIRefreshControl()
refreshControl.addTarget(self, action: #selector(ListViewController.handleRefresh), for: UIControlEvents.valueChanged)
return refreshControl
}()
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.addSubview(self.refreshControl)
}
It works fine but produces a nasty UI effect where it overlaps the header view.
I have tried setting the frame height and contentInset but neither worked.

What I discovered was that I had to put a delay on the UIRefreshControl dismissal for the UI to function properly.
This may be very unique to me but I am posting it just in case.
let when = DispatchTime.now() + 0.5 // change to desired number of seconds
DispatchQueue.main.asyncAfter(deadline: when) {
refreshControl.endRefreshing()
}

Related

Pull to refresh for UICollectionView in ViewController jumps when applying NSDiffableDataSourceSnapshot

I've added a UICollectionView as a subview to my ViewController. I've also set a UIRefreshControl for the UICollectionView which applies a NSDiffableDataSourceSnapshot when I pull down to refresh. The animation jumps when applying the snapshot (note this happens even when the navigator bar title isn't large.)
Here are the relevant snippets of code
var collectionView: UICollectionView! = nil
var dataSource: UICollectionViewDiffableDataSource<Section, Item>! = nil
override func viewDidLoad() {
super.viewDidLoad()
let collectionView = UICollectionView(frame: view.bounds, collectionViewLayout:
generateLayout())
view.addSubview(collectionView)
collectionView.autoresizingMask = [.flexibleHeight, .flexibleWidth]
collectionView.backgroundColor = .systemGroupedBackground
self.collectionView = collectionView
collectionView.delegate = self
let refreshControl = UIRefreshControl()
refreshControl.addTarget(self, action: #selector(doSomething), for: .valueChanged)
collectionView.refreshControl = refreshControl
}
#objc func doSomething(refreshControl: UIRefreshControl) {
DispatchQueue.main.async {
var dataSourceSnapshot = NSDiffableDataSourceSnapshot<Section, Item>()
// add data
dataSource.apply(dataSourceSnapshot)
}
refreshControl.endRefreshing()
}
Are there any changes I can make to apply the snapshot without the sudden jump?
Unfortunately Apple Engineers can not properly solve that issue since iOS 6.
Long story short: you don't want to endRefreshing while your scroll view isTracking. UIRefreshControl modifies contentInset and contentOffset of the scrollView, and it always caused jumps like that.
If you have a network request state anywhere, or any other flag indicating that the loading is in progress you could simply do:
#objc func doSomething(refreshControl: UIRefreshControl) {
loading = true
DispatchQueue.main.async {
var dataSourceSnapshot = NSDiffableDataSourceSnapshot<Section, Item>()
// add data
dataSourceSnapshot.appendSections([.test])
dataSourceSnapshot.appendItems([.test])
self.dataSource.apply(dataSourceSnapshot)
self.loading = false
}
}
override func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
if !loading && collectionView.refreshControl!.isRefreshing {
collectionView.refreshControl!.endRefreshing()
}
}
I don't quite like loading flags like that, but that's just an example. Hopefully you have better source of truth indicating if some "work" is still in progress.
I guess there might be a nicer way of solving it (by patching classes in runtime), but it usually takes time to investigate. I wonder if that solution satisfies your needs.

UIRefreshControl - collectionView jumps when scrolling

I have a problem with UIRefreshControl. When I scroll down the collectionView the content of the collectionView jumps a little bit up.
GIF here: https://imgur.com/a/8rK5s
I have already seen some question about this exact problem, however, I haven't been able to find an answer that would work for me. (for example this question)
I have a UICollectionViewController inside another UIViewCotroller as a subview.
I have implemented UIRefreshControl like this:
let refreshControl = UIRefreshControl()
if #available(iOS 10.0, *){
self.collectionView?.refreshControl = self.refreshControl
} else{
self.collectionView?.insertSubview(self.refreshControl, at: 0)
}
self.collectionView?.alwaysBounceVertical = true
refreshControl.addTarget(self, action: #selector(refreshControlChanged), for: .valueChanged)
refreshControl.tintColor = UIColor.custom.blue.classicBlue
Also, I want my collectionView to refresh data after the user stops dragging.
#objc func refreshControlChanged(){
if !(self.collectionView?.isDragging)!{
nextRecommended()
}
}
override func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
if refreshControl.isRefreshing{
nextRecommended()
}
}
And inside nextRecommended():
DispatchQueue.main.async {
self.refreshControl.endRefreshing()
self.collectionView?.reloadData()
}
Any help much appreciated.

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

UIRefreshControl and tableFooterView = UIView()

In my tableView I need to have self.tableView.tableFooterView = UIView() otherwise the table scrolls too far down. The problem is if I add this then my pull to refresh UIRefreshControl no longer works. Is there a way to have both?
There's no reason why you can't have both. Have you added the UIRefreshControl in the right way?
Here's working code from a project of mine:
var pullToRefreshControl : UIRefreshControl!
override func viewDidLoad() {
super.viewDidLoad()
self.setFooterView()
self.addPullToRefreshView()
}
private func setFooterView() {
let footerView = UIView()
let footerLabel = UILabel()
footerLabel.text = "Table Footer"
footerLabel.sizeToFit()
footerView.addSubview(footerLabel)
self.tableView.tableFooterView = footerView
}
private func addPullToRefreshView() {
pullToRefreshControl = UIRefreshControl()
pullToRefreshControl.attributedTitle = NSAttributedString(string: "Pull To Refresh")
pullToRefreshControl.addTarget(self, action: "refresh:", forControlEvents: .ValueChanged)
self.tableView.addSubview(pullToRefreshControl!)
}
Make a Custom Control refresh control for This

Resources