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

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
}

Related

Custom UICollectionView Cell Swipe Animation

Does anyone know the best way how to implement this animation or library with similar functionality? I assume that it can be done via affine transforms. But maybe somebody knows some examples.
on tableView, you can use editActionForRowAt function(you can read more about it here https://developer.apple.com/documentation/uikit/uitableviewdelegate/1614956-tableview) but because you are using a collection view you have to do it by yourself.
Add every cell a UIPanGestureRecognizer and make the animation according to the to the pan gesture.
like this:
func setupSwipeGesture() {
swipeGesture = UIPanGestureRecognizer(target: self, action:#selector(swiped(_:)))
swipeGesture.delegate = self
self.addGestureRecognizer(swipeGesture)
}
func swiped(_ gestureRecognizer: UIPanGestureRecognizer) {
let xDistance:CGFloat = gestureRecognizer.translation(in: self).x
// do your animation
}

Change UISlider value with swipe gesture swift

I have a UIView with a swipe gesture .
let swipeUpGesture = UISwipeGestureRecognizer(target: self, action: #selector(NextStepCaptureVC.handleSwipeUp(gesture:)))
swipeUpGesture.direction = .up
view.addGestureRecognizer(swipeUpGesture)
func handleSwipeUp(gesture: UISwipeGestureRecognizer) {
print("Swipe Up")
heightSlider.setValue(20, animated: true)
}
When I try to change the value it works but the value jump from 0 to 20. I want the value to change continuously while swiping. How can I do it?
Judging from your code, it looks like you are trying to make 'panning up and down' on the screen translate to the UISlider value changing.
As already mentioned by others, first thing is to change your UISwipeGestureRecognizer to a UIPanGestureRecognizer
let pan = UIPanGestureRecognizer(target: self, action: #selector(pan(gesture:)))
view.addGestureRecognizer(pan)
Then in your pan function, you need to update the slider value based on how much the user has panned.
func pan(gesture: UIPanGestureRecognizer) {
// The amount of movement up/down since last change in slider
let yTranslation = gesture.translation(in: gesture.view).y
// The slide distance needed to equal one value in the slider
let tolerance: CGFloat = 5
if abs(yTranslation) >= tolerance {
let newValue = heightSlider.value + Float(yTranslation / tolerance)
heightSlider.setValue(newValue, animated: true)
// Reset the overall translation within the view
gesture.setTranslation(.zero, in: gesture.view)
}
}
Simply adjust the tolerance variable to make the user swipe more/less in order to adjust the slider value.
You don't want a UISwipeGestureRecognizer, you want a UIPanGestureRecognizer. A swipe is a one-time gesture.
Apple's documentation says "A swipe is a discrete gesture, and thus the associated action message is sent only once per gesture."
You set up a main action from your gesture recognizer to your code (You can use interface builder for that)
#IBAction func handlePan(recognizer:UIPanGestureRecognizer) {
if recognizer.state == UIGestureRecognizerState.Began {
} else if recognizer.state == UIGestureRecognizerState.Ended {
} else if recognizer.state == UIGestureRecognizerState.Changed {
}
}
Good luck! =]

Programmatically Starting Gesture Recognizer In Swift?

I am trying to determine if there is a means of programmatically setting a gesture recognizer state, to force it to begin prior to it actually detecting user input.
For example, I am adding a pan gesture recognizer to an image when a long press is detected, like so;
let longPressRecognizer = UILongPressGestureRecognizer(target: self, action: "longPressed:")
myImage.addGestureRecognizer(longPressRecognizer)
func longPressed(sender: UILongPressGestureRecognizer) {
let mainWidth = UIScreen.mainScreen().bounds.width
let mainHeight = UIScreen.mainScreen().bounds.height
let myView: UIView(frame: CGRect(x: 0, y: 0, width: mainWidth, height: mainHeight)
let gestureRecognizer = UIPanGestureRecognizer(target: self, action: "handlePan:")
myView.addGestureRecognizer(gestureRecognizer)
self.view.addSubview(myView)
}
In the handlePan() function, I'm able to determine when the pan starts and ends;
func handlePan(gesture: UIPanGestureRecognizer) {
if gesture!.state == UIGestureRecognizerState.Began {
print("Started pan")
}
if gesture!.state == UIGestureRecognizerState.Ended {
print("Ended pan")
}
}
My issue is that, to detect when the gesture started, the user has to (1) long press on the image, (2) release their finger, (3) press and hold and start panning. Ideally, I'd like to have the user (1) long press on the image, (2) start panning.
To accomplish this, I'm imagining I need to figure out a way to "trick" things into believing that the pan gesture already began.
note: In practicality, there is more complexity than what's presented here, which is why I need to add a subview with the pan gesture, rather than just adding the pan gesture to the image directly.
What you want to do is add both gesture recognizes up front, set their delegates to your class, allow them to recognize simultaneously (using the below method), and only use the data from the pan when the long press has successfully been recognized.
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer,
shouldRecognizeSimultaneouslyWithGestureRecognizer otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return true
}

Draggable UIButton/Elements in Swift?

this is my first question! I was just wondering, in Swift (specifically Swift 2, although that may go without saying!), how you create a button that the user can drag around. So for example, if it is a UIButton, the user can tap and hold it, and when they move their finger, the UIButton moves with it, and when they release it, it remains in the position that the user left it. Potentially there could be a snapping system but this is unimportant for now.
I've searched StackOverflow and found some quite interesting things, however it's all for Objective-C, and although Swift is pretty similar in some respects, I can't figure out in the slightest as to how to implement this in Swift.
It would be massively appreciated for a project that I am working on!
Thank you very much!
You can implement UIPanGestureRecognizer on your UIButton.
Wherever you create your button (viewDidLoad if using outlets):
let pan = UIPanGestureRecognizer(target: self, action: "panButton:")
button.addGestureRecognizer(pan)
This creates a new pan gesture recognizer and adds it to the button. Now, you'll want to implement the pan's action. First, you need to store the center of the button to be able to reset it when you finish panning. Add this as a view controller property:
var buttonCenter = CGPointZero
Then you implement the pan action. Note that you can use gesture recognizer states to determine when the pan starts and ends:
func panButton(pan: UIPanGestureRecognizer) {
if pan.state == .Began {
buttonCenter = button.center // store old button center
} else if pan.state == .Ended || pan.state == .Failed || pan.state == .Cancelled {
button.center = buttonCenter // restore button center
} else {
let location = pan.locationInView(view) // get pan location
button.center = location // set button to where finger is
}
}
Swift 4 & 5 Version of accepted answer:
var buttonCenter: CGPoint = .zero
viewDidLoad() {
super.viewDidLoad()
let pan = UIPanGestureRecognizer(target: self, action: #selector(YourViewController.panButton(pan:)))
button.addGestureRecognizer(pan)
}
#objc func panButton(pan: UIPanGestureRecognizer) {
if pan.state == .began {
buttonCenter = button.center // store old button center
} else if pan.state == .ended || pan.state == .failed || pan.state == .cancelled {
button.center = buttonCenter // restore button center
} else {
let location = pan.location(in: view) // get pan location
button.center = location // set button to where finger is
}
}
Basically, you want to implement a touch gesture recognizer and set the button's center to the center of your press when you tap/move said button.
Here's how you'll want to do that: https://stackoverflow.com/a/31487087/5700898
Also, really cool idea!

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