Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

iOS UITableView Offset Refresh Control

I have a UITableView that is constrained to the superview. So the first cell is at the top of the superview. Which means on the iPhone X that first cell bleeds under the notch, and into the status bar area. Which is exactly right. I want the cell to bleed under the notch and into the status bar area, which is the current result.

Problem is, I also want a refresh control on that table view, so that the user can pull to refresh the content. BUT when you pull to refresh on the iPhone X, the refresh control is cut off by the notch. I don't want the refresh control to be cut off by the notch.

So basically what I want to do is have the table view constrained to the superview, but have the refresh control constrained to the safe area.

I have looked through and it doesn't look like Apple provides many properties or methods to customize the behavior of the the refresh control.

I've considered using some type of custom solution, but every custom solution I have thought of loses all of the prebuilt physics of the refresh control and table view scrolling and such. There is so much behind the scenes logic and I really don't want to sacrifice the feeling of the pull to refresh and how that feels to the user since it's become such a common action in iOS.

Any ideas on how to have the first cell bleed under the notch (constrained to superview) but ensure the refresh control doesn’t get cut off by the notch (constrained to safe area)?

I have also attached screenshots below of how the cells bleed into the status bar, and how the refresh control is cut off by the notch.

EDIT: I have also published a sample project to GitHub here.

But the basic code related to the refresh control is below.

private lazy var refreshControl = UIRefreshControl()
override func viewDidLoad() {
    super.viewDidLoad()

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

@objc func handleRefresh() {
    DispatchQueue.main.asyncAfter(deadline: .now() + 3.0) {
        self.refreshControl.endRefreshing()
    }
}

enter image description here

enter image description here

like image 741
Charlie Fish Avatar asked Aug 31 '18 22:08

Charlie Fish


2 Answers

You can take benefits for underlaying UIScrollView. With your github example you got everything you need there, so if you extend it with:

extension ViewController: UIScrollViewDelegate {
    func scrollViewDidScroll(_ scrollView: UIScrollView) {
        if scrollView.contentOffset.y < 0 {
            topConstraint.constant = -scrollView.contentOffset.y/2
        } else {
            topConstraint.constant = 0
        }
    }
}

What react every time contentOffset starts to be lower than 0 and make your tableView moved to bottom and whenever is closed or scrolling down it will be set to default value - in your case 0.

That way you will endup with smooth animation related to dragging force/distance as below

enter image description here

like image 186
Jakub Avatar answered Sep 19 '22 22:09

Jakub


The solution is easy, you can set bounds of your refresh control in viewDidLoad. Use y offset value you need (50 for example)

  refreshControl.addTarget(self, action: #selector(handleRefresh), for: .valueChanged)
  refreshControl.bounds = CGRect(x: refreshControl.bounds.origin.x,
                                 y: 50,
                                 width: refreshControl.bounds.size.width,
                                 height: refreshControl.bounds.size.height)
  tableView.refreshControl = refreshControl

And now it looks like this

UPDATE:

You can add content inset at refresh beginning, and bring it back at the end.

  override func viewDidLoad() {
        super.viewDidLoad()


      refreshControl.addTarget(self, action: #selector(handleRefresh), for: .valueChanged)
      refreshControl.bounds = CGRect(x: refreshControl.bounds.origin.x,
                                     y: -100,
                                     width: refreshControl.bounds.size.width,
                                     height: refreshControl.bounds.size.height);
      tableView.refreshControl = refreshControl
    }

    @objc func handleRefresh() {
      self.tableView.contentInset = UIEdgeInsets(top: 200, left: 0, bottom: 0, right: 0)
        DispatchQueue.main.asyncAfter(deadline: .now() + 3.0) {
          self.refreshControl.endRefreshing()
          self.tableView.contentInset = UIEdgeInsets.zero
          self.tableView.setContentOffset(.zero, animated: true)
        }
    }
like image 42
Anton Rodzik Avatar answered Sep 20 '22 22:09

Anton Rodzik