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")
}
}
Related
I have a scrollView with the delegate method set.
private let scrollView: UIScrollView = {
let scrollView = UIScrollView(frame: .zero).usingAutoLayout()
scrollView.isPagingEnabled = true
scrollView.showsVerticalScrollIndicator = false
scrollView.showsHorizontalScrollIndicator = false
return scrollView
}()
I'm trying to making only scroll to the left to mimic a "delete cell", like in the phone book. I don't want the user to be able to scroll to the right. I have this, which kinda works:
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if scrollView.contentOffset.x < 0 {
scrollView.contentOffset.x = 0
}
The problem is that if I swipe fast the contentOffSet is set to positive values, which makes the scrollView scroll in the opposite direction. This usually happens after I finish the swipe gesture. This makes me think it has to do with the bounce, but even setting it to false, it still occurs.
Was able to come up with a solution:
extension SwipeableCollectionViewCell: UIScrollViewDelegate {
func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
if scrollView.panGestureRecognizer.translation(in: scrollView.superview).x > 0 {
self.scrollDirection = .rigth
} else {
self.scrollDirection = .left
}
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if self.scrollDirection == .rigth {
scrollView.contentOffset.x = 0
}
}
}
private enum ScrollDirection {
case rigth
case left
}
I have rotated my collectionView upside down so it would load the content at the top instead of at the bottom and I added this:
func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
let currentOffset = scrollView.contentOffset.y
let maxOffset = scrollView.contentSize.height - scrollView.frame.size.height
if maxOffset - currentOffset <= 10{
fetch()
}
}
but nothing gets fetch even though on the console it's printing every time I go reach the bottom. fetch() works, calling it doesn't
I did the same thing using this and it works fine.
override func scrollViewDidScroll(_ scrollView: UIScrollView) {
if scrollView.contentOffset.y > scrollView.contentSize.height - scrollView.frame.height + 50 /* 50 its just example for your like */ {
DispatchQueue.main.async {
fetch()
}
}
}
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 {
/// ...
}
}
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
}
}
in my app i am using small menu at the bottom of uiwebview. and i want to make like when user scroll downside that view must be hide. and when scrolling upside view must be unhide.
Like Safari.
this is what i tried
func scrollViewDidEndDecelerating(scrollView: UIScrollView) {
print("Going Down")
viewbottom.hidden = true
viewHieght.constant = 0
}
func scrollViewDidEndDragging(scrollView: UIScrollView, willDecelerate decelerate: Bool) {
print("Going Up")
viewbottom.hidden = false
viewHieght.constant = 45
}
but by using this code its continuously showing up and down.
Use scroll view pan gesture recogniser to determine the direction.
func scrollViewWillBeginDragging(scrollView: UIScrollView) {
if scrollView.panGestureRecognizer.translationInView(scrollView.superview).y > 0 {
// scrolls down
} else {
// scrolls up
}
}
keep tracking the scrollView.contentOffset.y value and compare the last with the current value like so:
in your mainView add: var lastScrollOffset = CGFloat()
compare the last value with the current one in func scrollViewDidScroll(scrollView: UIScrollView)
func scrollViewDidScroll(scrollView: UIScrollView) {
let scrollOffset = scrollView.contentOffset.y
if lastScrollOffset < scrollOffset{
//scrolling down
}else if lastScrollOffset > scrollOffset {
//scrolling up
}else{
//going crazy
}
lastScrollOffset = scrollOffset
}