UIRefreshControl weird jump when scrolling down with preferLargeTitles enabled - swift

My UIRefreshControl is not working properly when I scroll down my collectionview. The refreshControl pops out even with a slight scrolling and causes the title to jump down a bit.
I tried with:
extendedLayoutIncludesOpaqueBars = true
And that fixes something but still does a small jump (almost unnoticeable) when scrolling down to engage the refresh control.
If I do prefersLargeTitles = false, it works fine, no jumping.

I spent hours trying to fix it thanks to the poor Apple documentation on UIRefreshControl but finally found a solution. You have to add your refreshControl in the viewDidAppear method, I was adding it on the viewDidLoad method:
override func viewDidAappear(_ animated: Bool) {
super.viewDidAppear(animated)
collectionView.refreshControl = refreshControl
}

Related

Prefer Large Titles and RefreshControl not working well

I am using this tutorial to implement a pull-to-refresh behavior with the RefreshControl. I am using a Navigation Bar. When using normal titles everything works good. But, when using "Prefer big titles" it doesn't work correctly as you can see in the following videos. Anyone knows why? The only change between videos is the storyboard check on "Prefer Large Titles".
I'm having the same problem, and none of the other answers worked for me.
I realised that changing the table view top constraint from the safe area to the superview fixed that strange spinning bug.
Also, make sure the constant value for this constraint is 0 🤯.
At the end what worked for me was:
In order to fix the RefreshControl progress bar disappearing bug with large titles:
self.extendedLayoutIncludesOpaqueBars = true
In order to fix the list offset after refreshcontrol.endRefreshing():
let top = self.tableView.adjustedContentInset.top
let y = self.refreshControl!.frame.maxY + top
self.tableView.setContentOffset(CGPoint(x: 0, y: -y), animated:true)
If you were using tableView.tableHeaderView = refreshControl or tableView.addSubView(refreshControl) you should try using tableView.refreshControl = refreshControl
It seems there are a lot of different causes that could make this happen, for me I had a TableView embedded within a ViewController. I set the top layout guide of the tableview to the superview with 0. After all of that still nothing until I wrapped my RefreshControl end editing in a delayed block:
DispatchQueue.main.async {
if self.refreshControl.isRefreshing {
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0, execute: {
self.refreshControl.endRefreshing()
})
}
}
The only working solution for me is combining Bruno's suggestion with this line of code:
tableView.contentInsetAdjustmentBehavior = .always
I've faced the same problem. Call refreshControl endRefreshing before calling further API.
refreshControl.addTarget(controller, action: #selector(refreshData(_:)), for: .valueChanged)
#objc func refreshData(_ refreshControl: UIRefreshControl) {
refreshControl.endRefreshing()
self.model.loadAPICall {
self.tableView.reloadData()
}
}
The only solution that worked for me using XIBs was Bruno's one:
https://stackoverflow.com/a/54629641/2178888
However I did not want to use a XIB. I struggled a lot trying to make this work by code using AutoLayout.
I finally found a solution that works:
override func loadView() {
super.loadView()
let tableView = UITableView()
//configure tableView
self.view = tableView
}
I had this issue too, and i fixed it by embedded my scrollView (or tableView \ collectionView) inside stackView, and it's important that this stackView's top constraint will not be attached to the safeArea view (all the other constraints can). the top constraint should be connect to it's superview or to other view.
I was facing the same issue for very long, the only working solution for me was adding refresh control to the background view of tableview.
tableView.backgroundView = refreshControl
Short Answer
I fixed this by delaying calling to API until my collection view ends decelerating
Long Answer
I notice that the issue happens when refresh control ends refreshing while the collection view is still moving up to its original position. Therefore, I delay making API call until my collection view stops moving a.k.a ends decelerating. Here's a step by step:
Follow Bruno's suggestion
If you set your navigation bar's translucent value to false (navigationBar.isTranslucent = false), then you will have to set extendedLayoutIncludesOpaqueBars = true on your view controller. Otherwise, skip this.
Delay api call. Since I'm using RxSwift, here's how I do it.
collectionView.rx.didEndDecelerating
.map { [unowned self] _ in self.refreshControl.isRefreshing }
.filter { $0 == true }
.subscribe(onNext: { _ in
// make api call
})
.disposed(by: disposeBag)
After API completes, call to
refreshControl.endRefreshing()
Caveat
Do note that since we delay API call, it means that this whole pull-to-refresh process is not as quick as it could have been done without the delay.
Unfortunately, no advice helped. But I found a solution that helped me. Setting the transparency of the navigation bar helped.enter image description here
Problem can be solved if add tableview or scroll view as root view in UIViewController hierarchy (like in UITableViewController)
override func loadView() {
view = customView
}
where customView is UITableView or UICollectionView

navigationController?.navigationBar.isUserInteractionEnabled not working as expected

For the following viewController hierarchy, isUserInteractionEnabled doesn't appear to be working as expected.
NavigationController(ViewController A) --- pushes to ---> NavigationController(ViewController B)
In ViewController A's viewDidAppear method I set navigationController?.navigationBar.isUserInteractionEnabled to false and set it to true in ViewController B's viewDidAppear method. However, upon popping ViewController B and returning to ViewController A, the navigation bar remains enabled for user interaction. Any thoughts as why this may be happening are greatly appreciated, thanks in advance!
That seems to be a bug for which you could get around by doing that on the main thread:
override func viewDidAppear(_ animated: Bool) {
//...
DispatchQueue.main.async {
self.navigationController?.navigationBar.isUserInteractionEnabled = false
}
}
But this still leaves a millisecond window where the navigationBar's interaction is enabled.
You have to be really quick.
However...
I wouldn't recommend what you're doing; i.e. disabling the navigationBar.
You could lose the back ability, if it had one, because you're just disabling the navigationBar entirely.
Suggestion:
Since every viewController in the navigation stack has it's own navigationItem, that contains it's own set of barButtonItems, I would recommend you keep references of the UIBarButtonItem and enable/disable them explicitly.
i.e.
#IBOutlet var myBarButtonItem: UIBarButtonItem!
override func viewDidAppear(_ animated: Bool) {
//...
myBarButtonItem.isEnabled = false
}
Furthermore, the state of this barButtonItem is handled in this viewController itself and you need not do things like self.navigationController?.navigationBar.isUserInteractionEnabled = true elsewhere.

Refresh control target action not triggered

My viewcontroller has a tableview, in which i populate my views in different sections. I am trying to add a refresh control to this tableview. What I have implemented is as below.
func setUpRefreshControl() {
let refreshControl = UIRefreshControl()
refreshControl.tintColor = UIColor.red
refreshControl.addTarget(self, action: #selector(handleRefresh(_: )), for: UIControlEvents.valueChanged)
if #available(iOS 10.0, *) {
tableView.refreshControl = refreshControl
} else {
tableView.addSubview(refreshControl)
}
}
#objc func handleRefresh(_ sender: UIRefreshControl) {
print("-----------REFRESHED------------")
}
Next, when I pull down the tableview to refresh, the refresh control (red in color as setup)is visible. However, the target function is not called.
My viewcontroller is inside a tab bar controller, which is embedded in a navigation controller. I guess the issue is related to view hierarchy because when I try the same code in a separate project with no nav bar and tab bar, its working fine. But I cannot figure out what the current issue is. Any suggestion on solving this? Thank you.
EDIT: I tested it in different simulators: 6, 6s, 6s+, 7, 7+, 8, 8+, X. I found that the above code runs fine in all the plus versions including X. However, all the simulators are running ios 11.2 so I still cannot figure it out what might be causing this issue.
I was having a similar issue, pull to refresh would work fine on 5.5" screens but all other phones would show only a partial animation then never quite trigger the refresh.
I found invoking refreshControl.didMoveToSuperview() in the viewDidAppear for my view controller fixed the problem.
If I understand correctly, this same method is invoked when you set tableView.refreshControl = refreshControl but needs recalculation by the time the view is actually set to appear. I'll need to dig in a bit more to be sure of the specifics, but hope this is helpful for the time being. 🙂
I solved this issue changing the property Size in Simulated Metrics to Inferred and the property Simulated Size to Fixed in the view controller.
I hope this works for you.
This is the refresh control code which i implemented and its working for me in my current project, just 3 days ago.
var refreshControl: UIRefreshControl!
this is my global refreshControl variable and below is the implementation of it.
//MARK: - REFRESH CONTROLLER VIEW
refreshControl = UIRefreshControl()
refreshControl.attributedTitle = NSAttributedString(string: "Pull to refresh")
refreshControl.addTarget(self, action: #selector(refresh(_ :)), for: UIControlEvents.valueChanged)
refreshControl.attributedTitle = NSAttributedString(string: "")
tblRequest.addSubview(refreshControl)
Hope, this works for you ! :-)

ScrollView UIRefreshControl is not calling function

I added a refreshControl to a scrollView in iOS 10 an swift 3, but the added function is not called.
Here is my code.
let rc = UIRefreshControl()
rc.addTarget(self, action: #selector(refresh), for: UIControlEvents.valueChanged)
if #available(iOS 10.0, *) {
scrollView.refreshControl = rc
}
...
#objc func refresh(_ sender: AnyObject) {
let response = serverHelper.send(word: JSONParser.getAllGamesForPlayer(player: localPlayer))
let games = JSONParser.parseToArrayDic(string: response)
print("All games \(games)")
addAllGameButtons(games: games)
sender.endRefreshing()
}
I want that the refreshContol is calling the refresh function.
But the function is never called.
What I'm doing wrong here?
EDIT
The refreshControl is working, but the problem is, that I have to scroll down to the end of the screen so that the function gets called.
My scrollView has a high of 1200 px.
Anyone who knows how to solve this?
I had this bug when my view controller had size: freeform of 1500px on the storyboard.
When I changed it to fixed size & inferred, the refresh started to work as expected.
Looks like a bug on Apple's side that the refresh control, value change distance is set when the view is loaded from Xib/Storyboard but not updated when it is laid out for the device.

Edit button does not have any functionality, no red circle with a minus appears on the left side of cells

I have looked at another question that was asking the exact same question as mine, but the answer told them to call the setEditing function (which I tried, and say later in the question). But I don't see how you could call this function only when the edit button is clicked. I suppose I could create my own BarButtonItem and run this method when my bar button item is clicked, but I figured this would be far easier since I need the basic functionality of the edit button.
I have a UIViewController that has a table on it named peersTable. When I click the edit button it switches to done, but nothing happens on the table. I have also added my own UITableViewRowActions and when I swipe to the left on the cells, my custom actions do show up.
Here is some of my code:
class PeerViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, ConnectionManagerDelegate {
#IBOutlet weak var peersTable: UITableView!
...
override func viewDidLoad() {
...
peersTable.delegate = self
peersTable.dataSource = self
self.navigationItem.rightBarButtonItem = self.editButtonItem
...
}
I have also tried calling the peersTable.setEditing(true, animated: true) method myself, and in this case, the red minus does appear. I've never seen this issue before, so I don't understand why it's happening. Am I somehow setting the delegate wrong? Or possibly I'm doing something wrong since this is a regular View and not a TableView (even though I've done this before in a previous project).
Thanks in advance! If you need any more information let me know!
Since this is a not a TableViewController it won't automatically set your tableView to editing mode when you press the edit button.
You need to override setEditing method so you can set the tableView to editing mode.
Add this to your ViewController class:
override func setEditing(_ editing: Bool, animated: Bool) {
super.setEditing(editing, animated: animated)
peersTable.setEditing(editing, animated: animated)
}