'White line below UIRefreshControl when pulled.. tableView

I am implementing a very basic Refresh control...

    var refreshControl = UIRefreshControl()
    refreshControl.addTarget(self, action: Selector(("refresh:")), for: UIControlEvents.valueChanged)
    refreshControl.backgroundColor = UIColor.red
    self.tableView.addSubview(refreshControl)

for some reason though whenever I pull down to refresh it's like the refresh control cannot keep up with the table view and there is a white gap between the two.

here is the problem on the simulator... it is worse on the iPhone I believe

http://gph.is/2ijyH26



Solution 1:[1]

While I also suggest you should do what Artem posted, it didn't directly remove that gap for me. Turns out I was running into 2 different issues related to UIRefreshControl and found a few 'hacks' to get around them. The easy fix is to set the tableView's background colour to the same colour as your refresh control, but that has the side effect of seeing the same colour at the bottom of your table view.

My set up is a UINavigationController hosting a UIViewController with a UITableView subview and a UISearchBar set as the tableView's tableHeaderView. My goal is to match the colour of the navigation bar, refresh control, and the search bar.

Issue 1

I think this the same issue that you're seeing. It seems like as soon as you drag to start the pull to refresh action, there's a gap that appears during the first few pts of the gesture. After some threshold, the refresh control becomes the correct colour. On the way back to the table view's resting scroll state though, we see that same gap again just before it reaches a content offset of 0. Here's what that looks like:

UIRefreshControl flashing gap

Solution 1

If you subclass UIRefreshControl and override the frame and isHidden properties and just print out their values when they are set, you'll notice that the refresh control doesn't actually get unhidden until the distance from the top of the table view is 4pt. Similarly, when you scroll back down you'll also see that it is set to hidden around the same spot, which is before you can't visibly see the refresh control anymore and why we see the gap peeking to the tableView background.

In our subclass, we can prevent this 'early' hiding and 'late' unhiding by overriding the setter and getter of isHidden and the didSet of frame to only hide when the refresh control's offset is actually 0.

class RefreshControl: UIRefreshControl {

    override var isHidden: Bool {
        get {
            return super.isHidden
        }
        set(hiding) {
            if hiding {
                guard frame.origin.y >= 0 else { return }
                super.isHidden = hiding
            } else {
                guard frame.origin.y < 0 else { return }
                super.isHidden = hiding
            }
        }
    }

    override var frame: CGRect {
        didSet {
            if frame.origin.y < 0 {
                isHidden = false
            } else {
                isHidden = true
            }
        }
    }
}

I call this a hack because I'm not a huge fan of modifying the existing behaviour of UIRefreshControl, but I haven't found a better way to get around this yet.

Issue 2

When pulling to refresh past that gap threshold in issue 1, it seems like the frame of the UIRefreshControl can't keep up with the search bar. This gives us another kind of gap that follows the search bar around. This is what it looks like:

UIRefreshControl lagging gap

Solution 2

This lagging gap looks like the frames aren't being updated as fast as our scrolling maybe even animated. Turns out that if we set the frame to itself in somewhere like layoutSubviews, we get the following behaviour:

override func layoutSubviews() {
    super.layoutSubviews()
    var originalFrame = frame
    frame = originalFrame
}

Again, this is pretty hacky, but I haven't found a different way to go about this either.

Result

UIRefreshControl subclassed with overrides

Solution 2:[2]

Why do you add refreshControl as a subview? You must do that:

refreshControl.addTarget(self, action: #selector(refresh), for: .valueChanged)
tableView.refreshControl = refreshControl

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2