I have UIScrollView with vertical scroll active. What I am trying to do is to add a swipe gesture with direction .down which will be recognized when the user cannot scroll the content anymore because it reaches the edge.
I was trying with require(toFail:) but it doesn't work properly.
let swipeDown = UISwipeGestureRecognizer(target: self, action: #selector(self.respondToSwipeGesture))
swipeDown.direction = UISwipeGestureRecognizerDirection.down
swipeDown.require(toFail: self.scrollView.panGestureRecognizer)
self.scrollView.addGestureRecognizer(swipeDown)
I have also added UIGestureRecognizerDelegate method to recognize simultaneously:
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return true
}
How to give always priority to scrolling content inside scrollView and when it is not possible anymore to detect swipe?
OK, the way I have managed that issue was simply check if the contentOffset reaches point 0.0, and if yes then disable scrolling and activate additional gesture. For my case that was enough.
if scrollView.contentOffset.y == 0.0 {
print("content on top")
// use delegate method here to manage the gestures
}
Related
I have a vertically scrollable UICollectionView with cells that have an added horizontal pan gesture.
Is there an advised way to disable the horizontal pan gesture while scrolling the UICollectionView? I ask because whenever I do scroll vertically, the slightest movements left or right also trigger the pan gesture and move the cell right or left.
I tried enabling/disabling the UICollectionView when the state of the gesture changed but it was difficult to scroll since a pan gesture would be read in almost every touch instance. I also tried doing something I found online in assessing the x/y velocity and determining whether to fulfill the pan gesture. This seemed to prevent any horizontal panning though..
And finally, I need to use pan versus swipe because I need to include conditional statements on a translation threshold when someone pans (i.e. didn't go x distance, return cell back to original position). If there is a way with swipe, I'm all ears!
Relevant code below:
func onPan(_ sender: UIPanGestureRecognizer) {
//Attempt 1
theCollectionView.isScrollEnabled = false
let cellView = sender.view!
let point = sender.translation(in: cellView)
cellView.center = CGPoint(x: parentView.center.x + point.x, y: cellView.center.y)
if sender.state == .ended {
//Attempt 1
homeCollectionView.isScrollEnabled = true
UIView.animate(withDuration: 0.2, animations: {
cellView.center = CGPoint(x: self.parentView.center.x, y: cellView.center.y)
})
}
}
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return true
}
//Attempt 2
func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
return abs((pan.velocity(in: pan.view)).x) > abs((pan.velocity(in: pan.view)).y)
}
I have the following setup:
TableView
ScrollView
ContainerView
ScrollView lies on top of TableView and completely covers it, but they are siblings.
ScrollView scrolls left and right (paging enabled) and TableView scrolls up and down.
I want to scroll the right thing depending on scroll direction.
Tried subclassing ScrollView and overriding UIGestureRecognizerDelegate:
override func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
isUserInteractionEnabled = true
if let gR = gestureRecognizer as? UIPanGestureRecognizer{
let vel = gR.velocity(in: gR.view)
let x = fabs(vel.y) < fabs(vel.x)
isUserInteractionEnabled = x
return x
}
return true
}
That approach didn't work out properly.
I also tried
scrollView.addGestureRecognizer(tableView.panGestureRecognizer)
but that didn't work either because it removes panGestureRecognizer from TableView.
I create custom push/pop interactive transition, but I can't understand how to handle this transition and table view scroll simultaneously.
For handle custom pop animation I add pan gesture
let pan = UIPanGestureRecognizer(
target: self,
action: #selector(didPan))
pan.delegate = self
view.addGestureRecognizer(pan)
Next I handle this pan
if recognizer.state == .began {
animator.interactive = true
navigationController!.popViewController(animated: true)
}
animator.handlePan(recognizer: recognizer)
Then inside animator conformed to UIPercentDrivenInteractiveTransition and UIViewControllerAnimatedTransitioning I do transform animation.
I try to pop navigation from VC_2 to VC_1 by pan gesture.
The problem is after adding pan gesture table view is not scrolling. I think it's because 2 gesture recognizers (my own and UIKit table view's recognizer). Actually I want my own pan interactively pop my VC_2 when table is scrolled up.
The only one idea was
func gestureRecognizer(
_ gestureRecognizer: UIGestureRecognizer,
shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return tableView.contentOffset.y <= 0
}
But on pop table view offset changed strange: I mean table is scroll too much as pan gesture moved (move for 20px, offset changed for 500px some kind of that).
What is the best way to handle this? You can see such animation in Inbox app from Google - pop from e-mail to list.
CustomView contains a subview, SubView, which implements a tap handler. However, for this implementation, SubView should ignore taps and let CustomView handle them.
The code below is supposed to achieve this, but setting userInteractionEnabled to false prevents taps on SubView from cascading to CustomView. Shouldn't CustomView still receive tap events if SubView has userInteractionEnabled set to false?
The Apple documentation says setting userInteractionEnabled to false causes events to get ignored, not that the view will swallow them so superviews don't receive them.
// CustomView class, which is a subclass of UIView
// Handle taps
let singleTap = UITapGestureRecognizer(target: self, action: #selector(doTap))
addGestureRecognizer(singleTap)
// Add SubView
view.insertSubview(SubView, atIndex: 0)
SubView.userInteractionEnabled = false
Updated code (doInit is called since buttonTapped is invoked on taps) but still not working:
class CustomButton : UIButton, UIGestureRecognizerDelegate {
private func doInit() {
...
// Handle taps
let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(buttonTapped))
tapRecognizer.delegate = self
addGestureRecognizer(tapRecognizer)
}
func gestureRecognizer(gestureRecognizer: UIGestureRecognizer, otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return true
}
}
SubView will still receive the taps, but it will ignore them and it will stop there. If a view has userInteractionEnabled set to false, it will block touches that are meant for parent views.
My guess is you want to add this gesture recognizer delegate:
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer,
shouldRecognizeSimultaneouslyWithGestureRecognizer otherGestureRecognizer: UIGestureRecognizer) -> Bool
This will allow your parent view to receive touches at the same time as your SubView.
Building on Laynemoseley's answer, this is the relevant bit from the Apple docs:
In iOS 6.0 and later,
default control actions prevent overlapping gesture recognizer
behavior. For example, the default action for a button is a single
tap. If you have a single tap gesture recognizer attached to a
button’s parent view, and the user taps the button, then the button’s
action method receives the touch event instead of the gesture
recognizer. This applies only to gesture recognition that overlaps the
default action for a control, which includes:
A single finger single tap on a UIButton, UISwitch, UIStepper,
UISegmentedControl, and UIPageControl. A single finger swipe on the
knob of a UISlider, in a direction parallel to the slider. A single
finger pan gesture on the knob of a UISwitch, in a direction parallel
to the switch. If you have a custom subclass of one of these controls
and you want to change the default action, attach a gesture recognizer
directly to the control instead of to the parent view. Then, the
gesture recognizer receives the touch event first. As always, be sure
to read the iOS Human Interface Guidelines to ensure that your app
offers an intuitive user experience, especially when overriding the
default behavior of a standard control.
It seems the only solution is to override the gesture recognition and allow simultaneous gestures to get recognized.
Is there a way to determine the panning location of a UIPageViewController while sliding left/right? I have been trying to accomplish this but its not working. I have a UIPageViewController added as a subview and i can slide it horizontally left/right to switch between pages however i need to determine the x,y coordinates of where I am panning on the screen.
I figured out how to do this. Basically a UIPageViewController uses UIScrollViews as its subviews. I created a loop and set all the subviews that are UIScrollViews and assigned their delegates to my ViewController.
/**
* Set the UIScrollViews that are part of the UIPageViewController to delegate to this class,
* that way we can know when the user is panning left/right
*/
-(void)initializeScrollViewDelegates
{
UIScrollView *pageScrollView;
for (UIView* view in self.pageViewController.view.subviews){
if([view isKindOfClass:[UIScrollView class]])
{
pageScrollView = (UIScrollView *)view;
pageScrollView.delegate = self;
}
}
}
- (void)scrollViewDidScroll:(UIScrollView *)scrollView{
NSLog(#"Im scrolling, yay!");
}
My personal preference is not to rely too much on the internal structure of the PageViewController because it can be changed later which will break your code, unbeknownst to you.
My solution is to use a pan gesture recogniser. Inside viewDidLoad, add the following:
let gestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(handler))
gestureRecognizer.delegate = yourDelegate
view.addGestureRecognizer(gestureRecognizer)
Inside your yourDelegate's definition, you should implement the following method to allow your gesture recogniser to process the touches
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return true
}
Now, you should be able to access the X/Y location of the user's touches:
func handler(_ sender: UIPanGestureRecognizer) {
let totalTranslation = sender.translation(in: view)
//...
}