Searched a lot for this one, but couldn't find a proper solution yet.
Is it possible to disable the bounce effect of a UIPageViewController
and still use the UIPageViewControllerTransitionStyleScroll
?
Add the <UIScrollViewDelegate>
delegate to your UIPageViewController's header
Set the UIPageViewController's underlying UIScrollView's delegates to their parent in viewDidLoad
:
for (UIView *view in self.view.subviews) {
if ([view isKindOfClass:[UIScrollView class]]) {
((UIScrollView *)view).delegate = self;
break;
}
}
The implementation for scrollViewDidScroll is to reset the contentOffset to the origin (NOT (0,0), but (bound.size.width, 0)) when the user is reaching out of the bounds, like this:
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
if (_currentPage == 0 && scrollView.contentOffset.x < scrollView.bounds.size.width) {
scrollView.contentOffset = CGPointMake(scrollView.bounds.size.width, 0);
} else if (_currentPage == totalViewControllersInPageController-1 && scrollView.contentOffset.x > scrollView.bounds.size.width) {
scrollView.contentOffset = CGPointMake(scrollView.bounds.size.width, 0);
}
}
Finally, the implementation for scrollViewWillEndDragging is to deal with a bug scenario when the user quickly swipes from left to right at the first page, the first page won't bounce at the left (due to the function above), but will bounce at the right caused by the (maybe) velocity of the swipe. And finally when bounced back, the UIPageViewController will trigger a page flip to the 2nd page (which is of course, not expected).
- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset {
if (_currentPage == 0 && scrollView.contentOffset.x <= scrollView.bounds.size.width) {
*targetContentOffset = CGPointMake(scrollView.bounds.size.width, 0);
} else if (_currentPage == totalViewControllersInPageController-1 && scrollView.contentOffset.x >= scrollView.bounds.size.width) {
*targetContentOffset = CGPointMake(scrollView.bounds.size.width, 0);
}
}
Code to put into viewDidLoad
:
for subview in self.view.subviews {
if let scrollView = subview as? UIScrollView {
scrollView.delegate = self
break;
}
}
Implementation for scrollViewDidScroll:
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if (currentPage == 0 && scrollView.contentOffset.x < scrollView.bounds.size.width) {
scrollView.contentOffset = CGPoint(x: scrollView.bounds.size.width, y: 0);
} else if (currentPage == totalViewControllersInPageController - 1 && scrollView.contentOffset.x > scrollView.bounds.size.width) {
scrollView.contentOffset = CGPoint(x: scrollView.bounds.size.width, y: 0);
}
}
Implementation for scrollViewWillEndDragging:
func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
if (currentPage == 0 && scrollView.contentOffset.x <= scrollView.bounds.size.width) {
targetContentOffset.pointee = CGPoint(x: scrollView.bounds.size.width, y: 0);
} else if (currentPage == totalViewControllersInPageController - 1 && scrollView.contentOffset.x >= scrollView.bounds.size.width) {
targetContentOffset.pointee = CGPoint(x: scrollView.bounds.size.width, y: 0);
}
}
Disable UIPageViewController's bounce
Swift 2.2
Addition to answers
1) Add UIScrollViewDelegate to UIPageViewController
extension PageViewController: UIScrollViewDelegate
2) Add to viewDidLoad
for view in self.view.subviews {
if let scrollView = view as? UIScrollView {
scrollView.delegate = self
}
}
3) Add UIScrollViewDelegate methods
func scrollViewDidScroll(scrollView: UIScrollView) {
if currentIndex == 0 && scrollView.contentOffset.x < scrollView.bounds.size.width {
scrollView.contentOffset = CGPoint(x: scrollView.bounds.size.width, y: 0)
} else if currentIndex == totalViewControllers - 1 && scrollView.contentOffset.x > scrollView.bounds.size.width {
scrollView.contentOffset = CGPoint(x: scrollView.bounds.size.width, y: 0)
}
}
func scrollViewWillEndDragging(scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
if currentIndex == 0 && scrollView.contentOffset.x < scrollView.bounds.size.width {
scrollView.contentOffset = CGPoint(x: scrollView.bounds.size.width, y: 0)
} else if currentIndex == totalViewControllers - 1 && scrollView.contentOffset.x > scrollView.bounds.size.width {
scrollView.contentOffset = CGPoint(x: scrollView.bounds.size.width, y: 0)
}
}
Below is a complete solution that, unlike with other answers, doesn't require you to write/test your own code for tracking the index of the currently displayed page.
Assuming that your pages are stored in sourcePageViewControllers
immutable array and after you've created your UIPageViewController
as myPageViewController
:
let scrollView = myPageViewController.view.subviews.compactMap({ $0 as? UIScrollView }).first!
scrollView.delegate = <instance of YourScrollViewDelegateClass>
And then:
extension YourScrollViewDelegateClass: UIScrollViewDelegate {
func scrollViewDidScroll(_ scrollView: UIScrollView) {
guard sourcePageViewControllers.count > 1 else {
scrollView.isScrollEnabled = false
return
}
guard let viewControllers = myPageViewController.viewControllers, viewControllers.count != 0 else { return }
let baseRecord =
viewControllers
.map { [superview = myPageViewController.view!] viewController -> (viewController: UIViewController, originX: CGFloat) in
let originX = superview.convert(viewController.view.bounds.origin, from: viewController.view).x
return (viewController: viewController, originX: originX)
}
.sorted(by: { $0.originX < $1.originX })
.first!
guard let baseIndex = sourcePageViewControllers.firstIndex(of: baseRecord.viewController) else { return }
let baseViewControllerOffsetXRatio = -baseRecord.originX/scrollView.bounds.width
let progress = (CGFloat(baseIndex) + baseViewControllerOffsetXRatio)/CGFloat(sourcePageViewControllers.count - 1)
if !(0...1 ~= progress) {
scrollView.isScrollEnabled = false
scrollView.isScrollEnabled = true
}
}
}
If you keep track of your currentIndex then the below should be sufficient but its a little buggy because there is a random scenario where it stops scrolling altogether.
I think the scrollView.bounces is a little buggy, perhaps I am missing something because most of the time it works fine, if anyone is able to have a solution based on the below it would be great please.
public func scrollViewDidScroll(_ scrollView: UIScrollView) {
scrollView.bounces = currentIndex == 0 ||
currentIndex == controllers.count - 1
? false
: true
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With