Refresh control target action not triggered - swift

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 ! :-)

Related

Update to Xcode 11.3.1 - navigationBar and half of the Views disappear after storyboard refactoring

Using Xcode 11.3.1, Simulator11.3.1, iPhoneX, Swift5.1.3, iOS13.3,
I am wondering why half of my app suddenly disappears !!
Could it be the update to Xcode 11.3.1 ???
The following shows a screenshot of the Xcode Debug View Hierarchy.
The left side is what the iPhone 11 Pro Simulator shows and the right side is the Debug View Hierarchy:
Clearly there are many more objects in the view hierarchy (such as the round buttons at the bottom) that are not shown on the Simulator (and also not on a physical iPhoneX). Also the NavigationBar is missing completely !!!!
The blue highlighted object is a custom navigationBar (consisting of a stackView). This worked before but not since the Xcode update. I am really not believing this. What could go wrong here ??
If it is not the Xcode-update, then my refactoring of the storyboard could also be a cause of this view-losses.
Before my refactoring, the VC at question was a ChildViewController of another ViewController. Now, it is the entry point of the App. Could this change bring the view-losses ? I want to see a NavigationController with largeTitle. But there is no NavigationController whatsoever now!
Here is the code that sets up the navigationBar:
override func viewDidLoad() {
// set up navigationItem and navigationController look and feeel
navigationItem.largeTitleDisplayMode = .always
navigationItem.backBarButtonItem = UIBarButtonItem(title: "", style: .plain, target: nil, action: nil)
navigationController?.set_iOS12_lookAndFeel()
navigationItem.title = "bluub"
}
And the needed NavigationController extension:
import UIKit
extension UINavigationController {
func set_iOS12_lookAndFeel() {
if #available(iOS 13.0, *) {
self.keep_iOS12_lookAndFeel()
} else {
let attrLargeTitle = AppConstants.FontAttributes.NavBar_LargeTitleTextAttributes
self.navigationBar.largeTitleTextAttributes = attrLargeTitle
let attrTitle = AppConstants.FontAttributes.NavBar_TitleTextAttributes
self.navigationBar.titleTextAttributes = attrTitle
}
}
private func keep_iOS12_lookAndFeel() {
if #available(iOS 13.0, *) {
let navBarAppearance = UINavigationBarAppearance()
navBarAppearance.configureWithDefaultBackground()
navBarAppearance.backgroundEffect = .init(style: .systemThickMaterialDark)
navBarAppearance.titleTextAttributes = AppConstants.FontAttributes.NavBar_TitleTextAttributes
navBarAppearance.largeTitleTextAttributes = AppConstants.FontAttributes.NavBar_LargeTitleTextAttributes
navBarAppearance.buttonAppearance.normal.titleTextAttributes = AppConstants.FontAttributes.NavBar_ButtonAppearance_Normal
navBarAppearance.doneButtonAppearance.normal.titleTextAttributes = AppConstants.FontAttributes.NavBar_Done_ButtonAppearance_Normal
self.navigationBar.standardAppearance = navBarAppearance
self.navigationBar.scrollEdgeAppearance = navBarAppearance
}
}
}
.
---------------- more findings -----------------------------
After another storyboard refactoring, I could bring back the round menu buttons. However, the largeTitle-NavigationBar is still completely missing.
Frankly, the latest refactoring did not introduce any new constraints or other storyboard settings as before. The fact that I kicked out the NavigationController and replaced it by an identical new one, plus, re-assigned one or the other constraint of the menu-button-View, did bring the bottom menu back alive. As far as I can tell, no difference to the previous storyboard was introduced.
It is very annoying why a storyboard needs to be redrawn basically to render correctly. Something seems corrupt here as for the Xcode functionality with storyboard !
But lets leave this talk.
My remaining question:
How can I bring back a missing NavigationBar ?????????
.
---------------- another finding -----------------------------
If I reassign the "first-entry-ViewController" to the old ViewController that eventually adds the Menu-button-ViewController as a ChildViewController --> then everything works!
If I assign the "first-entry-ViewController" to be the Menu-button-ViewController directly, then the NavigationBar disappears !
Here is the overview:
I finally found a solution.
It indeed had to do with my login-architecture of this app.
The fact that only by setting the "first-entry-ViewController" as the old-Main-ViewController made a difference:
This old-Main-ViewController (that eventually adds the Menu-button-ViewController as its Child) did have the following line in its viewWillAppear method:
navigationController?.setNavigationBarHidden(true, animated: animated)
Its intention was actually to never show the navigationBar of its own. But instead load a ChildViewController that itself shows a navigationBar of its own.
The strange thing with storyboard: Even tough setting the Menu-button-ViewController as first-entry does somehow still consider the navigationController-hiding mechanism of the previous first-entry setting. This seems a bug to me inside storyboard. I would assume that visible navigationBar is the default behaviour. But having set it once to be hidden keeps it hidden, even tough the hiding-command is no longer executed. Anyway, very strange behaviour.
By eliminiting that line - or better - by adding it "with hidden = false" inside the Menu-Button-ViewController, makes the NavigationBar being shown again !!!
My learning is to keep an eye on all navigationController actions or mutations throughout the entire App hierarchy. The fact that a single ViewController might mutate something on its navigationController might not be enough. You have to check event parent-ViewControllers or segue-parents as well. And most annoying, applying a different first-entry to a VC does require you to overwrite default behaviours of your views to make sure your views are shown !

UIRefreshControl weird jump when scrolling down with preferLargeTitles enabled

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
}

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

Programmatic beginRefreshing() on iOS11 has problems with largeTitles mode

We have found what seems to be a bug in UIKit but wanted to post here to see if anyone else has this problem or found a solution.
We're trying to use the new iOS11 large titles and hoisted search bar/refreshcontrol. We seemed to have found a problem where the root viewController of the navigation stack shows a minor display issue (problem A) but once another viewcontroller is pushed onto the navigation stack, the display goes nuts (problem B):
Things to note:
The problem is worse on the 2nd VC in the stack rather than the 1st
The refreshControl is not the green color the code sets it to the 1st time you see it on each sceen
The refreshControl slides down as you pull to refresh, it shouldn't do this
This odd behavior seems to only be a problem when we programmatically do a "pull to refresh" in viewDidLoad so that the user can see that the data is loading when they enter the screen. If we remove the lines that invoke refreshControl?.beginRefreshing() the display is clean. I've recreated this problem in a sample vanilla app. This is the entirety of the viewcontroller that shows the problem:
import UIKit
class ViewController: UITableViewController {
var tableHeaderSearchController: UISearchController!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
self.navigationController?.navigationBar.prefersLargeTitles = true
self.navigationController?.navigationItem.largeTitleDisplayMode = .always
tableHeaderSearchController = UISearchController(searchResultsController: UITableViewController())
navigationItem.searchController = tableHeaderSearchController
refreshControl?.tintColor = UIColor.green
refreshControl?.backgroundColor = UIColor.clear
refreshControl?.attributedTitle = NSAttributedString(string: "Loading Stuff...", attributes: [NSAttributedStringKey.font: UIFont.systemFont(ofSize: 17)])
refreshControl?.addTarget(self, action: #selector(refreshPulled), for: .valueChanged)
// Commenting out these 2 lines makes it work fine but you can't see the initial refresh spinner
refreshControl?.beginRefreshing()
refreshPulled()
}
#objc func refreshPulled() {
DispatchQueue.main.asyncAfter(deadline: .now() + 3) { [refreshControl] in
refreshControl?.endRefreshing()
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
Here's the storyboard. It's just a vanilla tableviewcontroller wrapped in a navigationController. 3 static cells, the 2nd one traverses to another instance of the same controller type.
Any ideas would be greatly appreciated. We'd really like to adopt the new look but this stuff is making it very hard to do so.
First, it is absolutely crucial that the table view extend up underneath the navigation bar and that is iOS 11 offset behavior be correct:
self.edgesForExtendedLayout = .all
self.tableView.contentInsetAdjustmentBehavior = .always
Second, scrolling to show the refresh control when you refresh manually is up to you, and calculating the amount is not at all simple:
self.refreshControl!.sizeToFit()
let top = self.tableView.adjustedContentInset.top
let y = self.refreshControl!.frame.maxY + top
self.tableView.setContentOffset(CGPoint(0, -y), animated:true)
self.refreshControl!.beginRefreshing()
The bar still stays too big during the refresh, but I don't see what can be done about that. Basically Apple has implemented large titles and shown the refresh control in the nav bar without thinking through the effects or dealing with the resulting bugs.

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.