I have a uipageviewcontroller and the pages have an area on the screen where there is a uitableview. I want the user to only be able to swipe through pages outside of that uitableview.
I can't seem to find where these gesture recognizers are hiding. I am setting them up as delegates like this:
self.view.gestureRecognizers = self.pageViewController?.gestureRecognizers
for gesture in self.view.gestureRecognizers!{
// get the good one, i discover there are 2
if(gesture is UIPanGestureRecognizer)
{
println("ispan")
// replace delegate by yours (Do not forget to implement the gesture protocol)
(gesture as! UIPanGestureRecognizer).delegate = self
}
}
I am seeing ispan in the logs so it seems to find some uipangesturerecognizer but when I override the function like this:
func gestureRecognizerShouldBegin(gestureRecognizer: UIGestureRecognizer) -> Bool {
println("gesture should begin")
var point = gestureRecognizer.locationInView(self.view)
return true
}
it doesn't print out "gesture should begin" at all... I have the class set as a UIGestureRecognizerDelegate what am I doing wrong? I'm guessing I have the wrong gesture recognizers set as delegates how can I set the correct ones as delegates?
Could something like this work?
func gestureRecognizer(gestureRecognizer: UIGestureRecognizer, shouldReceiveTouch touch: UITouch) -> Bool {
if(touch.view == <your tableView>){
return false
}else{
return true
}
}
You might need to also test which gestureRecognizer it is (the one from the pageView or the one from the tableView).
Related
I have a problem where I have a UIScrollView and a Header (UIView) inside my main View and my Header is over my UIScrollView as such:
UIView.
|
|- UIScrollView.
|
|- Header. (UIView)
I want my header to be able to detect taps on it, but I also want my scroll view to be able to scroll when I drag over my Header which right now it is not possible because my Header is over it and is blocking the scroll.
To sum up, I want my Header to detect taps but forward scrolls to my UIScrollView.
To tackle this problem I tried multiple things, and here are some of them:
Adding a UIPanGestureRecognizer to my Header so it is able to detect dragging
Adding a UITapGestureRecognizer to my Header so it is able to detect tapping
Setting isUserInteractionEnabled = false when dragging begins so the gesture can be passed to the next UIResponder which in this case is my UIScrollView
Setting isUserInteractionEnabled = true once my dragging has finished so it can again detect tapping
This is the code snippet:
override func viewLoad() {
myScreenEdgePanGestureRecognizer = UIPanGestureRecognizer(target: self, action:#selector(handlePan))
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action:#selector(handleTap(_:)))
headerView.addGestureRecognizer(myScreenEdgePanGestureRecognizer)
headerView.addGestureRecognizer(tapGestureRecognizer)
}
#objc func handlePan(_ sender: UITapGestureRecognizer){
print("dragging")
if headerView.isUserInteractionEnabled{
headerView.isUserInteractionEnabled = false
}
if sender.state == .began {
} else if sender.state == .ended {
headerView.isUserInteractionEnabled = true
}
}
#objc func handleTap(_ sender: UITapGestureRecognizer){
print("tapped")
}
At this point I see how dragging and tapping are being detected just fine, but for some reason isUserInteractionEnabled = false seems to not be changing how the view is behaving. This code is acting as isUserInteractionEnabled is always true no mater what.
Things that I have also tried besides this:
overriding the hitTest function inside UIButton
overriding touchesBegan, touchesMoved, touchesEnded methods overriding next
setting the variable to return ScrollView as the next UIResponder
setting the isExclusiveTouch method in UIButton
changing the isUserInteractionEnabled in every way possible
I was struggling with this problem too, you should try to use methods of UIGestureRecognizerDelegate which allows you to handle simultaneous gestures.
Connect your gesture recognizers delegates e.g. tapGestureRecognizer.delegate = self
Make your ViewController conform this protocol e.g.
extension YourViewController: UIGestureRecognizerDelegate {}
Implement this function:
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool { return true }
Is possible to turn off the backswipe for certain part of the screen? In particular, I was thinking of using this method:
func gestureRecognizerShouldBegin(gestureRecognizer: UIGestureRecognizer!) -> Bool {
return false;
}
I was thinking to recognize what part of the screen the user has pressed on, if it is within a certain interval, I would return true, else false. Not sure how to pickup where the user has pressed.
I assume you are talking about the functionality provided by the UINavigationController.
First your class should conform to the UIGestureRecognizerProtocol something like this:
class MyController: UIViewController, UIGestureRecognizerProtocol
Then you need to register to become the delegate for the navigation controller so probably in your viewDidLoad or viewWillAppear (depending on needs) you have this:
self.navigationController?.interactivePopGestureRecognizer?.delegate = self
Then you can use the following to check where in the view the gesture start:
func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
let location = gestureRecognizer.location(in: self.view)
if location.y > 500 {
return false
}
return true
}
Obviously this is just a test case where it ignores the gesture when it starts above 500 in the vertical and you can change that to whatever you want.
I've got a tableView inside of a pageViewController and when swiping on a cell to bring up the option to delete the cell the gesture is only recognized under certain circumstances, say you swiped very quickly and aggressively.
I imagine this is happening because it's not sure whether the swiping gesture is meant for the pageView or the tableView. Is there a way to specifically determine where the swipe gesture is happening to enable a nice smooth display of the delete button?
Theory:
Both UIPageViewController and UITableView are implemented using UIScrollView, where UIPageViewController embeds UIScrollView and UITableView is a subclass of UIScrollView
UITableView also uses a couple of UIPanGestureRecognizers to bring in all the magic. One of these is UISwipeActionPanGestureRecognizer which handles the swipe to delete actions.
This issue is caused because UIPageViewControllers UIPanGestureRecognizer wins in the conflict with the UITableViews UISwipeActionPanGestureRecognizers.
So we have to some how tell UIPageViewController to ignore gestures if UITableViews UISwipeActionPanGestureRecognizer are in action.
Luckily there is something already provided by UIGestureRecognizer.
UIGestureRecognizer's require(toFail otherGestureRecognizer: UIGestureRecognizer) creates a relation between the two gesture recognizers that will prevent the gesture's actions being called until the other gesture recognizer fails.
So all we had to do is fail UIPageViewControllers embedded UIScrollviews panGestureRecognizer when UITableViews UISwipeActionPanGestureRecognizer are triggered.
There are two ways you could achieve this.
Solution 1: Add a new gesture recognizer to table view and Mimic UISwipeActionPanGestureRecognizer. And make UIPageViewController panGesture require to fail this new gestureRecognizer
Solution 2 (A bit dirty): Make a string comparision to the UITableView's UISwipeActionPanGestureRecognizer and make UIPageViewController panGesture require to fail this new gestureRecognizer
Code:
Solution 1
A helpful utility to get UIPageViewControllers embedded UIScrollView
extension UIPageViewController {
var scrollView: UIScrollView? {
return view.subviews.first { $0 is UIScrollView } as? UIScrollView
}
}
Add the below code to UIViewController holding the UITableView and call it from viewDidLoad()
func handleSwipeDelete() {
if let pageController = parent?.parent as? UIPageViewController {
let gestureRecognizer = UIPanGestureRecognizer(target: self, action: nil)
gestureRecognizer.delaysTouchesBegan = true
gestureRecognizer.cancelsTouchesInView = false
gestureRecognizer.delegate = self
tableView.addGestureRecognizer(gestureRecognizer)
pageController.scrollView?.canCancelContentTouches = false
pageController.scrollView?.panGestureRecognizer.require(toFail: gestureRecognizer)
}
}
And finally delegate methods
func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
guard let panGesture = gestureRecognizer as? UIPanGestureRecognizer else {
return false
}
let translation = panGesture.translation(in: tableView)
// In my case I have only trailing actions, so I used below condition.
return translation.x < 0
}
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer,
shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return otherGestureRecognizer.view == tableView
}
Solution 2 (A bit dirty)
A helpful utility to get UIPageViewControllers embedded UIScrollView
extension UIPageViewController {
var scrollView: UIScrollView? {
return view.subviews.first { $0 is UIScrollView } as? UIScrollView
}
}
Add the below code to UIViewController holding the UITableView and call it from viewDidLoad()
func handleSwipeDelete() {
guard let pageController = parent as? UIPageViewController else {
return
}
pageController.scrollView?.canCancelContentTouches = false
tableView.gestureRecognizers?.forEach { recognizer in
let name = String(describing: type(of: recognizer))
guard name == "_UISwipeActionPanGestureRecognizer" else {
return
}
pageController.scrollView?
.panGestureRecognizer
.require(toFail: recognizer)
}
}
I had the same problem. I found a solution that works well.
Put this in your UIPageViewController's viewDidLoad func.
if let myView = view?.subviews.first as? UIScrollView {
myView.canCancelContentTouches = false
}
PageViewControllers have an auto-generated subview that handles the gestures. We can prevent these subviews from cancelling content touches. The tableview will be able to capture swipes for the delete button, while still interpreting swipes that fail the tableview's gesture requirements as page swipes. The delete button will show in cases where you hold and swipe or swipe "aggressively."
You can set delaysContentTouches to false on the tableView itself as well. This solution worked for my collection view's UISlider elements.
See Swift 4.0 code below:
yourTableView.delaysContentTouches = false
I have found a working solution by reassigning UIScrollView's panGestureRecognizer's delegate to my class and ignoring the original delegate when a right-to-left pan is detected. I used method swizzling for that.
Please check my sample project: https://github.com/kambala-decapitator/SwipeToDeleteInsidePageVC
In case you have a tableView in the first or the last viewController inside of your UIPageViewController maybe think of disabling the bouncing at the end of your UIPageViewController first and then implement this solution.
In case you don't know how to disable the bounce of the UIPageViewController, check out my answer here.
I have a collection view of bars that can slide up and down. Each cell in the collection view is using a UIPanGestureRecognizer to control the blue bar sliding up and down. The collection view does not scroll here.
There is an "edit mode" which disables the pan gesture controlling the bars. The hope here is that in "edit mode", the collection view can then scroll left and right.
My attempt at doing this was to disabled the pan gesture in each of the cells. I also tried using the UIGestureRecognizerDelegate methods to try to disable touches and fail the cell's pan gesture in favor of the collection view's pan gesture. It seems that the collection view's pan gesture is not forwarded to any of the cell's gesture delegate calls.
These delegate calls got me the closest (delegate for cell's pan gesture):
func gestureRecognizer(gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWithGestureRecognizer otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return true
}
func gestureRecognizer(gestureRecognizer: UIGestureRecognizer, shouldReceiveTouch touch: UITouch) -> Bool {
return !self.editMode // bar doesn't need to pan in edit mode
}
With this implemented, I could pan the collection view if I started the pan in the white space between cells. Starting a pan on a cell would not do anything, though.
EDIT: I uploaded a sample project of the issue to github.
If I understand this correctly, I think the problem could be as follows:
Your UICollectionView will listen for pan gestures. So far so good.
In KBHBarSlider you add UIPanGestureRecognizer for you sliders. They have "priority" over the underlaying UICollectionView.
This actually makes sense as your BarSliders are above the CollectionView. My best take would be to let the BarSliders add the UIPanGestureRecognizer when needed, and have them completely removed when editing.
So first try and remove the gestureRecognizer you added in VOLBarSliderCell.
Then in KBHBarSlider add something like: (and make sure your init calls addPanRecognizer() instead of setup())
func addPanRecognizer() {
let panGesture = UIPanGestureRecognizer(target: self, action: "viewDidPan:")
self.gestureRecognizers = [panGesture]
}
func removePanRecognizer() {
self.gestureRecognizers = []
}
It is now possible to add and remove the gestures on demand, do so from your edit get/set overrides in VOLBarSliderCell:
var editing: Bool = false {
didSet {
if !self.editing {
self.barSlider.addPanRecognizer()
// Removed code, to keep this example short...
} else {
self.barSlider.removePanRecognizer()
// Removed code, to keep this example short...
}
}
}
Try adding this code at VOLBarSliderCell:29:
self.panGesture.enabled = !self.editing
And remove:
func gestureRecognizer(gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWithGestureRecognizer otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return true
}
func gestureRecognizer(gestureRecognizer: UIGestureRecognizer, shouldReceiveTouch touch: UITouch) -> Bool {
if self.editing && gestureRecognizer == self.panGesture {
return false
}
return true
}
To be fair your problem was not clear to me, so I gave a try as an answer anyway since I cannot comment yet to request clarifications.
I understood you wanna scroll your UICollectionView while you are in edit mode. So that's what the code above does. If you wish to disable scrolling altogether UICollectionView while NOT editing then you can use scrollEnabled = false on the UICollectionView.
Clarify a bit better what is the problem you are trying to solve. That way you might be able to bring precise answers.
You're on the right track. Keep a reference to your leftRight gestureRecognizer. Then in shouldReceiveTouch, check if editing and the gestureRecognizer is your leftRight recognizer
func gestureRecognizer(gestureRecognizer: UIGestureRecognizer, shouldReceiveTouch touch: UITouch) -> Bool {
if(self.editing && gestureRecognizer != self.leftRightRecognizer){
return 0;
}
return 1;
}
My app has a table view (with a scroll-into of course) and this view slides on and off with a gesture recognizer (like on the Facebook app).
If I use a button to slide [the table view onto the screen], it works fine but when I use a gesture recognizer, the table view can't be scrolled anymore.
Here is the code of gesture recognizer with the problem:
[self.view addGestureRecognizer:self.slidingViewController.panGesture];
Somebody have an idea?
Your gesture is probably preventing the scroll view gesture from working because by default only 1 gesture can be recognising at a time. Try adding yourself as the delegate of your gesture and implementing:
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
return YES;
}
self.slidingViewController.panGesture.delegate = self;
also, add <UIGestureRecognizerDelegate> to the list of protocols you implement
I have used UIPangesture in my UItableview and to avoid this gesture I have used below delegate,
//This method helped me stopped up/down pangesture of UITableviewCell and allow only vertical scroll
override func gestureRecognizerShouldBegin(gestureRecognizer: UIGestureRecognizer) -> Bool {
if let panGestureRecognizer = gestureRecognizer as? UIPanGestureRecognizer {
let translation = panGestureRecognizer.translationInView(superview)
if fabs(translation.x) > fabs(translation.y) {
return true
}
return false
}
return false
}
Here is the swift version:
func gestureRecognizer(gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWithGestureRecognizer otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return true
}
I had same issue of defining long press gesture on table view and not being able to scroll table when I long press on it.
Fixed by:
1- adding
UIGestureRecognizerDelegate
2- adding
gesture.delegate = self (after you defined the long press gesture)
3- adding this function:
func gestureRecognizer(gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWithGestureRecognizer otherGestureRecognizer: UIGestureRecognizer) -> Bool {return true}
If I get it right the view that you're adding the gesture recognizer to is the table view. By default the UIScrollView (and implicitly UITableView) class uses the pan gesture recognizer for the scroll and your gesture recognizer interferes with that. If you use another view as a container for the table view and you're adding the pan gesture recognizer to it should work.