Is there a way to detect or get a notification when user changes the page in a paging-enabled UIScrollView?
Implement the delegate of UIScrollView. This method is what you are looking for.
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
Use this to detect which page is currently being shown and perform some action on page change:
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
static NSInteger previousPage = 0;
CGFloat pageWidth = scrollView.frame.size.width;
float fractionalPage = scrollView.contentOffset.x / pageWidth;
NSInteger page = lround(fractionalPage);
if (previousPage != page) {
// Page has changed, do your thing!
// ...
// Finally, update previous page
previousPage = page;
}
}
If it's acceptable for you to only react to the page change once the scrolling has completely stopped, then it would be best to do the above inside the scrollViewDidEndDecelerating:
delegate method instead of the scrollViewDidScroll:
method.
scrollViewDidEndDecelerating
is a better solution but not a complete one. I've added a solution of my own that solved it completely for me. –
Coper In paging enabled scroll view you can use scrollViewDidEndDecelerating to know when the view is settled on a page (might be the same page).
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
scrollViewDidScroll
gets called on every movement. And in context of paging enabled view can be used to find when it is scrolled enough to move to next page (if dragging is stopped at that point).
- (void)scrollViewWillBeginDecelerating:(UIScrollView *)scrollView
and - (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
are not called at all? –
Catheterize How about combining two methods of UIScrollViewDelegate?
In scrollViewDidEndDragging(_:willDecelerate:)
, if it stops right away, we do the page calculation; if it is decelerating, we let it go and it will be caught by scrollViewDidEndDecelerating(_:)
.
The code is tested with XCode version 7.1.1, Swift version 2.1
class ViewController: UIViewController, UIScrollViewDelegate {
// MARK: UIScrollViewDelegate
func scrollViewDidEndDragging(scrollView: UIScrollView, willDecelerate decelerate: Bool) {
if decelerate == false {
let currentPage = scrollView.currentPage
// Do something with your page update
print("scrollViewDidEndDragging: \(currentPage)")
}
}
func scrollViewDidEndDecelerating(scrollView: UIScrollView) {
let currentPage = scrollView.currentPage
// Do something with your page update
print("scrollViewDidEndDecelerating: \(currentPage)")
}
}
extension UIScrollView {
var currentPage: Int {
return Int((self.contentOffset.x+ (0.5*self.frame.size.width))/self.frame.width)+1
}
}
First result on google for page detection so I had to answer this with a better solution in my opinion. (even if this question was asked 2 and a half years ago.)
I'd prefer not to call scrollViewDidScroll
just to track the page number. Thats an overkill for something simple as that.
Using scrollViewDidEndDecelerating
does work and stops on a page change BUT (and it's a big but) if the user will swipe on the screen twice a bit faster than normal scrollViewDidEndDecelerating
will be called only once. You can easily go from page #1 to page #3 without processing page #2.
This solved it completely for me:
- (void)scrollViewWillBeginDecelerating:(UIScrollView *)scrollView
{
scrollView.userInteractionEnabled = NO;
}
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
{
//Run your code on the current page
scrollView.userInteractionEnabled = YES;
}
That way the user can only swipe one page at a time without the risk described above.
scrollViewWillBeginDecelerating
and you will learn that the mil second the user lifts his finger from the first swipe you'll reach that break point. –
Coper scrollViewDidEndDecelerating
is triggered. That's the point of this fix. –
Coper Swift 4
I found the best way to do this is by using scrollViewWillEndDragging(_:withVelocity:targetContentOffset:)
. It lets you predict if paging will occur as soon as you lift your finger off the screen. This example is for paging horizontally.
Remember to the assign the scrollView.delegate
to the object that adopts UIScrollViewDelegate
and implements this method.
var previousPageXOffset: CGFloat = 0.0
func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
let targetOffset = targetContentOffset.pointee
if targetOffset.x == previousPageXOffset {
// page will not change
} else if targetOffset.x < previousPageXOffset {
// scroll view will page left
} else if targetOffset.x > previousPageXOffset {
// scroll view will page right
}
previousPageXOffset = targetOffset.x
// If you want to track the index of the page you are on just just divide the previousPageXOffset by the scrollView width.
// let index = Int(previousPageXOffset / scrollView.frame.width)
}
Implement the delegate of UIScrollView. This method is what you are looking for.
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
Here is the swift solution for this.
Make two properties currentPage and previousPage in the class where you are implementing your code and initialize them to 0.
Now update currentPage from scrollViewDidEndDragging(:willDecelerate:) and scrollViewDidEndDecelerating(:scrollView:).
And then update previousPage in scrollViewDidEndScrollingAnimation(_:scrollView:)
//Class Properties
var currentPage = 0
var previousPage = 0
func scrollViewDidEndDragging(scrollView: UIScrollView, willDecelerate decelerate: Bool) {
updatePage(scrollView)
return
}
func scrollViewDidEndDecelerating(scrollView: UIScrollView){
updatePage(scrollView)
return
}
func updatePage(scrollView: UIScrollView) {
let pageWidth:CGFloat = scrollView.frame.width
let current:CGFloat = floor((scrollView.contentOffset.x-pageWidth/2)/pageWidth)+1
currentPage = Int(current)
if currentPage == 0 {
// DO SOMETHING
}
else if currentPage == 1{
// DO SOMETHING
}
}
func scrollViewDidEndScrollingAnimation(scrollView: UIScrollView) {
if previousPage != currentPage {
previousPage = currentPage
if currentPage == 0 {
//DO SOMETHING
}else if currentPage == 1 {
// DO SOMETHING
}
}
}
var scrollViewPage = 0
override func viewDidLoad() {
super.viewDidLoad()
scrollViewPage = scrollView.currentPage
}
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
if scrollViewPage != scrollView.currentPage {
scrollViewPage = scrollView.currentPage
// Do something with your page update
print("scrollViewDidEndDecelerating: \(scrollViewPage)")
}
}
And Use extension
extension UIScrollView {
var currentPage: Int {
return Int((self.contentOffset.x + (0.5 * self.frame.size.width)) /
self.frame.width) + 1
}
}
Cut and paste for 2019
It's not so easy to do this:
var quantumPage: Int = -100 { // the UNIQUELY LANDED ON, NEVER REPEATING page
didSet {
print(">>>>>> QUANTUM PAGE IS \(quantumPage)")
pageHasActuallyChanged() // your function
}
}
private var possibleQuantumPage: Int = -100 {
didSet {
if oldValue != possibleQuantumPage {
quantumPage = possibleQuantumPage
}
}
}
public func scrollViewDidEndDragging(
_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
if decelerate == false {
possibleQuantumPage = currentPageEvenIfInBetween
}
}
public func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
possibleQuantumPage = currentPageEvenIfInBetween
}
var currentPageEvenIfInBetween: Int {
return Int((self.contentOffset.x + (0.5 * self.frame.width)) / self.frame.width)
}
Works perfectly.
pageHasActuallyChanged
will only be called when the user changes pages in what humans would consider "changing pages".
Be aware: the bringup is tricky:
This is difficult to initialize at bringup time, and will depend on how you are using the paged system.
In any paged system you will very likely have something like "scrollToViewAtIndex..."
open func scrollToViewAtIndexForBringup(_ index: Int) {
if index > -1 && index < childViews.count {
let w = self.frame.size.width
let h = self.frame.size.height
let frame = CGRect(x: CGFloat(index)*w, y: 0, width: w, height: h)
scrollRectToVisible(frame, animated: false) // NOTE THE FALSE
// AND IMPORTANTLY:
possibleQuantumPage = currentPageEvenIfInBetween
}
}
So, if the user opens the "book" at page 17, in your boss class you'd be calling that function to set it to "17" on bringup.
In such an example, you'd just have to remember that you must set initially our possibleQuantumPage value in any such bringup functions; there's no really generalized way to handle the starting situation.
After all you may, just for example, want to "quickly scroll" to the bringup page, and, who knows what that "means" in a quantumPage situation. So, be sure to initialize your quantum page system carefully during bringup, based on your situation.
In any event, just copy and paste the five functions at the top to get perfect quantum paging.
For Swift
static var previousPage: Int = 0
func scrollViewDidScroll(_ scrollView: UIScrollView){
let pageWidth: CGFloat = scrollView.frame.width
let fractionalPage: CGFloat = scrollView.contentOffset.x / pageWidth
let page = lround(Double(fractionalPage))
if page != previousPage{
print(page)
// page changed
}
}
© 2022 - 2024 — McMap. All rights reserved.