Hide navigation bar when scrolling web view without navigation controller in iOS - ios

I want the same behaviour as this
How do you hide navigation bar when scrolling in web view if the main view has a navigation bar without a navigationController? Navigation bars don't have the alternative via storyboard to check 'hide bars on swipe'.
This will not work either
self.navigationController.hidesBarsOnSwipe = true
This is what my SB looks like:
Regards

you might try the following solution:
Swift
import UIKit
class ViewController: UIViewController, UIScrollViewDelegate {
#IBOutlet var webView:UIWebView!
#IBOutlet var navigationBar:UINavigationBar!
#IBOutlet var navigationBarTop:NSLayoutConstraint!
var lastContentOffset:CGFloat = 0.0
override func viewDidLoad() {
super.viewDidLoad()
self.webView.scrollView.delegate = self
self.webView.loadRequest(URLRequest(url: URL(string: "https://www.apple.com")!))
}
public func scrollViewDidScroll(_ scrollView: UIScrollView) {
let threshold:CGFloat = 20.0
let delta = abs(self.lastContentOffset - scrollView.contentOffset.y)
if delta > threshold || scrollView.contentOffset.y <= 0 {
if self.lastContentOffset > scrollView.contentOffset.y && self.navigationBarTop.constant < 0 && (self.lastContentOffset < (scrollView.contentSize.height - scrollView.frame.height)) {
self.showNavBar(true)
} else if (self.lastContentOffset < scrollView.contentOffset.y && self.navigationBarTop.constant == 0 && (self.lastContentOffset > 0)) {
self.showNavBar(false)
}
}
self.lastContentOffset = scrollView.contentOffset.y;
}
func showNavBar(_ isVisible:Bool) {
UIView.animate(withDuration: 0.25, animations: {
self.navigationBarTop.constant = isVisible ? 0 : -(self.navigationBar.frame.height + UIApplication.shared.statusBarFrame.height)
self.view.setNeedsLayout()
self.view.layoutIfNeeded()
})
}
}
full source code here: https://github.com/mugx/AnimatedNavigationBar
Objective-C
#import <UIKit/UIKit.h>
#interface ViewController : UIViewController<UIScrollViewDelegate>
#property IBOutlet UIWebView *webView;
#property IBOutlet UINavigationBar *navigationBar;
#property IBOutlet NSLayoutConstraint *navigationBarTop;
#property (nonatomic,assign) CGFloat lastContentOffset;
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.webView.scrollView.delegate = self;
[self.webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:#"https://www.apple.com"]]];
}
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
CGFloat threshold = 20.0;
CGFloat delta = fabs(self.lastContentOffset - scrollView.contentOffset.y);
if (delta > threshold || scrollView.contentOffset.y <= 0) {
if (self.lastContentOffset > scrollView.contentOffset.y && self.navigationBarTop.constant < 0 && (self.lastContentOffset < (scrollView.contentSize.height - scrollView.frame.size.height))) {
[self showNavBar:true];
} else if (self.lastContentOffset < scrollView.contentOffset.y && self.navigationBarTop.constant == 0 && (self.lastContentOffset > 0)) {
[self showNavBar:false];
}
}
self.lastContentOffset = scrollView.contentOffset.y;
}
- (void)showNavBar:(Boolean)isVisible {
[UIView animateWithDuration:0.25 animations:^{
self.navigationBarTop.constant = isVisible ? 0 : -(self.navigationBar.frame.size.height + UIApplication.sharedApplication.statusBarFrame.size.height);
[self.view setNeedsLayout];
[self.view layoutIfNeeded];
}];
}
#end
This is the first solution I thought, of course is not drag and drop, you have to link the respective IBOutlet. Anyway the mechanism I think is clear, leverage on scrollView delegate embedded in the webview, then calculate the delta offset in order to show/hide (with a smooth animation) your custom navigation bar. If something is not clear, I'll modify the answer.

webView.scrollView.delegate = self
extension ViewController:UIScrollViewDelegate{
func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
navigationController?.hidesBarsOnSwipe = velocity.y > 0
}
}

Related

Detect that a PDFView did scroll

Does PDFKit on iOS expose a PDFView's underlying UIScrollView or is there any other way to directly detect that the user has scrolled a PDFView?
My use case is to hide a nav bar when the document is scrolled so as a workaround I've added my own pan gesture recogniser to the PDFView's parent and I do the hiding in gestureRecognizerShouldBegin and always return false but I expect there's something more like UIScrollViewDelegate that I'm missing in the docs.
Try this,
NotificationCenter.default.addObserver(self, selector: #selector(handlePageChange(notification:)), name: Notification.Name.PDFViewPageChanged, object: nil)
#objc private func handlePageChange(notification: Notification)
{
print("Page changed")
}
Does PDFKit on iOS expose a PDFView's underlying UIScrollView
No, but hopefully Apple will add this in the future. I remember that UIWebView didn't have it originally and it was added later.
or is there any other way to directly detect that the user has scrolled a PDFView
No, it looks like none of the notifications provided by PDFViewDelegate address this.
I'm migrating from UIWebView to PDFView and am using scrollViewDidScroll for a bunch of stuff, so I didn't want to rely on just adding a pan gesture recognizer. Building from #Matthijs's answer, I'm finding the UIScrollView inside the PDFView, making my class its delegate, then passing any events back to the scroll view (which was its own delegate before my class became the delegate) so it can respond to them, too. With UIWebView, this last step was not necessary, but with PDFView, zooming and possibly other functions won't work without it.
I'm overriding all the documented delegate methods to reduce the chance that this will break if Apple changes the internal function of PDFView. However, I had to check respondsToSelector in each method, because the original scroll view delegate doesn't currently implement all of them.
- (void)viewDidLoad {
// create the PDFView and find its inner scrollView
self.pdfView = [[PDFView alloc] init];
for (UIView *subview in self.pdfView.subviews) {
if ([subview isKindOfClass:[UIScrollView class]]) {
self.scrollView = (UIScrollView *)subview;
} else {
for (UIView *subsubview in subview.subviews) {
if ([subsubview isKindOfClass:[UIScrollView class]]) {
self.scrollView = (UIScrollView *)subsubview;
}
}
}
}
}
- (void)loadPDFDocument:(NSString *)URL {
// load a document, then become the delegate for the scrollView (we have to do that after loading the document)
PDFDocument *document = [[PDFDocument alloc] initWithURL:URL];
self.pdfView.document = document;
self.scrollView.delegate = self;
}
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
// *** respond to scroll events here ***
UIScrollView <UIScrollViewDelegate> *scrollViewDelegate = (UIScrollView <UIScrollViewDelegate> *)self.scrollView;
if ([scrollViewDelegate respondsToSelector:#selector(scrollViewDidScroll:)]) {
[scrollViewDelegate scrollViewDidScroll:scrollView];
}
}
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView {
UIScrollView <UIScrollViewDelegate> *scrollViewDelegate = (UIScrollView <UIScrollViewDelegate> *)self.scrollView;
if ([scrollViewDelegate respondsToSelector:#selector(scrollViewWillBeginDragging:)]) {
[scrollViewDelegate scrollViewWillBeginDragging:scrollView];
}
}
- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset {
UIScrollView <UIScrollViewDelegate> *scrollViewDelegate = (UIScrollView <UIScrollViewDelegate> *)self.scrollView;
if ([scrollViewDelegate respondsToSelector:#selector(scrollViewWillEndDragging:withVelocity:targetContentOffset:)]) {
[scrollViewDelegate scrollViewWillEndDragging:scrollView withVelocity:velocity targetContentOffset:targetContentOffset];
}
}
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate {
UIScrollView <UIScrollViewDelegate> *scrollViewDelegate = (UIScrollView <UIScrollViewDelegate> *)self.scrollView;
if ([scrollViewDelegate respondsToSelector:#selector(scrollViewDidEndDragging:willDecelerate:)]) {
[scrollViewDelegate scrollViewDidEndDragging:scrollView willDecelerate:decelerate];
}
}
- (BOOL)scrollViewShouldScrollToTop:(UIScrollView *)scrollView {
UIScrollView <UIScrollViewDelegate> *scrollViewDelegate = (UIScrollView <UIScrollViewDelegate> *)self.scrollView;
if ([scrollViewDelegate respondsToSelector:#selector(scrollViewShouldScrollToTop:)]) {
return [scrollViewDelegate scrollViewShouldScrollToTop:scrollView];
}
return TRUE;
}
- (void)scrollViewDidScrollToTop:(UIScrollView *)scrollView {
UIScrollView <UIScrollViewDelegate> *scrollViewDelegate = (UIScrollView <UIScrollViewDelegate> *)self.scrollView;
if ([scrollViewDelegate respondsToSelector:#selector(scrollViewDidScrollToTop:)]) {
[scrollViewDelegate scrollViewDidScrollToTop:scrollView];
}
}
- (void)scrollViewWillBeginDecelerating:(UIScrollView *)scrollView {
UIScrollView <UIScrollViewDelegate> *scrollViewDelegate = (UIScrollView <UIScrollViewDelegate> *)self.scrollView;
if ([scrollViewDelegate respondsToSelector:#selector(scrollViewWillBeginDecelerating:)]) {
[scrollViewDelegate scrollViewWillBeginDecelerating:scrollView];
}
}
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
UIScrollView <UIScrollViewDelegate> *scrollViewDelegate = (UIScrollView <UIScrollViewDelegate> *)self.scrollView;
if ([scrollViewDelegate respondsToSelector:#selector(scrollViewDidEndDecelerating:)]) {
[scrollViewDelegate scrollViewDidEndDecelerating:scrollView];
}
}
- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView {
UIScrollView <UIScrollViewDelegate> *scrollViewDelegate = (UIScrollView <UIScrollViewDelegate> *)self.scrollView;
if ([scrollViewDelegate respondsToSelector:#selector(viewForZoomingInScrollView:)]) {
return [scrollViewDelegate viewForZoomingInScrollView:scrollView];
}
return nil;
}
- (void)scrollViewWillBeginZooming:(UIScrollView *)scrollView withView:(UIView *)view {
UIScrollView <UIScrollViewDelegate> *scrollViewDelegate = (UIScrollView <UIScrollViewDelegate> *)self.scrollView;
if ([scrollViewDelegate respondsToSelector:#selector(scrollViewWillBeginZooming:withView:)]) {
[scrollViewDelegate scrollViewWillBeginZooming:scrollView withView:view];
}
}
- (void)scrollViewDidEndZooming:(UIScrollView *)scrollView withView:(UIView *)view atScale:(CGFloat)scale {
UIScrollView <UIScrollViewDelegate> *scrollViewDelegate = (UIScrollView <UIScrollViewDelegate> *)self.scrollView;
if ([scrollViewDelegate respondsToSelector:#selector(scrollViewDidEndZooming:withView:atScale:)]) {
[scrollViewDelegate scrollViewDidEndZooming:scrollView withView:view atScale:scale];
}
}
- (void)scrollViewDidZoom:(UIScrollView *)scrollView {
UIScrollView <UIScrollViewDelegate> *scrollViewDelegate = (UIScrollView <UIScrollViewDelegate> *)self.scrollView;
if ([scrollViewDelegate respondsToSelector:#selector(scrollViewDidZoom:)]) {
[scrollViewDelegate scrollViewDidZoom:scrollView];
}
}
- (void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView {
UIScrollView <UIScrollViewDelegate> *scrollViewDelegate = (UIScrollView <UIScrollViewDelegate> *)self.scrollView;
if ([scrollViewDelegate respondsToSelector:#selector(scrollViewDidEndScrollingAnimation:)]) {
[scrollViewDelegate scrollViewDidEndScrollingAnimation:scrollView];
}
}
- (void)scrollViewDidChangeAdjustedContentInset:(UIScrollView *)scrollView {
UIScrollView <UIScrollViewDelegate> *scrollViewDelegate = (UIScrollView <UIScrollViewDelegate> *)self.scrollView;
if ([scrollViewDelegate respondsToSelector:#selector(scrollViewDidChangeAdjustedContentInset:)]) {
[scrollViewDelegate scrollViewDidChangeAdjustedContentInset:scrollView];
}
}
I did this to detect zooming and panning on a pdfView to copy those gestures to a second pdfView, and it's working perfectly fine here.
Got some help to detect vertical and horizontal panning by the PanDirectionGestureRecognizer I found here: stackoverflow.com/a/55635482/558112
class Document: UIViewController, UIScrollViewDelegate {
override func viewDidLoad() {
super.viewDidLoad()
// Subscribe to notifications.
NotificationCenter.default.addObserver(self, selector: #selector(onPageZoomAndPan), name: .PDFViewScaleChanged, object: pdfView
// get the scrollView in pdfView and attach gesture recognizers
outerLoop: for subView in pdfView.subviews {
for view in subView.subviews {
if let scrollView = view as? UIScrollView {
let xScrollViewPanGesture = PanDirectionGestureRecognizer(direction: .horizontal, target: self, action: #selector(onPageZoomAndPan))
xScrollViewPanGesture.delegate = self
scrollView.addGestureRecognizer(xScrollViewPanGesture)
let yScrollViewPanGesture = PanDirectionGestureRecognizer(direction: .vertical, target: self, action: #selector(onPageZoomAndPan))
yScrollViewPanGesture.delegate = self
scrollView.addGestureRecognizer(yScrollViewPanGesture)
break outerLoop
}
}
}
}
// MARK: - UIScrollViewDelegate
#objc private func onPageZoomAndPan() {
let rect = pdfView.convert(pdfView.bounds, to: pdfView.currentPage!)
pdfViewSecondScreen.scaleFactor = pdfView.scaleFactor
pdfViewSecondScreen.go(to: rect, on: pdfView.currentPage!)
}
}
enum PanDirection {
case vertical
case horizontal
}
// UIKit.UIGestureRecognizerSubclass
import UIKit.UIGestureRecognizerSubclass
class PanDirectionGestureRecognizer: UIPanGestureRecognizer {
let direction : PanDirection
init(direction: PanDirection, target: AnyObject, action: Selector) {
self.direction = direction
super.init(target: target, action: action)
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent) {
super.touchesMoved(touches, with: event)
if state == .began {
let vel = velocity(in: self.view!)
switch direction {
case .horizontal where abs(vel.y) > abs(vel.x):
state = .cancelled
case .vertical where abs(vel.x) > abs(vel.y):
state = .cancelled
default:
break
}
}
}
}
Decided on a solution other's haven't done yet. Went with key value observing on the contentOffset property of the underlying UIScrollView.
You can use this extension to run a callback every time the scroll offset changes.
var observation = pdfView.onScrollOffsetChanged { scroll in
print("PDFView scrolled to \(scroll.contentOffset).")
}
The extension
extension PDFView {
func onScrollOffsetChange(handler: #escaping (UIScrollView) -> Void) -> NSKeyValueObservation? {
detectScrollView()?.observe(\.contentOffset) { scroll, _ in
handler(scroll)
}
}
private func detectScrollView() -> UIScrollView? {
for view in subviews {
if let scroll = view as? UIScrollView {
return scroll
} else {
for subview in view.subviews {
if let scroll = subview as? UIScrollView {
return scroll
}
}
}
}
print("Unable to find a scrollView subview on a PDFView.")
return nil
}
}
try this!
(pdfView.subviews[0] as? UIScrollView)?.delegate = self
and observe the scrollview delegate
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if scrollView.contentOffset.y > 0 {
/// ...
} else {
/// ...
}
}

Zoom UIImageView in UIScrollView – not centered

I have an UIScrollView with an UIImageView in it. Just a simple test case: The UIImageView is full width of the scroll view and centered to Y with auto layout. However, in the final result on my device it is not really centered by Y.
But i have the problem that when zooming the UIImageView with the scroll view it "drifts" to the bottom area – not just scaled in the center as it should be. It also "drifts" to the left when zoom out (at the very end of the video) and bounces back to the center.
I've made a small preview video of this behavior: https://www.youtube.com/watch?v=ivRNkzNrcEA
Here is my simple test code:
class TestController: UIViewController, UIScrollViewDelegate {
#IBOutlet weak var SCROLL: UIScrollView!
#IBOutlet weak var IMAGE: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
SCROLL.minimumZoomScale = 1;
SCROLL.maximumZoomScale = 6.0;
SCROLL.zoomScale = 1.0;
SCROLL.contentSize = IMAGE.frame.size;
SCROLL.delegate = self;
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
}
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
}
func viewForZoomingInScrollView(scrollView: UIScrollView) -> UIView? {
return IMAGE
}
func scrollViewDidZoom(scrollView: UIScrollView) {
}
func scrollViewDidEndZooming(scrollView: UIScrollView, withView view: UIView?, atScale scale: CGFloat) {
}
}
And here is my test auto layout:
I am not sure if it is possible to keep centering with just auto-layout. One approach that works for me is to to sub-class UIScrollView and modify layoutSubviews as illustrated in Apple WWDC example. Code below
-(void) layoutSubviews
{
[super layoutSubviews];
UIImageView *v = (UIImageView *)[self.delegate viewForZoomingInScrollView:self];
//Centering code
CGFloat svw = self.bounds.size.width;
CGFloat svh = self.bounds.size.height;
CGFloat vw = v.frame.size.width;
CGFloat vh = v.frame.size.height;
CGRect f = v.frame;
if (svw > vw)
{
f.origin.x = (svw - vw)/2.0;
}
else
{
f.origin.x = 0;
}
if (svh > vh)
{
f.origin.y = (svh - vh)/2.0;
}
else
{
f.origin.y = 0;
}
v.frame = f;
}

How to disable scrolling while using UIPanGestureRecognizer on a UIView

In my app I have a draggable UIView contained in a UIScrollView so that I can zoom it. This moving UIView has a UIPanGestureRecognizer but if I zoom in first and then I try to move the UIView, that does not happen and instead the scrolling is performed as if the UIPanGestureRecognizer is not detected.
This is my associated method to the UIPanGestureRecognizer:
func handlePan(recognizer: UIPanGestureRecognizer) {
if (self.panRec.state == UIGestureRecognizerState.Began) {
self.scrollViewContainer.scrollEnabled = false
}
else if (self.panRec.state == UIGestureRecognizerState.Ended) {
self.scrollViewContainer.scrollEnabled = true
}
let translation = recognizer.translationInView(self)
if let view = recognizer.view {
self.center = CGPoint(x:view.center.x + translation.x,
y:view.center.y + translation.y)
}
recognizer.setTranslation(CGPointZero, inView: self)
self.updatePinsPosition()
}
As you can see I try to disable scrolling when the the user taps in the draggable UIView and reactivate it at the end but it doesn't work. Where am I making a mistake? What am I missing?
UPDATE
So explain myself better, I have a UIScrollView which contains a UIView and inside this last there are some other UIViews acting as pinch, the azure ones. I decided to use a further UIView as container so that the sub views are zoomed all together by scrolling. So, my zoom code is the following:
class PhotoViewController: UIViewController, UINavigationControllerDelegate, UIImagePickerControllerDelegate, UIScrollViewDelegate {
#IBOutlet weak var scrollView: UIScrollView!
#IBOutlet weak var containerView: ContainerController!
override func viewDidLoad() {
...
self.scrollView.delegate = self
self.scrollView.showsVerticalScrollIndicator = true
self.scrollView.flashScrollIndicators()
self.scrollView.minimumZoomScale = 1.0
self.scrollView.maximumZoomScale = 10.0
}
func viewForZoomingInScrollView(scrollView: UIScrollView) -> UIView? {
return self.containerView
}
}
You have to override this method of Scrollview Delegate in your ViewController for both zooming and panning
- (void)scrollViewDidEndZooming:(UIScrollView *)scrollView withView:(UIView *)view atScale:(float)scale {
scale *= [[[self.scrollView window] screen] scale];
[view setContentScaleFactor:scale]; // View which you want to Zoom & pan.
for (UIView *subview in view.subviews) {
[subview setContentScaleFactor:scale]; // all the Container Views
}
}
Hope it will help you to solve your problem.

How to detect scrollView scrolling direction?

I have a UIScrollView inside that I have a UITableView The scroll view should scrol horizontally. And I have implemented - (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView to do some tasks when the scroll view end its horizontal scrolling. But My problem is, this delegate is firing even when the UITableView vertical scrolling stopped too. So how can I detect just the scrollview horizontal scrolling within this delegate?
Please help me,
Thanks
Check these delegate methods
CGPoint _lastContentOffset;
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView {
_lastContentOffset = scrollView.contentOffset;
}
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
if (_lastContentOffset.x < (int)scrollView.contentOffset.x) {
NSLog(#"Scrolled Right");
}
else if (_lastContentOffset.x > (int)scrollView.contentOffset.x) {
NSLog(#"Scrolled Left");
}
else if (_lastContentOffset.y < scrollView.contentOffset.y) {
NSLog(#"Scrolled Down");
}
else if (_lastContentOffset.y > scrollView.contentOffset.y) {
NSLog(#"Scrolled Up");
}
}
Swift version:
var _lastContentOffset: CGPoint!
func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
_lastContentOffset = scrollView.contentOffset
}
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
if _lastContentOffset.x < scrollView.contentOffset.x {
NSLog("Scrolled Right")
}
else if _lastContentOffset.x > scrollView.contentOffset.x {
NSLog("Scrolled Left")
}
else if _lastContentOffset.y < scrollView.contentOffset.y {
NSLog("Scrolled Down")
}
else if _lastContentOffset.y > scrollView.contentOffset.y {
NSLog("Scrolled Up")
}
}

Get scroll position of UIPageViewController

I am using a UIPageViewController, and I need to get the scroll position of the ViewController as the users swipe so I can partially fade some assets while the view is transitioning to the next UIViewController.
The delegate and datasource methods of UIPageViewController don't seem to provide any access to this, and internally I'm assuming that the UIPageViewController must be using a scroll view somewhere, but it doesn't seem to directly subclass it so I'm not able to call
func scrollViewDidScroll(scrollView: UIScrollView) {
}
I've seen some other posts suggestion to grab a reference to the pageViewController!.view.subviews and then the first index is a scrollView, but this seems very hacky. I'm wondering if there is a more standard way to handle this.
You can search for the UIScrollView inside your UIPageViewController. To do that, you will have to implement the UIScrollViewDelegate.
After that you can get your scrollView:
for v in pageViewController.view.subviews{
if v.isKindOfClass(UIScrollView){
(v as UIScrollView).delegate = self
}
}
After that, you are able to use all the UIScrollViewDelegate-methods and so you can override the scrollViewDidScroll method where you can get the scrollPosition:
func scrollViewDidScroll(scrollView: UIScrollView) {
//your Code
}
Or if you want a one-liner:
let scrollView = view.subviews.filter { $0 is UIScrollView }.first as! UIScrollView
scrollView.delegate = self
UIPageViewController scroll doesn't work like normal scrollview and you can't get scrollView.contentOffset like other scrollViews.
so here is a trick to get what's going on when user scrolls :
first you have to find scrollview and set delegate to current viewController like other answers said.
class YourViewController : UIPageViewController {
var startOffset = CGFloat(0) //define this
override func viewDidLoad() {
super.viewDidLoad()
//from other answers
for v in view.subviews{
if v is UIScrollView {
(v as! UIScrollView).delegate = self
}
}
}
.
.
.
}
extension YourViewController : UIScrollViewDelegate{
func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
startOffset = scrollView.contentOffset.x
}
public func scrollViewDidScroll(_ scrollView: UIScrollView) {
var direction = 0 //scroll stopped
if startOffset < scrollView.contentOffset.x {
direction = 1 //going right
}else if startOffset > scrollView.contentOffset.x {
direction = -1 //going left
}
let positionFromStartOfCurrentPage = abs(startOffset - scrollView.contentOffset.x)
let percent = positionFromStartOfCurrentPage / self.view.frame.width
//you can decide what to do with scroll
}
}
Similar to Christian's answer but a bit more Swift-like (and not unnecessarily continuing to loop through view.subviews):
for view in self.view.subviews {
if let view = view as? UIScrollView {
view.delegate = self
break
}
}
As of iOS 13, the UIPageViewController seems to reset the scrollview's contentOffset once it transitions to another view controller. Here is a working solution:
Find the child scrollView and set its delegate to self, as other answers suggested
Keep track of the current page index of the pageViewController:
var currentPageIndex = 0
// The pageViewController's viewControllers
let orderredViewControllers: [UIViewController] = [controller1, controller2, ...]
pageViewController.delegate = self
func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) {
guard completed, let currentViewController = pageViewController.viewControllers?.first else { return }
currentPageIndex = orderredViewControllers.firstIndex(of: currentViewController)!
}
Get the progress that ranges from 0 to 1
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let contentOffsetX = scrollView.contentOffset.x
let width = scrollView.frame.size.width
let offset = CGFloat(currentPageIndex) / CGFloat(orderredViewControllers.count - 1)
let progress = (contentOffsetX - width) / width + offset
}
var pageViewController: PageViewController? {
didSet {
pageViewController?.dataSource = self
pageViewController?.delegate = self
scrollView?.delegate = self
}
}
lazy var scrollView: UIScrollView? = {
for subview in pageViewController?.view?.subviews ?? [] {
if let scrollView = subview as? UIScrollView {
return scrollView
}
}
return nil
}()
extension BaseFeedViewController: UIScrollViewDelegate {
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let offset = scrollView.contentOffset.x
let bounds = scrollView.bounds.width
let page = CGFloat(self.currentPage)
let count = CGFloat(viewControllers.count)
let percentage = (offset - bounds + page * bounds) / (count * bounds - bounds)
print(abs(percentage))
}
}
To make the code as readable and separated as possible, I would define an extension on UIPageViewController:
extension UIPageViewController {
var scrollView: UIScrollView? {
view.subviews.first(where: { $0 is UIScrollView }) as? UIScrollView
}
}
It's quite easy to set yourself as the delegate for scroll view events, as so:
pageViewController.scrollView?.delegate = self

Resources