Detect UIScrollview bottom reached - ios

I have a UIScrollView with images and when the user scrolls to the end of the scrollview i want to update the content. This is my code to detect when the bottom of the scrollview is reached:
The code below is implemented in scrollViewDidScroll: delegate
CGFloat scrollViewHeight = imagesScrollView.bounds.size.height;
CGFloat scrollContentSizeHeight = imagesScrollView.contentSize.height;
CGFloat bottomInset = imagesScrollView.contentInset.bottom;
CGFloat scrollViewBottomOffset = scrollContentSizeHeight + bottomInset - scrollViewHeight;
if(imagesScrollView.contentOffset.y > scrollViewBottomOffset){
[imagesView addSubview:imagesBottomLoadingView];
[self downloadImages];
}
My problem is that when the user scrolls to bottom, my function is called several times, but i want to call it only once. I tried with imagesScrollView.contentOffset.y == scrollViewBottomOffset but it doesn't work and the function is not called

If you want to detect them in swift:
override func scrollViewDidScroll(scrollView: UIScrollView) {
if (scrollView.contentOffset.y >= (scrollView.contentSize.height - scrollView.frame.size.height)) {
//reach bottom
}
if (scrollView.contentOffset.y < 0){
//reach top
}
if (scrollView.contentOffset.y >= 0 && scrollView.contentOffset.y < (scrollView.contentSize.height - scrollView.frame.size.height)){
//not top and not bottom
}}

Carlos answer is better.
For Swift 4.x you must change method name:
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if (scrollView.contentOffset.y + 1) >= (scrollView.contentSize.height - scrollView.frame.size.height) {
//bottom reached
}
}

- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
{
float bottomEdge = scrollView.contentOffset.y + scrollView.frame.size.height;
if (bottomEdge >= scrollView.contentSize.height)
{
// we are at the end
}
}

Sometimes you will have to use +1 in the condition because the contentSize.height gives you a few decimals over, so if you use this, you avoid it...
override func scrollViewDidScroll(scrollView: UIScrollView) {
if (scrollView.contentOffset.y + 1) >= (scrollView.contentSize.height - scrollView.frame.size.height) {
//bottom reached
}
}

Have you thought of adding a boolean. update it when the method is called for the first time and maybe when user scrolls back up.

I used a mixed approach for this. Let me explain:
While your calculus is correct, the delegate your listening to is an overkill since scrollViewDidScroll is called many times which can lead to performance issues. You should (as I did) use scrollViewDidEndDragging and scrollViewDidEndDecelerating which are called only once at the end of each of their respective events.
func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
// Scrolling acceleration didn't continue after the finger was lifted
if !decelerate {
executeActionAtTheEnd(of: scrollView)
}
}
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
executeActionAtTheEnd(of: scrollView)
}
private func executeActionAtTheEnd(of scrollView: UIScrollView) {
if scrollView.contentOffset.y + 1 >= (scrollView.contentSize.height - scrollView.frame.size.height) {
// Do here whatever you want when end of scrolling is reached
}
}

For Swift 4.5:
func scrollViewDidScroll(_ scrollView: UIScrollView)
{
let scrollViewHeight = scrollView.frame.size.height
let scrollContentSizeHeight = scrollView.contentSize.height
let scrollOffset = scrollView.contentOffset.y
if (scrollOffset == 0)
{
// then we are at the top
print("then we are at the top")
}
else if (scrollOffset + scrollViewHeight == scrollContentSizeHeight)
{
print("then we are at the end")
// then we are at the end
}
}

implement scrollViewDidScroll: and check contentOffset in that for reaching the end

func scrollViewDidEndDecelerating(scrollView: UIScrollView) {
if (scrollView.contentOffset.y >= (scrollView.contentSize.height - scrollView.frame.size.height)) {
//reach bottom
}
if (scrollView.contentOffset.y <= 0){
//reach top
}
if (scrollView.contentOffset.y >= 0 && scrollView.contentOffset.y < (scrollView.contentSize.height - scrollView.frame.size.height)){
//not top and not bottom
}}
Make sure your view implementing the UIScrollViewDelegate
MyView: UITableViewDelegate,UIScrollViewDelegate

Related

How to show / hide custom view depending the contentOffset of my tableview?

I have a chat app and I'm trying to show a custom view that I've made when the user scrolls to the top, also hide it if it's on the bottom of tableview. (like whatsapp does it)
To be honest I'm struggling with the logic of show/hide button.
Tried to save the contentOffset.y of my tableview right after I reload the data so I'll know that's the bottom, and if it's smaller to show the custom view, but mainTableView.contentOffset.y it's always 0.
func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
if (scrollView == mainTableView) {
print(mainTableView.contentOffset.y)
if let point = startingPointForView {
//where var startingPointForView: CGFloat?
// and tried to save it after I reload the data
//self.startingPointForView = self.mainTableView.contentOffset.y
// but it's always 0
}
// Show and hide button logic
}
}
An image of what I m trying to achieve: https://imgur.com/ZkYEi2P
try this code to hide/show custom view according to UIscrollview contentOffset
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let scrollViewContentHeight = scrollView.contentSize.height
let scrollViewHeight = scrollView.frame.height
if scrollView.contentOffset.y < (scrollViewContentHeight - scrollViewHeight){
//Custom view show
}else{
//Custom view Hide
}
}
May be this code will help you
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if scrollView.panGestureRecognizer.translation(in: scrollView).y > 0 {
// down
button.isHidden = false
} else {
// up
button.isHidden = true
}
}
For someone who is looking to hide a button when tableview is scrolling can use below code:
var previousContentOffset: CGFloat = CGFloat()
extension YourViewController: UIScrollViewDelegate{
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if scrollView == self.yourTableView{
let currentContentOffset = scrollView.contentOffset.y
if (currentContentOffset > previousContentOffset) {
// scrolling towards the bottom
if scrollView.contentOffset.y > 50 {
self.yourButton.isHidden = true
} else {
self.yourButton.isHidden = false
}
} else if (currentContentOffset < previousContentOffset) {
// scrolling towards the top
let maximumOffset = scrollView.contentSize.height - scrollView.frame.size.height
// Change 10.0 to adjust the distance from bottom
if maximumOffset - currentContentOffset <= 10.0 {
self.yourButton.isHidden = true
} else {
self.yourButton.isHidden = false
}
}
previousContentOffset = currentContentOffset
}
}
}

Which page of UICollectionView is visible?

Right now I have a UICollectionView set up with horizontal paging. I have sized my cells to fit the screen so that there is only ever one visible(unless you are scrolling).
What I wish to do is get the index path of the cell that is in view once you have stopped scrolling. This is because other parts of the view are dependent on the information that is currently visible.
I have set up a function in the scrollViewWillBeginDecelerating function and I am close but I still don't seem to understand how it is grabbing the cells.
func scrollViewWillBeginDecelerating(_ scrollView: UIScrollView) {
if( lastContentOffset > scrollView.contentOffset.x && lastPointVisited > 0)
{
lastPointVisited -= 1
print("Scrolling Right")
}
else if(lastContentOffset < scrollView.contentOffset.x && lastPointVisited < myPoints.count - 1)
{
lastPointVisited += 1
print("Scrolling Left")
}
lastContentOffset = scrollView.contentOffset.x
}
This gets me accurate results if I am very careful about scrolling. Going fast or only half scrolling a page so that it snaps back seems to throw everything off.
You can use following code to get page number
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
let pageWidth = scrollView.frame.size.width
let page = Int(floor((scrollView.contentOffset.x - pageWidth / 2) / pageWidth) + 1)
print("Page Number : \(page)")
}
It works for me If I put the same code in these two delegates at same time
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
//code here
}
func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
//code here
}
instead of writing it in
func scrollViewWillBeginDecelerating(_ scrollView: UIScrollView)
It gives me the exact value of index whether it is scrolling fast or half scrolled.
Hope it helps!
try this,
func scrollViewWillBeginDecelerating(_ scrollView: UIScrollView) {
let cellObj = collectionViewDemo.visibleCells[0]
print(collectionViewDemo.indexPath(for: cellObj))
}
You can use the below code it will be always effective to find the visible collectionView page
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let pageWidth = scrollview.frame.width
let pageIndex = scrollview.frame.width
let indexTemp = Int((scrollview.contentOffset.x +
pageWidth / 2) / pageWidth)
print(pageIndex)
}

Easiest way to disable back scrolling in scrollview

I want to stop backward scrolling on ScrollView after user scrolls to the next page. How can I do that.
I tried the following two codes, but the first one does not have any effect
self.scrollView.contentOffset = CGPointMake(self.view.frame.width,0)
and the second only disables the forward scrolling.
self.scrollView.contentSize = CGSizeMake( 2 * scrollWidth, scrollHeight);
To disable scrolling in one direction you implement the UIScrollViewDelegate method scrollViewDidScroll and put your logic there. For instance this TableViewController can only ever scroll down, because if the user tries to scroll up, we just overwrite the contentOffset, effectively undoing their scroll before they see it.
class ViewController: UITableViewController {
var lastScrollPosition = CGPoint.zero
override func scrollViewDidScroll(_ scrollView: UIScrollView) {
guard scrollView.contentOffset.y > lastScrollPosition.y else {
scrollView.setContentOffset(lastScrollPosition, animated: false)
return
}
lastScrollPosition = scrollView.contentOffset
}
}
If your cell is equal in size to your screen, you can apply the following option, which is very smooth:
var lastScrollPosition = CGPoint.zero
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
if scrollView.contentOffset.x == lastScrollPosition.x + UIScreen.main.bounds.width {
lastScrollPosition.x += UIScreen.main.bounds.width
}
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
guard scrollView.contentOffset.x > lastScrollPosition.x else {
scrollView.setContentOffset(lastScrollPosition, animated: false)
return
}
}

How can I detect the scroll direction from the UICollectionView?

I have a UICollectionView. I want to detect scroll direction. I have a two different animation style for scroll down and scroll up. So I must learn scroll direction.
CGPoint scrollVelocity = [self.collectionView.panGestureRecognizer
velocityInView:self.collectionView.superview];
if (scrollVelocity.y > 0.0f)
NSLog(#"scroll up");
else if(scrollVelocity.y < 0.0f)
NSLog(#"scroll down");
This is just work at finger touched. Not work for me
Try this:
Add this somewhere in you header:
#property (nonatomic) CGFloat lastContentOffset;
Then override the scrollViewDidScroll: method:
#pragma mark - UIScrollViewDelegate
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
if (self.lastContentOffset > scrollView.contentOffset.y)
{
NSLog(#"Scrolling Up");
}
else if (self.lastContentOffset < scrollView.contentOffset.y)
{
NSLog(#"Scrolling Down");
}
self.lastContentOffset = scrollView.contentOffset.y;
}
Found in Finding the direction of scrolling in a UIScrollView?
this is the best way to get scroll direction, hope this helps you
- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset {
CGPoint targetPoint = *targetContentOffset;
CGPoint currentPoint = scrollView.contentOffset;
if (targetPoint.y > currentPoint.y) {
NSLog(#"up");
}
else {
NSLog(#"down");
}
}
Swift 4.2
private var lastContentOffset: CGFloat = 0
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if lastContentOffset > scrollView.contentOffset.y && lastContentOffset < scrollView.contentSize.height - scrollView.frame.height {
// move up
print("move up")
originalHeight ()
} else if lastContentOffset < scrollView.contentOffset.y && scrollView.contentOffset.y > 0 {
// move down
print("move down")
minimizeHeaderView()
}
// update the new position acquired
lastContentOffset = scrollView.contentOffset.y
}
I was trying to find a way to detect if the user is mostly trying to pull vertically or horizontally the scrollView. I give you my solution, I hope it can be useful to anyone :
CGPoint _lastContentOffset;
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView {
_lastContentOffset = scrollView.contentOffset;
}
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
if (ABS(_lastContentOffset.x - scrollView.contentOffset.x) < ABS(_lastContentOffset.y - scrollView.contentOffset.y)) {
NSLog(#"Scrolled Vertically");
} else {
NSLog(#"Scrolled Horizontally");
}
}
This work find for me and I use this to avoid the scrollView to move horizontally when scrolling vertically and opposite.

Detect UITextView scroll location

I am trying to implement a form of a Terms & Conditions page where the "Proceed" button is only enabled once the user has scrolled to the bottom of a UITextView. So far I have set my class as a UIScrollView delegate & have implemented the method below:
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
{
NSLog(#"Checking if at bottom of UITextView");
CGPoint bottomOffset = CGPointMake(0,self.warningTextView.frame.size.height);
//if ([[self.warningTextView contentOffset] isEqualTO:bottomOffset])
{
}
}
I have commented the if statement because I am not sure how to check if the UITextView is at the bottom.
UITextView is a UIScrollView subclass. Therefore the UIScrollView delegate method you are using is also available when using UITextView.
Instead of using scrollViewDidEndDecelerating, you should use scrollViewDidScroll, as the scrollview may stop scrolling without deceleration.
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
if (scrollView.contentOffset.y >= scrollView.contentSize.height - scrollView.frame.size.height)
{
NSLog(#"at bottom");
}
}
A Swift version for this question:
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if scrollView.contentOffset.y >= scrollView.contentSize.height - scrollView.frame.size.height {
print( "View scrolled to the bottom" )
}
}
This should solve it. It works. I am using it.
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
{
float bottomEdge = scrollView.contentOffset.y + scrollView.frame.size.height;
if (bottomEdge >= scrollView.contentSize.height)
{
// we are at the end
}
}

Resources