Allow vertical scrolling of UIScrollView with Charts on iOS - ios

I have a LineChartView from the Charts framework within a UIScrollView. Values on the chart can be highlighted by panning around to see more details about the specific data points.
These are my goals for the overall behaviour of panning within my view controller:
The scroll view should scroll if the user pans anywhere on the view controller. Even on the chart view, providing that the direction is vertical.
The chart should only highlight values when the user scrolls on it horizontally and the scroll view should not react in this case.
Currently, the scroll view ignores scrolling when interacting with the chart completely and the chart reacts to vertical panning, which it should not, so while I'm close to a solution, it's not quite there.
Here is my current implementation...
- (void)viewDidLoad {
[super viewDidLoad];
NSArray<UIGestureRecognizer *> *gestureRecognizers = self.lineChartView.gestureRecognizers;
for (UIGestureRecognizer *gestureRecognizer in gestureRecognizers) {
if ([gestureRecognizer isKindOfClass:[UIPanGestureRecognizer class]]) {
gestureRecognizer.delegate = self;
self.lineChartViewPanGestureRecognizer = (UIPanGestureRecognizer *)gestureRecognizer;
}
}
UIPanGestureRecognizer *lineChartViewPanGestureRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(handleLineChartViewPanGestureRecognizer:)];
[self.lineChartView addGestureRecognizer:lineChartViewPanGestureRecognizer];
}
- (void)handleLineChartViewPanGestureRecognizer:(UIPanGestureRecognizer*)sender {
if (sender.state == UIGestureRecognizerStateEnded) {
[self.lineChartView highlightValues:NULL];
}
}
#pragma mark - UIGestureRecognizerDelegate
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {
if (gestureRecognizer == self.lineChartViewPanGestureRecognizer && otherGestureRecognizer == self.scrollView.panGestureRecognizer) {
UIPanGestureRecognizer* panGestureRecognizer = (UIPanGestureRecognizer *)otherGestureRecognizer;
CGPoint velocity = [panGestureRecognizer velocityInView:self.view];
return fabs(velocity.y) > fabs(velocity.x);
}
return YES;
}
Anyone have any suggestions as to how I can keep the scroll view scrolling when the user pans vertically on the chart (which should itself ignore such panning)?

Related

Detect 2 Touch Pan vs 1 Touch Pan on UIScrollView?

I have a UIScrollView with a UIImageView inside. I have the UIScrollView set to scroll only with a 2 finger pan gesture, that works fine. Here's the code I'm using for that:
for (UIGestureRecognizer *gestureRecognizer in _imgScrollView.gestureRecognizers) {
if ([gestureRecognizer isKindOfClass:[UIPanGestureRecognizer class]]) {
UIPanGestureRecognizer *panGR = (UIPanGestureRecognizer *) gestureRecognizer;
panGR.minimumNumberOfTouches = 2;
panGR.maximumNumberOfTouches = 2;
}
}
I would like to capture 1 finger panning with touch events(touchesBegan:, touchesMoved:, touchesEnded:) and pass these events to another UIView that is directly under the UIScrollView.
The reason I want to do this is because the other UIView is a drawing view where the user can draw lines. This drawing view does not use a pan gesture recognizer to draw lines, just simple touchesBegan, touchesMoved, and touchesEnded methods. The user should be able to scroll around the image with 2 fingers and draw with 1 finger, this is the end goal functionality.
I realize that other questions have asked something similar to this but I haven't been able to find one with a conclusive answer on how to handle 2 finger pans and 1 finger pans separately. Here's my viewDidLoad method where I implement the scrollView:
-(void)viewDidLoad
{
[super viewDidLoad];
_imgScrollView.delegate = self;
imgView = [[UIImageView alloc] init];
[_imgScrollView addSubview:imgView];
_imgScrollView.showsVerticalScrollIndicator = NO;
_imgScrollView.showsHorizontalScrollIndicator = NO;
for (UIGestureRecognizer *gestureRecognizer in _imgScrollView.gestureRecognizers) {
if ([gestureRecognizer isKindOfClass:[UIPanGestureRecognizer class]]) {
UIPanGestureRecognizer *panGR = (UIPanGestureRecognizer *) gestureRecognizer;
panGR.minimumNumberOfTouches = 2;
panGR.maximumNumberOfTouches = 2;
}
}
}
Thanks in advance.

Make collection view default pan gesture disabled when two finger swipe gesture executed

Please help me with the below scenario.
Self.view (Swipe Gesture added here)
UicollectionView Object in subView (Default pan,swipe gestures are there)
Want to disable scrollview scrolling/failCollectionViewPan gesture for 2 finger swipe event
Means collection view will not scroll if 2 finger swipe executed
Another approach can be to disable Collection view scrolling while 2 fingers used. Over here, I want not to swipe collection view on 2 finger swipe.
I have implemented this code, but its slowing down scrolling.
[self.collectionView.panGestureRecognizer shouldBeRequiredToFailByGestureRecognizer:_swipeL];
Then I have implemented below codes
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer{
return YES;
}
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer{
return YES;
}
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch{
return YES;
}
Now both, Swipe and Scroll are working together.
Then, I have tried in a below manner, but still not got fixed.
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer{
if([gestureRecognizer isKindOfClass:[UISwipeGestureRecognizer class]]){
if(gestureRecognizer.numberOfTouches==2){
if( [[otherGestureRecognizer.view class] isSubclassOfClass:[UITableViewCell class]] ||
[NSStringFromClass([otherGestureRecognizer.view class]) isEqualToString:#"UITableViewCellScrollView"] ||
[NSStringFromClass([otherGestureRecognizer.view class]) isEqualToString:#"UITableViewWrapperView"] || [NSStringFromClass([otherGestureRecognizer.view class]) isEqualToString:#"UIScrollViewPanGestureRecognizer"] || [NSStringFromClass([otherGestureRecognizer.view class]) isEqualToString:#"UIScrollViewPagingSwipeGestureRecognizer"])
{
NSLog(#"Allow&Disable %#", [otherGestureRecognizer description]);
[gestureRecognizer requireGestureRecognizerToFail:otherGestureRecognizer];
return NO;
}
}
}
return YES;
}
Also I have implemented below 2 methods to fix it but fails.
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRequireFailureOfGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer NS_AVAILABLE_IOS(7_0);
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldBeRequiredToFailByGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer NS_AVAILABLE_IOS(7_0);
Its is able to fix for ScrollView with such ways, but the same ways are not working for UICollectionView. Because of, colection view's default pan gesture, it cannot be able to modified. While tring this way, app crashed.
If you want to detect pan gesture, try something like this. But you need to recognise direction of pan gesture:
- (void)viewDidLoad
{
[super viewDidLoad];
UIPanGestureRecognizer *panGesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(panGesture:)];
panGesture.delegate = self;
[self.yourCollectionView addGestureRecognizer:panGesture];
}
- (void)panGesture:(UIPanGestureRecognizer *)gesture;
{
if (gesture.numberOfTouches == 2) {
NSLog(#"Two fingers pan gesture");
}
}
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
if ([gestureRecognizer isKindOfClass:[UIPanGestureRecognizer class]]
&& gestureRecognizer.numberOfTouches == 2) {
return NO;
}
return YES;
}
Your problem with UISwipeGestureRecognizer is, that it fires later than UIPanGesture, so in the shouldRecognizeSimultaneouslyWithGestureRecognizer delegate call, swipe gesture is always otherGestureRecognizer, and UIPanGesture is always gestureRecognizer, and in this function you can only disable otherGestureRecoginzer...
UPDATE:
Another solution: use another UIPanGestureRecognizer to disable scroll pan gesture:
- (void)viewDidLoad
{
[super viewDidLoad];
UISwipeGestureRecognizer *swipeGesture = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:#selector(swipeGesture:)];
swipeGesture.direction = UISwipeGestureRecognizerDirectionDown;
swipeGesture.delegate = self;
swipeGesture.numberOfTouchesRequired = 2;
UIPanGestureRecognizer *panGesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:nil];
panGesture.delegate = self;
[self.tableView addGestureRecognizer:panGesture];
[self.tableView addGestureRecognizer:swipeGesture];
}
- (void)swipeGesture:(UIPanGestureRecognizer *)gesture;
{
NSLog(#"Two fingers swipe gesture");
}
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
if ([gestureRecognizer isKindOfClass:[UIPanGestureRecognizer class]]
&& gestureRecognizer.numberOfTouches == 2) {
return NO;
}
return YES;
}
This answer might be late, but I did have a similar problem today and couldn't find a solution, until I figured something out myself. It's quite simple, actually.
Swift 5
We create a gesture
let gesture = UIPanGestureRecognizer(target: self, action: #selector(functionCall))
gesture.minimumNumberOfTouches = 2
gesture.maximumNumberOfTouches = 2
We then assign that gesture to the collectionView, effectively overwriting the scroll gesture on it.
collectionView.addGestureRecognizer(gesture) // simply overwrites the 2 finger gesture on the collectionView
It now is possible to scroll the collectionView with 1, 3 or more fingers, but the 2 finger pan gesture is blocked.

Customizing UIPageViewController gesture recognizers Scroll behavior

I want to implement -(BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch gesture delegate in pageviewcontroller.m file but i can't get any gesture as:
NSArray *temp = self.gestureRecognizers;
NSLog(#"count %i",temp.count); // this logs count 0
NSArray *temp = self.view.gestureRecognizers;
NSLog(#"count %i",temp.count); // this also logs count 0
for (UIGestureRecognizer *gR in temp) {
gR.delegate = self;
}
in above code self is pointing towards pageviewcontroller.
therefore i can't assign delegate to pageviewcontroller gestures.
Edited Part:
OK I got it, I am not getting any gesture object because of uipageviewscroll style.
But I have a problem I need to disable pageviewcontroller pan gesture and need to scroll the pageviewcontroller from two buttons, like if user try to pan, and it's starting point is inside my uibuttons frame then pageviewcontroller should scroll otherwise not.
I am using transitionStyle UIPageViewControllerTransitionStyleScroll.
Any solution for this...
Thanks in advance
After trying so many hacks, I finally got solution.
What i did, first got the pageviewcontroller scorll view in a property
for (UIView *view in mypageviewcontroller.view.subviews) {
if([view isKindOfClass:[UIScrollView class]])
{
pagescrollview= (UIScrollView *)view;
}
}
Then assigned pan gesture to pageviewcontroller scorllview and gesture delegate.
UIPanGestureRecognizer* g1 = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(gesture)];
[g1 setDelegate:self];
[pagescrollview addGestureRecognizer:g1];
Then in gesture delegate , i checked whether the gesture starts from my desire points, if starts from a location that should scroll pageviewcontroller then this delegate method should return no to enable original pagescorllview gesture to receive pan gesture.
-(BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch
{
if ([gestureRecognizer isKindOfClass:[UIPanGestureRecognizer class]]) {
CGPoint touchPoint = [touch locationInView:self.view];
if(floor(NSFoundationVersionNumber)<=NSFoundationVersionNumber_iOS_6_1)
touchPoint.y -=44;
else
touchPoint.y -=64;
PGNavigationController *nav =ViewControllerArray[VisiblePageindex];
PageContentVC *pagecontentvc = ((PageContentVC *) nav.visibleViewController);
if (touchPoint.y > pagecontentvc.leftpanalbtn.frame.origin.y && (pagecontentvc.leftpanalbtn.frame.size.height+pagecontentvc.leftpanalbtn.frame.origin.y )>touchPoint.y && touchPoint.x >pagecontentvc.leftpanalbtn.frame.origin.x
&& touchPoint.x<(pagecontentvc.leftpanalbtn.frame.origin.x+pagecontentvc.leftpanalbtn.frame.size.width)) {
return NO;
}
else if (touchPoint.y > pagecontentvc.rightpanalbtn.frame.origin.y && (pagecontentvc.rightpanalbtn.frame.size.height+pagecontentvc.rightpanalbtn.frame.origin.y )>touchPoint.y && touchPoint.x >pagecontentvc.rightpanalbtn.frame.origin.x
&& touchPoint.x<(pagecontentvc.rightpanalbtn.frame.origin.x+pagecontentvc.rightpanalbtn.frame.size.width))
{
return NO;
}
if( touchPoint.y>282 && touchPoint.x>118 &&touchPoint.y<282+75 && touchPoint.x < 118+85)
{
return NO;
}
// else if()
}
return YES;
}
hope it help someone else.

Drag and drop within UIScrollView

I am trying to design a view which contains a list of items to be ordered and placed into an array. The items are dynamically generated and thus cold be an amount that goes off the bottom of the screen.
For this reason my first view is a UIScrollview which takes the whole devices screen
Nested under this I have a label, explaining what the list is for and how to interact with it and then a UITableView with drag & drop with the delegate methods from http://b2cloud.com.au/how-to-guides/reordering-a-uitableviewcell-from-any-touch-point
The problem I am facing is that while the script works great when there are 1 or two rows, when the contentsize of the UIscrollview is larger than the screen it seems to take priority over the drag & drop leading to unpredictable behaviour.
Is there any way to make clicks on the table take priority to only edit the cells and allow the user to scroll by interacting elsewhere on the view?
Thanks
UPDATE
Based on the comment below I managed to get:
- (void)viewDidLoad
{
[super viewDidLoad];
//
//
//
UIPanGestureRecognizer *tapGesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(wasPanned:)];
[self.view addGestureRecognizer:tapGesture];
}
-(void)wasPanned:(UIPanGestureRecognizer *)gesture
{
CGPoint point = [gesture locationInView:scrollView];
UIView *isTable = [scrollView hitTest:point withEvent:nil];
if (gesture.state == UIGestureRecognizerStateBegan)
{
if([[[isTable class] description] isEqualToString:#"UITableViewCellReorderControl"])
{
NSLog(#"Dragged from within table");
[scrollView setScrollEnabled: NO];
}
else
{
[scrollView setScrollEnabled:YES];
}
}
else{
[scrollView setScrollEnabled:YES];
}
}
Now, when the scrollview is not long enough to begin scrolling it NSLogs the message fine
However when the scrollview is longer it only recognises the gesture if the scrollview hasn't began scrolling first
Update
I now have the Console recognising touches in the table 100% of the time and disable scrolling. Disabling the scrolling however also stops the drag & drop functionality. Does anyone know why?
Extra code:
tapGesture.delegate = self;
#pragma mark UIGestureRecognizerDelegate
-(BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
return YES;
}
So my final (but not perfect) solution was to do this:
.h
<UIGestureRecognizerDelegate>
.m (viewDidLoad)
UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(wasTapped:)];
[self.view addGestureRecognizer:tapGesture];
tapGesture.delegate = self;
.m
-(void)wasTapped:(UIPanGestureRecognizer *)gesture
{
CGPoint point = [gesture locationInView:scrollView];
UIView *isTable = [scrollView hitTest:point withEvent:nil];
if (gesture.state == UIGestureRecognizerStateBegan)
{
if([[[isTable class] description] isEqualToString:#"UITableViewCellReorderControl"])
{
NSLog(#"Dragged from within table");
[scrollView setScrollEnabled: NO];
}
else
{
[scrollView setScrollEnabled:YES];
}
}
else{
[scrollView setScrollEnabled:YES];
}
}
A tap gesture works better than a Pan gesture because a pan gesture seems to recognise press&hold, drag, stop, drag as two different gestures. The feature now works as it should but if you so much as move your finger before the animation for a cell moving has started it will scroll. You also have to wait for the view (can I call it overscroll?) animation to completely stop and the scrollbars disappear before it will grab cells.
I'd appreciate it if anyone can improve on it :)

UIPanGestureRecognizer conflict with scrollview

I'm trying to add a pan gesture recognizer to a view containing a scrollview, but I guess I've problems with priorities.
My global UIView has a UIPanGestureRecognizer set like this:
_bottomPanGestureRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(bottomPanGestureDetected:)];
_bottomPanGestureRecognizer.minimumNumberOfTouches = 2;
_bottomPanGestureRecognizer.maximumNumberOfTouches = 2;
_bottomPanGestureRecognizer.delaysTouchesBegan = NO;
_bottomPanGestureRecognizer.delaysTouchesEnded = NO;
I want to recognize this gesture to display another view from the bottom with some sort of pinch down-to-up.
The problem is that the scrollview is recognizing its own pan gesture before mine.
So I tried to delay it thanks to:
[_scrollView.panGestureRecognizer requireGestureRecognizerToFail:_bottomPanGestureRecognizer];
And it's working, the scrollview event is fired after my two finger down to up recognizer, but the problem is now when I only use one finger to scroll in the scrollview, the scroll works after a small delay.
I would like to have no delay for this event, is this possible? Any idea welcomed!
Cheers.
Cyril
In case it isn't solved yet, i solved the problem for me.
I added a UIPanGestureRecognizer to a UIScrollView to detect two finger pan gestures and the default UIScrollView behaviour (scrolling to something) still workes.
So what i did is to add the UIPanGestureReconizer to the UIScrollView:
UIPanGestureRecognizer *pangestureRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(displayReloadIndicator:)];
pangestureRecognizer.minimumNumberOfTouches = 2;
pangestureRecognizer.delegate = self;
[self.scrollView addGestureRecognizer:pangestureRecognizer];
[pangestureRecognizer release];
After this i added the code:
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {
return YES;
}
After this, i implemented the pan gesture recognizers action method.
- (void) displayReloadIndicator:(UIPanGestureRecognizer*) panGestureRecognizer {
UIGestureRecognizerState gestureRecognizerState = gestureRecognizer.state;
CGPoint translation = [gestureRecognizer translationInView:self.scv_bibgesamt];
if (gestureRecognizerState == UIGestureRecognizerStateBegan) {
// create a UIView with all the Pull Refresh Headers and add to UIScrollView
// This is really much lines of code, but its simply creating a UIView (later you'll find a myRefreshHeaderView, which is my base view) and add UIElements e.g. UIActivityIndicatorView, a UILabel and a UIImageView on it
// In iOS 6 you will also have the possibility to add a UIRefreshControl to your UIScrollView
}
else if (gestureRecognizerState == UIGestureRecognizerStateEnded
|| gestureRecognizerState == UIGestureRecognizerStateCancelled) {
if (translation.y >= _myRefreshHeaderView.frame.size.height + 12) { // _myRefreshHeaderView is my baseview
//so the UIScrollView has been dragged down with two fingers over a specific point and have been release now, so we can refresh the content on the UIScrollView
[self refreshContent];
//animatly display the refresh view as the top content of the UIScrollView
[self.scrollView setContentOffset:CGPointMake(0, myRefreshHeaderView.frame.size.height) animated:YES];
}
else {
//the UIScrollView has not been dragged over a specific point so don't do anything (just scroll back to origin)
[self.scrollView setContentOffset:CGPointMake(0, 0) animated:YES];
//remove the view (because it's no longer needed)
[_myRefreshHeaderView removeFromSuperview];
}
}
UPDATE:
In case you may wish to integrate the swipe back functionality from your navigationcontroller, you should integrate following code:
- (void) viewDidLoad {
[super viewDidLoad];
if ([self.navigationController respondsToSelector:#selector(interactivePopGestureRecognizer)]) {
self.navigationController.interactivePopGestureRecognizer.enabled = YES;
self.navigationController.interactivePopGestureRecognizer.delegate = nil;
}
//setup view controller
}
and
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch {
if (gestureRecognizer == _panGestureRecognizer
&& [self.navigationController.viewControllers count] > 1) {
CGPoint point = [touch locationInView:self.view.window];
if (point.x < 20
|| point.x > self.view.window.frame.size.width - 20) {
return NO;
}
}
return YES;
}
Implement panRecognizer delegate to enable simultaneously recognize UIScrollView UIGestureRecognizer
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
if (_panRecognizer == gestureRecognizer) {
if ([otherGestureRecognizer.view isKindOfClass:UIScrollView.class]) {
UIScrollView *scrollView = (UIScrollView *)otherGestureRecognizer.view;
if (scrollView.contentOffset.x == 0) {
return YES;
}
}
}
return NO;
}
SWIFT 4
If using a scrollView you can use below, it is only a panGestureRecognizer when a scrollView is dragging at the very top:
translation.y > 0 means you are moving from top to bottom and
locationInScrollView.y < 500.0 means you end dragging at 500 or less (you can customize this) to prevent that the refresh is done in middle or bottom of the scroll.
func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
let translation = scrollView.panGestureRecognizer.translation(in: scrollView.superview)
let locationInScrollView = scrollView.panGestureRecognizer.location(in: scrollView)
if translation.y > 0 && locationInScrollView.y < 500.0 {
print("scrollView refresh: Y: \(locationInScrollView.y)")
setupUI()
}
}

Resources