Search code examples
iosswiftcocoa-touchuiscrollviewuirefreshcontrol

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
        }
    }
}

Solution

  • 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)

    enter image description here

    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()
                }
            }
        }
    
    }