How to Have a Uiscrollview Scroll and Have a Gesture Recognizer

How to have a UIScrollView scroll and have a gesture recognizer?

Make a subclass of UIScrollView. Add this method in your new subclass

- (BOOL)gestureRecognizer:(UIPanGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UISwipeGestureRecognizer *)otherGestureRecognizer 
{
return YES;
}

Make your scrollView class to your new scrollview subclass.

UITapGestureRecognizer on UIScrollView

The delegate class (conforming to UIGestureRecognizerDelegate) must implement

- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
return YES;
}

This way, the tap gesture will work.

Swift - UISwipeGestureRecognizer Within UIScrollView

I think you need to implement the UIGestureRecognizerDelegate protocol

Does this achieve what you're looking for?

import UIKit

class ViewController: UIViewController, UIScrollViewDelegate, UIGestureRecognizerDelegate {

@IBOutlet weak var scrollView: UIScrollView!

override func viewDidLoad() {
super.viewDidLoad()

// these were made in a storyboard I whipped up for this example.
let vc0 = self.storyboard!.instantiateViewControllerWithIdentifier("vc0")
let vc1 = self.storyboard!.instantiateViewControllerWithIdentifier("vc1")

self.addChildViewController(vc0)
self.scrollView.addSubview(vc0.view)
vc0.didMoveToParentViewController(self)

var frame1 = vc1.view.frame
frame1.origin.x = self.view.frame.size.width
vc1.view.frame = frame1

self.addChildViewController(vc1)
self.scrollView.addSubview(vc1.view)
vc1.didMoveToParentViewController(self)

self.scrollView.contentSize = CGSizeMake(self.view.frame.size.width * 2, self.view.frame.size.height - 66)
self.scrollView.delegate = self

// Swipe Gesture Recognizers
// These can be lets because they aren't mutated and I'm using the latest Selector syntax
let swipeRight = UISwipeGestureRecognizer(target: self, action: #selector(ViewController.respondToSwipeGesture(_:)))
swipeRight.direction = UISwipeGestureRecognizerDirection.Right
let swipeLeft = UISwipeGestureRecognizer(target: self, action: #selector(ViewController.respondToSwipeGesture(_:)))
swipeLeft.direction = UISwipeGestureRecognizerDirection.Left
let swipeUp = UISwipeGestureRecognizer(target: self, action: #selector(ViewController.respondToSwipeGesture(_:)))
swipeUp.direction = UISwipeGestureRecognizerDirection.Up
let swipeDown = UISwipeGestureRecognizer(target: self, action: #selector(ViewController.respondToSwipeGesture(_:)))
swipeDown.direction = UISwipeGestureRecognizerDirection.Down

// ViewController will be the delegate for the left and right swipes
swipeRight.delegate = self
swipeLeft.delegate = self

self.view.addGestureRecognizer(swipeRight)
self.view.addGestureRecognizer(swipeLeft)
self.view.addGestureRecognizer(swipeUp)
self.view.addGestureRecognizer(swipeDown)
}

// here are those protocol methods with Swift syntax
func gestureRecognizer(gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWithGestureRecognizer otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return true
}

func gestureRecognizer(gestureRecognizer: UIGestureRecognizer, shouldReceiveTouch touch: UITouch) -> Bool {
return true
}

// Debugging - All Swipes Are Detected Now
func respondToSwipeGesture(gesture: UIGestureRecognizer) {
if let swipeGesture = gesture as? UISwipeGestureRecognizer {
switch swipeGesture.direction {
case UISwipeGestureRecognizerDirection.Right:
print("Swiped right")
case UISwipeGestureRecognizerDirection.Down:
print("Swiped down")
case UISwipeGestureRecognizerDirection.Left:
print("Swiped left")
case UISwipeGestureRecognizerDirection.Up:
print("Swiped up")
default:
break
}
}
}
}

How to make UIScrollView inside a UIScrollView act simultaneously

I solved it.

The UIScrollView's built-in UIPanGestureRecognizer must be delegated to its own UIScrollView as the error 'UIScrollView's built-in pan gesture recognizer must have its scroll view as its delegate.' said.

Instead subclass the UIScrollView and create a second UIPanGestureRecognizer which then acts simultaneously with the built-in UIPanGestureRecognizer.

Step 1 - 4:

  1. Subclass the cell's UIScrollView and create a second UIPanGestureRecognizer.

  2. Add the UIGestureRecognizerDelegate to the CellScrollView and make the shouldRecognizeSimultaneouslyWith return true.

    The second UIPanGestureRecognizer will now act simultaneously with the built-in UIPanGestureRecognizer.

class CellScrollView : UIScrollView, UIGestureRecognizerDelegate {   
var panGesture : UIPanGestureRecognizer!

override func awakeFromNib() {
panGesture = UIPanGestureRecognizer.init(target: self, action: nil)
addGestureRecognizer(panGesture)
}

func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return true
}
}

  1. In the cellForRow, make the newly created UIPanGestureRecognizer delegate to the UIScrollView_1's UIViewController.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {        
if medicines.isEmpty {
return tableView.dequeueReusableCell(withIdentifier: "emptyMedCell", for: indexPath)
}
let cell = tableView.dequeueReusableCell(withIdentifier: "random", for: indexPath)
let scrollView_1_VC = self.parent
cell.cellScrollView.panGesture.delegate = scrollView_1_VC
return cell
}

  1. Lastly, in the ScrollView_1's UIViewController let the shouldRecognizeSimultaneouslyWith return true.
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return true
}

Adding a UITapGesture to UIScrollView

Take a look at the UIGestureRecogniserDelegate functions.

You should be able to specify that both the pan and tap gestures can be recognised at the same time with the following function:

func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, 
shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return (gestureRecogniser is UIPanGestureRecogniser || gestureRecogniser is UITapGestureRecogniser) && (otherGestureRecognizer is UIPanGestureRecogniser || otherGestureRecognizer is UITapGestureRecogniser)
}

Note: Ensure your class conforms to the UIGestureRecogniserDelegate protocol and you set the gestures delegate so self.

This should work, but I am unable to fully test it right now.

UPDATE:

If you are trying to recognise a tap during an animation you will likely need to use the UIViewAnimationOptions.AllowUserInteraction option in the options of UIView.animateWithDuration. Used this other answer as a source

Recognize swipe gesture in UIView to scroll the scrollView using gesture-recognizer

EDIT

I actually just learned about a much simpler solution: just add view.addGestureRecognizer(scrollView.panGestureRecognizer) to your viewDidAppear or viewDidLoad.

What this essentially does is take the scrollView's UIPanGestureRecognizer, a gesture recognizer that recognizes finger dragging and scrolls the scrollView's content, and gives it to the base view so it can recognize finger dragging and scroll the scrollView's content too.

So you don't need all the math and scrollRectToVisible as described below. Oh well, I think it's useful and fun to learn more about how UIScrollViews work!

OLD

To programmatically make the UIScrollView "scroll", use UIScrollView's scrollRectToVisible.

See Programmatically scroll a UIScrollView for more details and code syntax.

Basically, at any point in time, a UIScrollView's content subview is offset some amount from its frame.

It helps to understand the parts to a UIScrollView (esp. the content subview), like in this picture (feel free to ignore the purple UIImageView): Sample Image

In the first image, the UIScrollView's content subview (red outline) is not offset from the UIScrollView's frame (assumed to be the same size as the visible screen) at all: both have the same top left corner at (0, 0).

In the second/middle image, the UIScrollView's content subview is now offset "up": the content subview's top left corner is at something like (0, -200) now, where (0, 0) is still the top left corner of the UIScrollView's frame.

Again, you can change the UIScrollView's content subview offset, and thus simulate a "scroll", using scrollRectToVisible, and the parameter animated: true.

Hint: to have the content scroll left (after the user swipes right-to-left) for example, you'd take the UIScrollView's current content offset's x coordinate (scrollView.contentOffset.x) and subtract the width of the UIScrollView (scrollView.frame.size.width) to it. So if the UIScrollView's current content offset (top-left corner) is (0, 0), the content offset (top-left corner) moves from (0-width, 0), and the content "moves" left.

Panning objects on a UIScrollView

I found the solution. I'd forgotten that the subviews are not placed directly into the scroll view. There is a view originally occupying the bounds of scrollview onto which the subviews are placed. The hierarchy is like this:

self.view
scroll view
UIView (fills whole scroll view)
subview1
subview2
subviewn

In my code to handle rotation, I was not resizing the UIView into which the subviews are placed. Correcting this issue solved the problem.

I'd originally tried placing the subviews without their UIView superview in between them and the scroll view, but it didn't work for some reason. Adding this extra layer solved that problem, but I forgot to handle the resizing when rotating.

So I guess the gesture recognizers did not respond because although they were visible, they were outside the bounds of their superview.

UIScrollView overrides my subview's pan gesture recognizers

Here's what works for me:

UIPanGestureRecognizer *subviewPanRecognizer = [[UIPanGestureRecognizer alloc]
initWithTarget:self action:@selector(panSubview:)];
[subview addGestureRecognizer:subviewPanRecognizer];

// play nice with subview's pan gesture
[scrollView.panGestureRecognizer
requireGestureRecognizerToFail:subviewPanRecognizer];


Related Topics



Leave a reply



Submit