Swift 4 Refresh Control not smooth but always jump - ios

I have a UIViewController, inside which there is a tableView. I added a RefreshControl. But when I pull, it always jumps for some times, which is not smooth and continuous at all.
I'm using Swift 4 and Xcode 10.1.
class ItemsController: UIViewController, UITableViewDelegate, UITableViewDataSource, UITextViewDelegate, ItemCellDelegate {
......
lazy var refreshControl: UIRefreshControl = {
let refreshControl = UIRefreshControl()
refreshControl.addTarget(self, action:
#selector(handleRefresh(_:)),
for: UIControl.Event.valueChanged)
refreshControl.tintColor = UIColor.lightGray
return refreshControl
}()
......
override func viewDidLoad() {
// Refresh
self.tableView.refreshControl = self.refreshControl
}
......
#objc func handleRefresh(_ refreshControl: UIRefreshControl) {
loadData()
self.tableView.reloadData()
refreshControl.endRefreshing()
}
......
}

I also faced that kind of problem. You can use this approach below:
Change your handleRefresh method like this:
#objc func handleRefresh(_ refreshControl: UIRefreshControl) {
if !tableView.isDragging {
refresh() // refresh is another method for your reloading jobs
}
}
Add refresh method.
func refresh() {
self.refreshControl.endRefreshing()
self.tableView.reloadData()
}
Lastly you need to implement scrollViewDidEndDragging. If you've pulled down far enough, the refreshControl will be refreshing, so call refresh. Otherwise, you either haven't pulled down far enough, you were dragging on a different part of the table, or you dragged so quickly that the refreshControl ValueChanged event has yet to fire, and it will refresh the table there.
func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
if refreshControl.isRefreshing == true {
refresh()
}
}

Is loadData() asynchronous? because if so it should have a completion handler and
tableView.reloadData()
refreshControl.endRefreshing()
should be done (on the main thread) from there. Otherwise what is happening is the user pulls down and you instantly update the tableView and stop the refresh control, so thats why you have no animation.

try
#objc func handleRefresh(_ refreshControl: UIRefreshControl) {
loadData()
self.tableView.reloadData()
DispatchQueue.main.async {
//DispatchQueue.main.asyncAfter(deadline: .now + 0.2) { // or this one with
//short delay
refreshControl.endRefreshing()
}
}

I just solved this problem by doing this:
#objc func handleRefresh(_ refreshControl: UIRefreshControl) {
refreshControl.endRefreshing()
}
func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
loadData()
}
Pretty simple. Now the animation is much better.
The tableView.reloadData(), which is inside of loadData(), is getting the animation less smooth. So I make it do after the user releases from dragging.

Related

Call extension function IOS Swift

How can I call extension function, so that the button is encouraged to call the view.
I made a UiButton Extension with a function to animate a button, and everything works, but only if I call it from:
#IBAction func botonVuelta(_ sender: UIButton) {
sender.pulsarAnimacion()
}
but if you called it from viewDidLoad it doesn’t work:
override func viewDidLoad() {
super.viewDidLoad()
botones.layer.cornerRadius = 20
botones.pulsarAnimacion()
}
I’d appreciate it if you could give me a solution,
I thank you in advance
viewDidLoad is a very early place to animate a view , try inside
override func viewDidAppear(_ animated:bool) {
super.viewDidAppear(animated)
botones.pulsarAnimacion()
}

RefreshControl doesn’t work in one of tabs with code used in similar way in other tabs

The app tab uses refresh button to update list which works fine. Trying to use "pull down to refresh" but it doesn’t work at all.
protocol StaffHistoryDelegate: class {
func updateList()
}
class StaffHistoryController: UIViewController {
#IBOutlet weak var tableView: UITableView!
#IBOutlet weak var activityLoad: UIActivityIndicatorView!
let myRefreshControl: UIRefreshControl = {
let refreshControl = UIRefreshControl()
refreshControl.attributedTitle = NSAttributedString(string: " ")
refreshControl.addTarget(self, action: #selector(refresh(sender:)), for: .valueChanged)
return refreshControl
}()
override func viewDidLoad() {
super.viewDidLoad()
prepareSearch()
delegateStaffHystory = self
do { try self.frc.performFetch() }
catch {}
tableView.refreshControl = myRefreshControl
}
#IBAction func btnRefreshPressed(_ sender: UIBarButtonItem) {
loadingShow(text: " ")
delegateTabController?.loadStaffBill(completion: { (error) in
DispatchQueue.main.async {
loadingHide()
if error != nil {
let alert = getAlert(title: error?.localizedDescription, message: nil)
self.present(alert, animated: true, completion: nil)
}
}
})
}
#objc private func refresh(sender: UIRefreshControl) {
updateList()
tableView.reloadData()
sender.endRefreshing()
}
Unfortunately I'm not a coder and it's really hard for me to understand what to do. (I managed to implement "pull down to refresh" on 2 other tabs but this one is coded in the different way).
I am assuming 'updateList()' is the function where your data is being refreshed -
#objc private func refresh(sender: UIRefreshControl) {
updateList()
tableView.reloadData()
sender.endRefreshing()
}
Check if tableView is reloading data before the function finishes computing.
Try adding the following line when data is being refreshed in the 'updateList' function.
tableView.reloadData()
If 'btnRefreshPressed' works, try this
#objc private func refresh(sender: UIRefreshControl) {
self.btnRefreshPressed(UIBarButtonItem())
sender.endRefreshing()
}

UIRefreshControl in UIScrollView Doesn't disappear on endRefreshing

I tried to use a UIRefreshControll in a UIScrollView but I had the problem that I needed to scroll a lot in order to get it to refresh. I needed to use both hands to make it work so I decided to write some scrolling logic to start the refreshing.
Now the problem is that sometimes the endRefreshing function doesn't make the UIRefreshControll view disappear and I end up having it there forever.
I have tried called endRefreshing in the main queue with a delay and it doesn't work. I have also tried setting isHidden to true and also removeFromSuperView. I've had no luck in making it work.
import UIKit
class RefreshableView : UIView {
#IBOutlet weak var scrollView: UIScrollView!
private let refreshControl = UIRefreshControl()
private var canRefresh = true
var refreshFunction: (() -> ())?
func setupUI() {
backgroundColor = ColorName.grayColor.color
scrollView.refreshControl = refreshControl
}
func endRefreshing() {
self.refreshControl.endRefreshing()
}
}
extension RefreshableView: UIScrollViewDelegate {
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if scrollView.contentOffset.y < -100 {
if canRefresh && !self.refreshControl.isRefreshing {
self.canRefresh = false
self.refreshControl.beginRefreshing()
self.refreshFunction?()
}
} else if scrollView.contentOffset.y >= 0 {
self.canRefresh = true
}
}
}
The scroll view's refreshControl handles the pull-to-refresh feature automatically, so you don't need any of the scrollViewDidScroll() code.
Assuming your storyboard looks something like this, where:
the view with the Red background is an instance of RefreshableView class
it contains a scroll view (connected via #IBOutlet)
and some content in the scroll view (here I have just a single label)
Your code can be like this:
class RefreshableView : UIView {
#IBOutlet var scrollView: UIScrollView!
private let refreshControl = UIRefreshControl()
var refreshFunction: (() -> ())?
func setupUI() {
backgroundColor = .gray
refreshControl.addTarget(self, action: #selector(didPullToRefresh), for: .valueChanged)
scrollView.refreshControl = refreshControl
}
#objc func didPullToRefresh() {
print("calling refreshFunction in controller")
self.refreshFunction?()
}
func endRefreshing() {
print("end refreshing called from controller")
self.refreshControl.endRefreshing()
}
}
class RefreshTestViewController: UIViewController {
#IBOutlet var theRefreshableView: RefreshableView!
override func viewDidLoad() {
super.viewDidLoad()
theRefreshableView.setupUI()
theRefreshableView.refreshFunction = {
print("simulating refresh for 2 seconds...")
DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) {
[weak self] in
guard let self = self else { return }
// do what you want to update the contents of theRefreshableView
// ...
// then tell theRefreshableView to endRefreshing
self.theRefreshableView.endRefreshing()
}
}
}
}

Why viewDidLayoutSubviews is called twice only on first run?

It's driving me crazy this. Only on first run the viewDidLayoutSubviews is called twice.
Here is the code I'm using:
class CICViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
func addQLabel(qLabel: UILabel, questionString: String, bgrLabel: UILabel) {// some code goes here
}
func makeRoundQButtons(qButtons:[UIButton]) {
// some code goes here
}
func addArrows(numberOfArrows:Int, buttonCurAngle:Double) {//some code goes here
}
func animateButtons(qButtons:[UIButton], buttonCurAngle:Double) {
// some code goes here
}
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
}
func backTapped(sender: UIBarButtonItem) {
navigationController?.popViewControllerAnimated(false)
//some code goes here
}
func restartTapped(sender: UIBarButtonItem) {
navigationController?.popToRootViewControllerAnimated(false)
//some code goes here
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
}
And in my ViewController I call this :
class OneViewController: CICViewController {
override func viewDidLoad() {
super.viewDidLoad()
//some code goes here
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
print("viewDidLayoutSubviews")
self.makeRoundQButtons(qButtons)
self.animateButtons(qButtons, buttonCurAngle: 2.0)
}
override func viewDidAppear(animated: Bool) {
//nothing goes here
}
}
There is no guarantee as for how many times viewDidLayoutSubviews will be called.
You can find a great discussion in this Stack Overflow post:
When is layoutSubviews called?
I found this article useful. A summary from what it says:
init does not cause layoutSubviews to be called (duh)
addSubview causes layoutSubviews to be called on the view being added, the view it’s being added to (target view), and all the subviews of the target view
setFrame intelligently calls layoutSubviews on the view having it’s frame set only if the size parameter of the frame is different
scrolling a UIScrollView causes layoutSubviews to be called on the scrollView, and it’s superview
rotating a device only calls layoutSubview on the parent view (the responding viewControllers primary view)
removeFromSuperview – layoutSubviews is called on superview only (not show in table)

When screen rotation ReloadData()

I can not update my custom view when you turn the screen of my device.
I tried to do this:
override func viewDidLoad() {
super.viewDidLoad()
var myCustomView = Customview()
self.view.addsubview(myCustomView)
}
}
override func didRotateFromInterfaceOrientation(fromInterfaceOrientation: UIInterfaceOrientation) {
// Reload Data here
self.view.reloadData() // Incorrect
}
But get me error:
the method "UIView" does not have a method called reloadData
override func didRotateFromInterfaceOrientation(fromInterfaceOrientation: UIInterfaceOrientation) {
// Reload Data here
self.view.reloadData() // Incorrect
}
UIView is not a UITableView it doesn't have reloadData method out of the box.
You can implement this method manually -> have fun :)
You want to call reloadData() on the instance of your UITableView (assuming there is any). UIView does not provide such a method.
Edit to get notified when the screen orientation has changed, set up a notification:
NSNotificationCenter.defaultCenter().addObserver(self, selector: "didRotate:", name: UIDeviceOrientationDidChangeNotification, object: nil)
and implement the selector, e.g.
func didRotate(notification: NSNotification)
{
myTableView.reloadData()
}
I think it's work for you
objective-C
[self.view setNeedsDisplay];
swift
self.view.setNeedsDisplay()
override func viewDidLoad() {
super.viewDidLoad()
var myCustomView = Customview()
self.view.addsubview(myCustomView)
}
override func didRotateFromInterfaceOrientation(fromInterfaceOrientation: UIInterfaceOrientation) {
// Reload Data here
self.view.setNeedsDisplay()
}

Resources