How to increase navigation controller back swipe gesture response area? - ios

I found that using navigation controller, the default back swipe gesture will be introduced when user swipes from the left side of the screen, which will make the app go back to the previous view.
Is there a way to start from any point on the screen other than starting from the left side of the screen?
Thanks.

Just add your own gesture recogniser and watch for gestures which velocity is greater in the x direction and going from left to right.
override func gestureRecognizerShouldBegin(gestureRecognizer: UIGestureRecognizer) -> Bool {
if (gestureRecognizer.isMemberOfClass(UIPanGestureRecognizer)){
let point:CGPoint = (gestureRecognizer as UIPanGestureRecognizer).velocityInView(self)
if (abs(point.x) > abs(point.y)){
return true
}
}
}
Handle the gesture over here:
func handlePanGestureRecognizer(gesture: UIPanGestureRecognizer){
let translation:CGPoint = gesture.translationInView(self)
if(translation.x > 0){
//its going from left to right...
//...Do stuff over here to handle the gesture, for eg. push the view from the stack
}
}

Related

Pan gesture in UITableViewCell prevents scrolling in UITableView. How to fix it?

I have got an UITableView with a custom TableViewCell. I use a pan gesture for recognizing the positions while moving my finger to the left and to the right. On basis of the finger position I change some values in the labels in this TableViewCell. This works really great. But suddenly I can not scroll the TableView up and down. I already read the reason. Swift can not work with two gesture recognizers at the same time. And I found many examples of people how have nearly the same problem. I tried many of them but I can not fix my problem. I use Swift 5. Could you please describe a bit more precise how to fix my problem? Many thanks
import UIKit
class TVCLebensmittel: UITableViewCell {
override func awakeFromNib() {
super.awakeFromNib()
let gestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(handlePan))
self.addGestureRecognizer(gestureRecognizer)
}
#IBAction func handlePan(_ gestureRecognizer: UIPanGestureRecognizer) {
if gestureRecognizer.state == .began {
let translation = gestureRecognizer.translation(in: self)
// Put my finger on the screen
} else if gestureRecognizer.state == .changed {
let translation = gestureRecognizer.translation(in: self)
// While moving my finger ...
} else if gestureRecognizer.state == .ended {
let translation = gestureRecognizer.translation(in: self)
// Lift finger
}
}
...
}
The solution is to insert the pan gesture to the tableview and not to the tableviewcell. So I can listen to the left and right pan and also the up and down movement of the tableview.
I just share my approach. link It works very well. I needed custom swipe design while perform delete. Try it. If you need more information feel free to ask. Check it if you like.
This gestureRecognizerShouldBegin let you to use scroll while using UIPanGestureRecognizer.
override func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
if (gestureRecognizer.isKind(of: UIPanGestureRecognizer.self)) {
let t = (gestureRecognizer as! UIPanGestureRecognizer).translation(in: contentView)
let verticalness = abs(t.y)
if (verticalness > 0) {
print("ignore vertical motion in the pan ...")
print("the event engine will >pass on the gesture< to the scroll view")
return false
}
}
return true
}

Swift, swipe gesture recognizer direction doesn’t work, can’t seem to get the direction

Edit: it seems the swipe gesture can only takes one direction a time now. If someone knows another way to handle multiple directions at once, I’d still appreciate information!
Eidt: I find a way to deal with multiple directions concisely in this [answer] (https://stackoverflow.com/a/46104997/9645644) It uses an array literal with forEach loop. It’s much more convenient than adding gestures and dragging actions separately from storyboard.
I’m trying to get swift swipe gestures to work, everything’s fine until I tried to detect the direction of the swipe. Below is my code. I don’t understand why this isn’t working and would appreciate help!
In a view controller’s viewDidLoad I set up and added the swipe gesture recognizer, with direction[.left, .right]. After that I implemented the handler method which needs to detect the direction of the swipe. There’s no other stuff in this view controller.
After it failed to work(no response when swipe), I added a few prints, and got the output in the title.
override func viewDidLoad() {
super.viewDidLoad()
let swipeGestureRecognizer = UISwipeGestureRecognizer(target:self, action:#selector(swipeHandler(recognizer: )))
swipeGestureRecognizer.direction = [.left, .right]
view.addGestureRecognizer(swipeGestureRecognizer)
}
#objc func swipeHandler (recognizer: UISwipeGestureRecognizer){
switch recognizer.state{
case .ended:
let direction = recognizer.direction
print(direction)
if direction == .left {print(”left”)}
else if direction == .right {print(“right”)}
else {print(print(“none”)}
defaul: break
}
No matter left or right I swipe, it always prints “none”. And the direction print always give a “UISwipeGestureRecognizerDirection(rawValue: 3)”
The direction property tells the gesture when to trigger. for example if direction == .right then the swipe will trigger only on a swipe to the right. (It does not tell you the direction detected)
You need to detect one direction at a time. I would also suggest to add a method to control each swipe direction. For example.
func setUpGestures() {
// Gesture that define a left swipe.
let swipeLeft = UISwipeGestureRecognizer(target: self, action: #selector(Scene.swipeLeft))
swipeLeft.direction = .left
view?.addGestureRecognizer(swipeLeft)
// Do the same for the rest of the directions.
}
#objc func swipeLeft() {
// Do something when the user swipe left.
}
Hope it helps!

How to swipe multi buttons with one touch

I have 5 buttons; each allows touchUpInside-action and touchDragOutside-action...as well as doubleTap-action and longPress-action via tapGestureRecognizers.
I would also like to allow user to start a swipe from any UIButton and Any addition UIButton that the swipe touches, these buttons (including first touch) perform their #IBAction func Swipe.
so a continuously swipe like so
would perform #IBAction func swipe for UIButtons 1,2,3,4 and 5.
You can try something like this:
// Create an array to hold the buttons you've swiped over
var buttonArray:NSMutableArray!
override func viewDidLoad() {
super.viewDidLoad()
// Make your view's UIPanGestureRecognizer call panGestureMethod:
// (don't use a UISwipeGestureRecognizer since it's a discrete gesture)
panGesture.addTarget(self, action: "panGestureMethod:")
}
func panGestureMethod(gesture:UIPanGestureRecognizer) {
// Initialize and empty array to hold the buttons at the
// start of the gesture
if gesture.state == UIGestureRecognizerState.Began {
buttonArray = NSMutableArray()
}
// Get the gesture's point location within its view
// (This answer assumes the gesture and the buttons are
// within the same view, ex. the gesture is attached to
// the view controller's superview and the buttons are within
// that same superview.)
let pointInView = gesture.locationInView(gesture.view)
// For each button, if the gesture is within the button and
// the button hasn't yet been added to the array, add it to the
// array. (This example uses 4 buttons instead of 9 for simplicity's
// sake
if !buttonArray.containsObject(button1) && CGRectContainsPoint(button1.frame, pointInView){
buttonArray.addObject(button1)
}
else if !buttonArray.containsObject(button2) && CGRectContainsPoint(button2.frame, pointInView){
buttonArray.addObject(button2)
}
else if !buttonArray.containsObject(button3) && CGRectContainsPoint(button3.frame, pointInView){
buttonArray.addObject(button3)
}
else if !buttonArray.containsObject(button4) && CGRectContainsPoint(button4.frame, pointInView){
buttonArray.addObject(button4)
}
// Once the gesture ends, trigger the buttons within the
// array using whatever control event would otherwise trigger
// the button's method.
if gesture.state == UIGestureRecognizerState.Ended && buttonArray.count > 0 {
for button in buttonArray {
(button as UIButton).sendActionsForControlEvents(UIControlEvents.TouchUpInside)
}
}
}
(Edit: Here are a few answers I've written in the past explaining what I meant by UISwipeGestureRecognizer being a discrete gesture: stackoverflow.com/a/27072281/2274694, stackoverflow.com/a/25253902/2274694)

Why does the Swipe Gesture Recognizer only work once?

I set up a Swipe Gesture Recognizer and I connected it to the code, so that an UIImageView rotates when the user swipes to the left.
#IBAction func swipeToLeft(sender: AnyObject) {
UIView.animateWithDuration(1.0, animations: {
self.image.transform = CGAffineTransformRotate(self.image.transform, -3.14159265358979 )
})
}
I made sure the viewDidLoad method looked like this:
image.userInteractionEnabled = true
However, the UIImageView only gets transformed only once.
You can download the demo of the project from this link. Why is that happening?
I guess, the problem is that the moment you rotated your image, the gesture recognizer, associated with it, also got rotated. You can make sure yourself:
Make a swipe right-to-left. The image will rotate. Then make a swipe left-to-right. It will rotate again.
If you want to always handle a swipe right-to-left, you can do it in a couple of ways:
If your view is always rotated by 180 degrees, the easiest way is to change the gesture recognizer orientation (#LyndseyScott was faster than me to right the code for this one, you can check her answer :) ).
Another option (especially, if there might be a situation, when you rotate your view by some arbitrary angle), is to create a UIView on top of the view you want to rotate (but not as its subview!), and to add the gesture recognizer to it instead.
Just to elaborate on FreeNickname's answer, since the gesture recognizer "rotates" along with the UIImageView, you can change your code to the following so that the swipe gesture swaps directions along with the image and you can continue swiping from right to left to trigger the animation:
#IBAction func swipeToLeft(sender: UISwipeGestureRecognizer) {
UIView.animateWithDuration(1.0, animations: {
self.doubleDot.transform = CGAffineTransformRotate(self.doubleDot.transform, -3.14159265358979 )
}, completion: {
(value: Bool) in
if sender.direction == UISwipeGestureRecognizerDirection.Left {
sender.direction = UISwipeGestureRecognizerDirection.Right
} else {
sender.direction = UISwipeGestureRecognizerDirection.Left
}
})
}

Pan Gesture - Swipe Gesture Conflict

I’m trying to create an application which duplicates the ability of Apple’s Photos app (iPhone) to zoom, pan and scroll through photographic images. (I also want to use the same controls when viewing pdfs and other documents.) I got the tap gesture to show/hide the navigation bar and the swipe gesture to scroll through the images from left to right & vice versa. Then I got the pinch gesture to zoom in and out, but when I added the pan gesture to move around within a zoomed image, the swipe gesture quit working.
I found potential solutions elsewhere on StackOverflow including the use of shouldRecognizeSimultaneouslyWithGestureRecognizer, but so far I have not been able to resolve the conflict. Any suggestions?
Here's the code:
func gestureRecognizer(UIPanGestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWithGestureRecognizer UISwipeGestureRecognizer: UIGestureRecognizer) -> Bool {
return true
}
#IBAction func handlePinch(sender: UIPinchGestureRecognizer) {
sender.view!.transform = CGAffineTransformScale(sender.view!.transform, sender.scale, sender.scale)
sender.scale = 1
}
#IBAction func handlePan(sender: UIPanGestureRecognizer) {
self.view.bringSubviewToFront(sender.view!)
var translation = sender.translationInView(self.view)
sender.view!.center = CGPointMake(sender.view!.center.x + translation.x, sender.view!.center.y + translation.y)
sender.setTranslation(CGPointZero, inView: self.view)
}
#IBAction func handleSwipeRight(sender: UISwipeGestureRecognizer) {
if (self.index == 0) {
self.index = ((photos.count) - 1);
}
else
{
self.index--;
}
// requireGestureRecognizerToFail(panGesture)
setImage()
}
You do not want shouldRecognizeSimultaneouslyWithGestureRecognizer: (which allows two gestures to happen simultaneously). That's useful if you want to, for example, simultaneously pinch and pan. But the simultaneous gestures will not help in this scenario where you are panning and swiping at the same time. (If anything, recognizing those simultaneously probably confuses the situation.)
Instead, you might want to establish precedence of swipe and pan gestures (e.g. only pan if swipe fails) with requireGestureRecognizerToFail:.
Or better, retire the swipe gesture entirely and use solely the pan gesture, which, if you're zoomed out will be an interactive gesture to navigate from one image to the next, and if zoomed in, pans the image. Interactive pan gestures generally a more satisfying UX, anyway; e.g., if swiping from one photo to the next, be able to stop mid pan gesture and go back. If you look at the Photos.app, it's actually using a pan gesture to swipe from one image to another, not a swipe gesture.
I discovered a tutorial at http://www.raywenderlich.com/76436/use-uiscrollview-scroll-zoom-content-swift that does a great job of introducing UIScrollView as a way of combining zooming, panning and paging in Swift. I recommend it for anyone trying to learn how to make these gestures work well together.
In similar case I've used another approach: extended pan gesture to support swipe:
// in handlePan()
switch recognizer.state {
struct Holder {
static var lastTranslate : CGFloat = 0
static var prevTranslate : CGFloat = 0
static var lastTime : TimeInterval = 0
static var prevTime : TimeInterval = 0
}
case .began:
Holder.lastTime = Date.timeIntervalSinceReferenceDate
Holder.lastTranslate = translation.y
Holder.prevTime = Holder.lastTime
Holder.prevTranslate = Holder.lastTranslate
//perform appropriate pan action
case .changed:
Holder.prevTime = Holder.lastTime
Holder.prevTranslate = Holder.lastTranslate
Holder.lastTime = Date.timeIntervalSinceReferenceDate
Holder.lastTranslate = translation.y
//perform appropriate pan action
case .ended ,.cancelled:
let seconds = CGFloat(Date.timeIntervalSinceReferenceDate) - CGFloat(Holder.prevTime)
var swipeVelocity : CGFloat = 0
if seconds > 0 {
swipeVelocity = (translation.y - Holder.prevTranslate)/seconds
}
var shouldSwipe : Bool = false
if Swift.abs(swipeVelocity) > velocityThreshold {
shouldSwipe = swipeVelocity < 0
}
if shouldSwipe {
// perform swipe action
} else {
// perform appropriate pan action
}
default:
print("Unsupported")
}
All you need to do is to find appropriate velocityTreshold for your swipe gesture

Resources