Eric1101000101
Eric1101000101

Reputation: 561

Progress of UIPageViewController

I would like to receive updates from the uipageviewcontroller during the page scrolling process. I want to know the transitionProgress in %. (This value should update when the user move the finger in order to get to another page). I'm interested in the animation progress from one page to another, not the progress through the total number of pages.

What I have found so far:

Upvotes: 15

Views: 11302

Answers (7)

Appygix
Appygix

Reputation: 652

in SWIFT to copy paste ;) works perfect for me

extension UIPageViewController: UIScrollViewDelegate {

    public override func viewDidLoad() {
        super.viewDidLoad()

        for subview in view.subviews {
            if let scrollView = subview as? UIScrollView {
                scrollView.delegate = self
            }
        }
    }

   public func scrollViewDidScroll(_ scrollView: UIScrollView) {
       let point = scrollView.contentOffset
       var percentComplete: CGFloat
       percentComplete = abs(point.x - view.frame.size.width)/view.frame.size.width
       print("percentComplete: ",percentComplete)
   }
}

Upvotes: 27

SamB
SamB

Reputation: 3253

KVO approach for Swift 4

var myContext = 0

override func viewDidLoad() {
    for view in self.view.subviews {
        if view is UIScrollView {
            view.addObserver(self, forKeyPath: "contentOffset", options: .new, context: &introPagingViewControllerContext)
        }
    }
}

// MARK: KVO

override func observeValue(forKeyPath keyPath: String?,
                           of object: Any?,
                           change: [NSKeyValueChangeKey : Any]?,
                           context: UnsafeMutableRawPointer?)
{
    guard let change = change else { return }
    if context != &myContext {
        super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context)
        return
    }

    if keyPath == "contentOffset" {
        if let contentOffset = change[NSKeyValueChangeKey.newKey] as? CGPoint {
            let screenWidth = UIScreen.main.bounds.width
            let percent = abs((contentOffset.x - screenWidth) / screenWidth)
            print(percent)
        }
    }
}

Upvotes: 0

Pochi
Pochi

Reputation: 13459

Based on Appgix solution, I'm adding this directly on my 'UIPageViewController' subclass. (Since I only need it on this one)

For Swift 3:

class MYPageViewControllerSubclass: UIPageViewController, UIScrollViewDelegate {

   override func viewDidLoad() {
          super.viewDidLoad()

          for subView in view.subviews {
             if subView is UIScrollView {
                (subView as! UIScrollView).delegate = self                
             }
          }
    }

    // MARK: - Scroll View Delegate

    public func scrollViewDidScroll(_ scrollView: UIScrollView) {
        let point = scrollView.contentOffset
        var percentComplete: CGFloat
        percentComplete = fabs(point.x - view.frame.size.width)/view.frame.size.width
        NSLog("percentComplete: %f", percentComplete)
    }

    // OTHER CODE GOES HERE...

}

Upvotes: 1

fruitcoder
fruitcoder

Reputation: 1228

While Appgix' solution seemed to work at first, I noticed that when the user pans in a UIPageViewController, lifts the finger shortly and then immediately starts dragging again while the "snap-back" animation is NOT YET finished and then lifts his finger again (which will again "snap-back"), the scrollViewDidScroll method is only called when the page view controller finished the animation. For the progress calculation this means the second pan produces continuous values like 0.11, 0.13, 0.16 but when the scroll view snaps back the next progress value will be 1.0 which causes my other scroll view to be out of sync.

To fight this I'm now listening to the scroll view's contentOffset key, which is still updated continuously in this situation.

Upvotes: 1

Eric1101000101
Eric1101000101

Reputation: 561

At last I found out a solution, even if it is probably not the best way to do it:

I first add an observer on the scrollview like this:

// Get Notified at update of scrollview progress
NSArray *views = self.pageViewController.view.subviews;
UIScrollView* sW = [views objectAtIndex:0];
[sW addObserver:self forKeyPath:@"contentOffset" options:NSKeyValueObservingOptionNew context:NULL];

And when the observer is called:

NSArray *views = self.pageViewController.view.subviews;
UIScrollView* sW = [views objectAtIndex:0];
CGPoint point = sW.contentOffset;

float percentComplete;
//iPhone 5
if([ [ UIScreen mainScreen ] bounds ].size.height == 568){
    percentComplete = fabs(point.x - 568)/568;

} else{
//iphone 4
    percentComplete = fabs(point.x - 480)/480;
}
NSLog(@"percentComplete: %f", percentComplete);

I'm very happy that I found this :-)

Upvotes: 4

genaks
genaks

Reputation: 757

Use this -

for (UIView *v in self.pageViewController.view.subviews) {
    if ([v isKindOfClass:[UIScrollView class]]) {
        ((UIScrollView *)v).delegate = self;
    }
}

to implement this protocol : -(void)scrollViewDidScroll:(UIScrollView *)scrollView

and then use @xhist's code (modified) in this way

-(void)scrollViewDidScroll:(UIScrollView *)scrollView
{
CGPoint point = scrollView.contentOffset;

float percentComplete;
percentComplete = fabs(point.x - self.view.frame.size.width)/self.view.frame.size.width;
NSLog(@"percentComplete: %f", percentComplete);
}

Upvotes: 2

Stéphane Copin
Stéphane Copin

Reputation: 1998

Since I thought that the functionality of scrolling would stay forever, but that the internal implementation may change to something other than a scroll view, I found the solution below (I haven't tested this very much, but still)

NSUInteger offset = 0;
UIViewController * firstVisibleViewController;
while([(firstVisibleViewController = [self viewControllerForPage:offset]).view superview] == nil) {
  ++offset;
}
CGRect rect = [[firstVisibleViewController.view superview] convertRect:firstVisibleViewController.view.frame fromView:self.view];
CGFloat absolutePosition = rect.origin.x / self.view.frame.size.width;
absolutePosition += (CGFloat)offset;

(self is the UIPageViewController here, and [-viewControllerForPage:] is a method that returns the view controller at the given page)

If absolutePosition is 0.0f, then the first view controller is shown, if it's equal to 1.0f, the second one is shown, etc... This can be called repeatedly in a CADisplayLink along with the delegate methods and/or UIPanGestureRecognizer to effectively know the status of the current progress of the UIPageViewController.

EDIT: Made it work for any number of view controllers

Upvotes: 2

Related Questions