What is the difference between Pan and Swipe in iOS?
By definition, a swipe gesture is necessarily also a pan gesture -- both involve translational movement of touch points. The difference is in the recognizer semantics: a pan recognizer looks for the beginning of translational movement and continues to report movement in any direction over time, while a swipe recognizer makes an instantaneous decision as to whether the user's touches moved linearly in the required direction.
By default, no two recognizers will recognize the same gesture, so there's a conflict between pan and swipe. Most likely, your pan recognizer "wins" the conflict because its gesture is simpler / more general: A swipe is a pan but a pan may not be a swipe, so the pan recognizes first and excludes other recognizers.
You should be able to resolve this conflict using the delegate method gestureRecognizer:shouldRecognizeSimultaneouslyWithGestureRecognizer:
, or perhaps without delegation by making the pan recognizer depend on the swipe recognizer with requireGestureRecognizerToFail:
.
With the conflict resolved, you should be able to simulate a one-finger swipe by quickly dragging the mouse. (Though as the mouse is more precise than your finger, it's a bit more finicky than doing the real thing on a device.) Two-finger pan/swipe can be done by holding the Option & Shift keys.
Pan (not swipe) between view controllers
I'm not sure UINavigationController is the correct container controller to provide what you need. You might find that UIPageViewController is a better choice, I think it will provide the natural swipe gesture you are looking for out of the box, although it's quite an opaque class with some quirks of its own.
To go the custom route (either with UINavigationController or the more flexible/customisable UIViewController custom presentation) you are on the right track with UIViewControllerAnimatedTransitioning and the other (numerous) associated protocols. In case you are in a hurry, that route will take you quite a long time to implement, so maybe a simple UIPageViewController implementation will have to do for the moment.
How to use pan gesture and a swipe gesture alternatively on the same view?
A swipe and a pan gesture can not be recognized at the same time because a swipe gesture is just a special type of pan that has to be faster, short lived, and in one direction.
What you should do is setup the pan gestures to require the swipe gesture to fail before they can start. You will need to set each of the UISwipeGestureRecognizer
to be a requireGestureRecognizerToFail
on the UIPanGestureRecognizer
override func viewDidLoad() {
super.viewDidLoad()
let swipeGestures = setupSwipeGestures()
setupPanGestures(swipeGestures: swipeGestures)
}
private func setupSwipeGestures() -> [UISwipeGestureRecognizer] {
let swipeUp = UISwipeGestureRecognizer(target: self, action: Selector("handleSwipes:"))
let swipeDown = UISwipeGestureRecognizer(target: self, action: Selector("handleSwipes:"))
swipeUp.direction = .Up
swipeDown.direction = .Down
circleView.addGestureRecognizer(swipeUp)
circleView.addGestureRecognizer(swipeDown)
return [swipeUp, swipeDown]
}
private func setupPanGestures(swipeGestures: [UISwipeGestureRecognizer]) {
let panGesture = UIPanGestureRecognizer(target: self, action: Selector("handleThePan:"))
for swipeGeature in swipeGestures {
panGesture.requireGestureRecognizerToFail(swipeGesture)
}
circleView.addGestureRecognizer(panGesture)
}
Swift 3
override func viewDidLoad() {
super.viewDidLoad()
let swipeGestures = setupSwipeGestures()
setupPanGestures(swipeGestures: swipeGestures)
}
private func setupSwipeGestures() -> [UISwipeGestureRecognizer] {
let swipeUp = UISwipeGestureRecognizer(target: self, action: #selector(self.handleSwipes))
let swipeDown = UISwipeGestureRecognizer(target: self, action: #selector(self.handleSwipes))
swipeUp.direction = .up
swipeDown.direction = .down
circleView.addGestureRecognizer(swipeUp)
circleView.addGestureRecognizer(swipeDown)
return [swipeUp, swipeDown]
}
private func setupPanGestures(swipeGestures: [UISwipeGestureRecognizer]) {
let panGesture = UIPanGestureRecognizer.init(target: self, action:#selector(self.handleThePanUp))
for swipeGesture in swipeGestures {
panGesture.require(toFail: swipeGesture)
}
circleView.addGestureRecognizer(panGesture)
}
swipe and pan gesture overlap
Check out the UIGestureRecognizerDelegate
protocol here:
https://developer.apple.com/library/ios/documentation/uikit/reference/UIGestureRecognizerDelegate_Protocol/Reference/Reference.html
Specifically, the
gestureRecognizer:shouldRecognizeSimultaneouslyWithGestureRecognizer:
method might be useful. If you simply return YES
from this method, both gestures can be recognized at the same time, so you can respond properly to both.
Can a pan gesture be used for moving UIWindows?
Yes, you can.
UIWindow
is subclass of UIView
and you can add PanGesture
normally. To move window, change frame of UIApplication.sharedApplication.delegate.window
, it will work fine.
Create a new project and replace AppDelegate.m
file with my code below. You can move window.
#import "AppDelegate.h"
@interface AppDelegate ()
@property (nonatomic, strong) UIPanGestureRecognizer* panGesture;
@property (nonatomic, assign) CGPoint lastPoint;
@end
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.
self.panGesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(handlePanGesture:)];
[self.window addGestureRecognizer:_panGesture];
_needUpdate = YES;
return YES;
}
- (void)handlePanGesture:(UIPanGestureRecognizer *)panGesture {
CGPoint point = [panGesture locationInView:self.window];
CGPoint center = self.window.center;
if (CGPointEqualToPoint(_lastPoint, CGPointZero)) {
_lastPoint = point;
}
center.x += point.x - _lastPoint.x;
center.y += point.y - _lastPoint.y;
self.window.frame = [UIScreen mainScreen].bounds;
self.window.center = center;
if (panGesture.state == UIGestureRecognizerStateEnded) {
_lastPoint = CGPointZero;
}
}
@end
How to have a UISwipeGestureRecognizer AND UIPanGestureRecognizer work on the same view
You're going to want to set one of the two UIGestureRecognizer
's delegates to an object that makes sense (likely self
) then listen, and return YES
for this method:
- (BOOL) gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer
shouldRecognizeSimultaneouslyWithGestureRecognizer:
(UIGestureRecognizer *)otherGestureRecognizer {
return YES;
}
This method is called when recognition of a gesture by either gestureRecognizer
or otherGestureRecognizer
would block the other gesture recognizer from recognizing its gesture. Note that returning YES
is guaranteed to allow simultaneous recognition; returning NO
, on the other hand, is not guaranteed to prevent simultaneous recognition because the other gesture recognizer's delegate may return YES
.
How to tell whether a UIPageViewController page turn is due to a swipe/pan or a tap
The question is really how to determine which of several gesture recognizers is responsible for something happening.
The key thing to understand is that any gesture recognizer can have more than one target/action installed. The UIPageViewController sets its own target/actions internally, but that doesn't preclude others being added.
When setting up a sub-class of UIPageViewController after it has been loaded, or instantiated, add the following code (or Swift code equivalent):
for (UIGestureRecognizer *gr in self.gestureRecognizers) {
if ([gr class] == [UIPanGestureRecognizer class])
[gr addTarget:self action:@selector(panGestureOccurred)];
else if ([gr class] == [UITapGestureRecognizer class])
[gr addTarget:self action:@selector(tapGestureOccurred)];
}
Then, add the two target/action methods to the sub-class:
- (void)tapGestureOccurred
{
// Set a flag here to rely on later after the page turn
}
- (void)panGestureOccurred
{
// Reset a flag here to rely on later after the page turn
}
This obviously generalizes for any other types of gestures, but currently only the pan and tap gestures appear to be supported by a UIPageViewController
.
panGestureOccurred
will be called multiple times while the user is touching the screen and moving a finger/stylus. The tapGestureOccurred
is only called once.
But only one of the two methods will be called, depending on which gesture recognizer the page view controller in its infinite wisdom decides wins. This all seems to work much more robustly than the too-low level touchesBegan
and touchesMoved
idea posited in the original question.
Related Topics
Calayer with Transparent Hole in It
Document Directory Path of Xcode Device Simulator
How to Implement Lazy Loading of Images in Table View Using Swift
How to Change Wkwebview or Uiwebview Default Font
Cannot Put a Google Maps Gmsmapview in a Subview of Main Main View
Uicollectionview Adding Image to a Cell
What Should Xcode 6 Gitignore File Include
How to Check If a File Exists in the Documents Directory in Swift
Immutable/Mutable Collections in Swift
Control Cursor Position in Uitextfield
How to Style Uitextview to Like Rounded Rect Text Field
Xcode 8 Build Crash on iOS 9.2 and Below
Simple and Clean Way to Convert JSON String to Object in Swift
How to Load an Uiimage into a Swiftui Image Asynchronously
How to Programmatically Enable Guided Access (Kiosk Mode) on an Iphone