Which page of UICollectionView is visible? - ios

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

Related

How do I control the whether a function will happen depending on the speed that a tableView is scrolled

I want an action to occur if a user scrolls down at any speed, or if a user scrolls up at a fast speed (or reaches the top of the view controller). I am using the following code below to sense any movement and implement the function where the "did move up" and "did move down" comments are, but I want to limit the did move up to occur only when scrolling fast or the user reached the top of the tableView. How do I do this?
// we set a variable to hold the contentOffSet before scroll view scrolls
var lastContentOffset: CGFloat = 0
// this delegate is called when the scrollView (i.e your UITableView) will start scrolling
func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
self.lastContentOffset = scrollView.contentOffset.y
}
// while scrolling this delegate is being called so you may now check which direction your scrollView is being scrolled to
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if self.lastContentOffset < scrollView.contentOffset.y {
// did move up
// I want this to only occur is the user is scrolling fast
} else if self.lastContentOffset > scrollView.contentOffset.y {
// did move down
} else {
// didn't move
}
}
I suppose you can store the time when scrollViewWillBeginDragging is called and then measure up the time difference against a certain amount of time in scrollViewDidScroll
var timeScrollingBegan: Date?
func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
// ...
timeScrollingBegan = Date()
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if self.lastContentOffset < scrollView.contentOffset.y {
// Calculate time difference in milliseconds
let timeDifference = Date().timeIntervalSince(timeScrollingBegan) * 1000
let movementDifference = scrollView.contentOffset.y - lastContentOffset
// If the movement difference is past a certain threshold
// in a certain amount of time, then it is too fast. It will
// take a bit of trial and error to determine the correct threshold
}
// ...
}
// You can probably use scrollViewDidEndDecelerating here instead
func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
timeScrollingBegan = nil
}

How can I detect when iOS ScrollView is at the very top?

How can I detect when the user scrolled to the very top of my collectionView?
I tried to use this code:
func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
if collectionView.contentOffset.y <= collectionView.contentSize.height - collectionView.frame.size.height {
print("top")
}
}
But it prints "top" even if I didn't get to the top.
scrollViewDidEndDragging means the user's finger lifted, but because it has physics to simulate inertia, the view may continue to move. There is another delegate method for exactly what you are looking for: scrollViewDidScrollToTop which as the name implies fires when you get to the top of the scrollview.
EDIT:
you can also try:
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if scrollView.contentOffset.y <= 0 {
print("top!")
}
}
EDIT third time's the charm? This one only fires once, after the scroll view has stopped moving, if you are at the top.
override func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
if scrollView.contentOffset.y == 0 {
print("top!")
}
}
You can try
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if scrollView.contentOffset.y == 0 {
// top
}
}

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

content offset doesn't work

I am trying to set the content offset in my uiscrollview. I have tried the following, and none of it works. None of the following affect my scrollView in any way, it simple appears on the screen as normal.:
let point = CGPoint(x: 0, y: self.view.frame.height / 2)
self.scrollView.setContentOffset(point, animated: true)
and:
self.scrollView.contentOffset.y = self.view.frame.size.height / 2
and:
self.scrollView.contentOffset = CGPointMake(0, self.view.frame.size.height / 2)
and:
self.scrollView.contentOffset = CGPointMake(0, scrollView.frame.size.height / 2)
I have a scroll view that is 2 times the height of my view. They are essentially separated into 2 different view that I can page between. I want the scrollview to start on the bottom part.
You should try them in the viewDidLayoutSubviews
Ref: iOS 9 scroll UITextView to top
I was really scratching my head when I found that setting scrollView.contentOffset to any value was not working. Eventually I realized that I was calling the scrollView delegate message 'scrollViewDidScroll' to manage scroll dragging peculiarities.
It turns out that 'scrollViewDidScroll' is called whenever the scrollView contentOffset is changed, and my code to manage drag issues was changing the contentOffset. I just made a 'BOOL isDragging' variable to only make changes to the contentOffset when dragging:
private var isDragging : Bool = false
private var initialContentOffset = CGPoint.zero
public func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
isDragging = true
...
}
public func scrollViewDidScroll(_ scrollView: UIScrollView) {
// need to keep track if we are dragging with isDragging
// since this 'scrollViewDidScroll' API is called with ANY contentOffset change
if (isDragging == true){
// do stuff to contentOffset to manage dragging peculiarities
...
}
}
public func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
if (decelerate == false){
scrollViewDidEndScrollingAnimation(scrollView)
}
}
public func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
scrollViewDidEndScrollingAnimation(scrollView)
}
public func scrollViewDidEndScrollingAnimation(_ scrollView: UIScrollView) {
isDragging = false
}

Detect UIScrollview bottom reached

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

Resources