Custom Interactive Transition With Table View - ios

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.

Related

Avoid Passing Touch Event to the Background View Behind

View
StackView (vertical)
Purple (View)
Green (View)
Orange (View)
View has a tap gesture recognizer. It prints "this" plus a random int.
If I tap Purple, Green or Orange, it still prints "this".
How can I disable taps on the colored boxes? I tried turning off the 'User Interaction Enabled' setting on the boxes.
Because the stack view is a sub-view of View and not added over the top (like a presented view controller), its going to receive the touch events like you're experiencing. Your view controller needs to adopt the UIGestureRecognizerDelegate protocol, then you need to set the gesture's delegate to the view controller (most likely you just need to set it to "self") and then implement the following function:
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
//the below will return false if the touch's view is not the gesture recognizer's view
return touch.view == gestureRecognizer.view
}
The code example was referenced from: UITapGestureRecognizer tap on self.view but ignore subviews

Change dragging one view to another in same gesture

I'm trying to reproduce the buttom sheet in Apple's iOS 10 Maps app. Most of it is working. I've been looking at this SO post and Pulley on GitHub, but none of them solves my issue.
When the sheets is fully opened, it is possible to scroll the content of the sheet as a UITableView, but when the user tries to scroll down (where the UITableView's contentOffset would be negative), the gesture is dragging in the sheet instead of the UITableView. The gesture seamlessly changes from dragging the UITableView to dragging the sheet.
It is possiple disable the scrolling of the UITableView in the gesture delegate's shouldRecognizeSimultaneouslyWith, but this is only called when a gesture begins.
I can't control the panGestureRecognizer of the UITableView, so I can't just capture the gesture and determine what view it should move.
How can I change what UIGestureRecognizer should recognize touches, in the middle of a gesture?
Try
override func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
if gestureRecognizer == myCustomPanGesture {
return self.tableView.contentOffset == 0
}
return true
}

Recognize swipe gesture in UIScrollView only when scrolling content reach the edge

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
}

Disabling user interaction for subview interferes with event handling for superview?

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.

UIPageViewController detecting pan gestures

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)
//...
}

Resources