How we can configure pull to refresh without bounce enabled in scroll view.
its simple when we keep bounce enable we just need to assign refresh control to scroll view but I don't want to enable bounce
Any suggestions would be appreciated. thanks in advance
Have tried scroll view did scroll method but it won't call as there might be case when scroll view does not have enough data to scroll the page
One approach is to create your own "refresh view" and:
Here's a quick example:
class RefreshVC: UIViewController {
let scrollView: UIScrollView = UIScrollView()
let contentView: UIView = UIView()
let contentLabel: UILabel = UILabel()
let myRefreshView: UIView = UIView()
let activityView: UIActivityIndicatorView = UIActivityIndicatorView()
var cBottom: NSLayoutConstraint!
var myData: [String] = []
override func viewDidLoad() {
super.viewDidLoad()
[contentLabel, contentView, scrollView, myRefreshView, activityView].forEach { v in
v.translatesAutoresizingMaskIntoConstraints = false
}
myRefreshView.addSubview(activityView)
contentView.addSubview(contentLabel)
scrollView.addSubview(contentView)
scrollView.addSubview(myRefreshView)
view.addSubview(scrollView)
let g = view.safeAreaLayoutGuide
let cg = scrollView.contentLayoutGuide
let fg = scrollView.frameLayoutGuide
NSLayoutConstraint.activate([
scrollView.topAnchor.constraint(equalTo: g.topAnchor, constant: 80.0),
scrollView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 40.0),
scrollView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -40.0),
scrollView.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: -80.0),
contentView.topAnchor.constraint(equalTo: cg.topAnchor, constant: 8.0),
contentView.leadingAnchor.constraint(equalTo: cg.leadingAnchor, constant: 8.0),
contentView.trailingAnchor.constraint(equalTo: cg.trailingAnchor, constant: -8.0),
contentView.bottomAnchor.constraint(equalTo: cg.bottomAnchor, constant: -8.0),
contentView.widthAnchor.constraint(equalTo: fg.widthAnchor, constant: -16.0),
contentLabel.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 8.0),
contentLabel.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 8.0),
contentLabel.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -8.0),
contentLabel.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -8.0),
activityView.centerXAnchor.constraint(equalTo: myRefreshView.centerXAnchor),
activityView.centerYAnchor.constraint(equalTo: myRefreshView.centerYAnchor),
myRefreshView.widthAnchor.constraint(equalToConstant: 200.0),
myRefreshView.heightAnchor.constraint(equalToConstant: 100.0),
myRefreshView.centerXAnchor.constraint(equalTo: scrollView.centerXAnchor),
])
cBottom = myRefreshView.bottomAnchor.constraint(equalTo: fg.topAnchor)
cBottom.isActive = true
myRefreshView.backgroundColor = .white.withAlphaComponent(0.90)
myRefreshView.layer.cornerRadius = 12
myRefreshView.layer.borderColor = UIColor.black.cgColor
myRefreshView.layer.borderWidth = 1
//activityView.style = .large
activityView.color = .red
activityView.startAnimating()
scrollView.backgroundColor = .systemBlue
contentView.backgroundColor = .systemYellow
contentLabel.backgroundColor = .cyan
contentLabel.numberOfLines = 0
contentLabel.textAlignment = .center
contentLabel.font = .systemFont(ofSize: 40.0, weight: .bold)
// let's start with 5 lines of text as our content
myData = (1...5).compactMap({ "Line \($0)" })
contentLabel.setContentCompressionResistancePriority(.required, for: .vertical)
contentLabel.text = myData.joined(separator: "\n")
scrollView.bounces = false
let pg = UIPanGestureRecognizer(target: self, action: #selector(handlePan(_:)))
scrollView.addGestureRecognizer(pg)
pg.delegate = self
}
var startPT: CGPoint = .zero
var isRefreshing: Bool = false
@objc func handlePan(_ pan: UIPanGestureRecognizer) {
guard let sv = pan.view as? UIScrollView,
isRefreshing == false
else { return }
let curPT = pan.location(in: view)
switch pan.state {
case .began:
// we only want to "pull down" the refresh view if
// we start dragging when the scroll view is all the
// way at the top
if sv.contentOffset.y == 0 {
startPT = curPT
} else {
startPT.y = .greatestFiniteMagnitude
}
case .changed:
let diff = curPT.y - startPT.y
// if we are dragging down
if diff > 0 {
// if the scroll view content is at the top
if sv.contentOffset.y == 0 {
scrollView.isScrollEnabled = false
// move the refresh view down
cBottom.constant = min(diff, myRefreshView.frame.height + 4.0)
// if the refresh view is fully down
if cBottom.constant == myRefreshView.frame.height + 4.0 {
isRefreshing = true
refreshContent()
}
}
}
default:
// if the refresh view has not been pulled all the way
// when drag ended / was cancelled
// animate it back up
print("done", cBottom.constant)
self.scrollView.isScrollEnabled = true
if cBottom.constant > 0.0, cBottom.constant < myRefreshView.frame.height + 4.0 {
cBottom.constant = 0
UIView.animate(withDuration: 0.3, animations: {
self.view.layoutIfNeeded()
})
}
}
}
@objc func refreshContent() {
// let's simulate a 1-second refresh task
// and add a line to the scroll view content
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0, execute: {
self.myData.append("Line \(self.myData.count + 1)")
self.contentLabel.text = self.myData.joined(separator: "\n")
// animate the refresh view back up
DispatchQueue.main.async {
self.cBottom.constant = 0
UIView.animate(withDuration: 0.3, animations: {
self.view.layoutIfNeeded()
}, completion: {_ in
self.isRefreshing = false
self.scrollView.isScrollEnabled = true
})
}
})
}
}
extension RefreshVC: UIGestureRecognizerDelegate {
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return true
}
}
and it looks like this when running:
Once you've added enough lines, the scroll view will scroll... and the "refresh view" will only get pulled-down if the scroll view is scrolled all the way to the top.
Note: this is EXAMPLE CODE ONLY!!!
It is just to give you a start. You would likely want to tweak the distances, interactive capabilities, etc.