Tap gesture only responds to last view - ios

I'm adding several views in the code below:
for var i=0;i<sets.count;i++ {
setView=UIView(frame: CGRectMake(0,y,400,65))
x=20
for var c=0;c<sets[i].count;c++ {
imageView=UIImageView(frame: CGRectMake(x,0,60,60))
dieFaces=types[sets[i][c]] as! NSArray
file="\(dieFaces![0]).png"
print(file)
imageView!.image=UIImage(named: file)
setView!.addSubview(imageView!)
x+=60
}
setView!.tag=i
setView!.addGestureRecognizer(tap)
scrollView.addSubview(setView!)
y+=66
}
Only the last view added is responding to the tap. What am I doing wrong?

A tap UITapGestureRecognizer can only be attached to a single view, so only the last view is responding.
You'll need to create a new gesture recognizer for each setView you're attaching it to.

UIGestureRecognizer can be added to only one view. So when you add it to another one it just removes itself from the previous view.
I can suggest two options:
Add recognizer to a superview. In this case it's UIScrollView
Or create more recognizers(one per view) and use the same target and action.

Related

Gesture recognizer callback over multiple views

I have a custom UIView that consists of 9 equally-sized subviews. It essentially looks like a tic-tac-toe board.
I've added a UIPanGestureRecognizer to this custom view. Each time the user pans over one of the subviews, I want to take an action. For example, the user could pan over the first 3 (of the 9) subviews in one gesture, and in this case I'd want to take 3 actions.
I could try to do some fancy math and figure out the frame of each subview, then figure out when the gesture crosses from one subview to another. However, I feel like there should be a more elegant way to get a callback when a gesture touches a new UIView. Does a functionality like this exist?
I was able to find a more elegant way by using hitTest, which returns the lowest subview with user interaction enabled. I defined the callback for the pan gesture recognizer as such:
var panSelected = Set<UILabel>()
#objc func handlePan(recognizer: UIPanGestureRecognizer) {
let view = recognizer.view
let loc = recognizer.location(in: view)
if let gridLabel = view?.hitTest(loc, with: nil) as? UILabel {
if !panSelected.contains(gridLabel) {
// my code here
}
}
try to use stack view to group views have same gesture, & using
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(stackViewTapped))
myStackView.addGestureRecognizer(tapGesture)
if there's different method needs to be implemented, just add another stack view.

Use right to left UIScreenEdgePanGestureRecognizer on UITableView that has section indexes

I want to add a UIScreenEdgePanGestureRecognizer to a UITableView so that I can edge swipe from the right side to go to the next screen in my controller hierarchy.
This works fine except when the table view has section indexes shown on the side. In that case, the section index area handles the touches, so I can't swipe from the edge. I would like to be able to support both the edge pan and the section index tap and vertical pan functionality.
I tried adding a view on top of the UITableView to handle the swipe, but then that handles all touches and the table view no longer gets anything.
Okay, so I tried a bunch of stuff and came up with a non-ideal but working solution.
The first part of the problem is that the section index is in its own view as a subview of UITableView. So it captures any touches or gesture recognizers that you might add to the UITableView.
The second problem is that the section index view is not part of the public API.
So my solution is to add a UIScreenEdgePanGestureRecognizer to the section index view.
The view is an instance of the private class UITableViewIndex. To get it I created a UITableView extension:
extension UITableView {
var sectionIndexView: UIView? {
for view in self.subviews {
if view.className() == "UITableViewIndex" {
return view
}
}
return nil
}
}
NOTE the above code is fragile and not future proof, because if Apple changes the class name or its location in the view hierarchy it will no longer work. In my case the edge swipe is a convenience feature, and there is a "Next" button at the top of the screen. In addition, most of my view controllers don't have a table index, so if it stopped working it would only be on a few screens. So if this solution doesn't work in the future then it's not a huge deal.
I also wrote an convenience function to get the edge gesture recognizer from any view:
extension UIView {
var screenEdgePanGestureRecognizer: UIScreenEdgePanGestureRecognizer? {
guard let gestures = self.gestureRecognizers else {
return nil
}
for gesture in gestures {
if let edgePan = gesture as? UIScreenEdgePanGestureRecognizer {
return edgePan
}
}
return nil
}
}
I add a UIScreenEdgePanGestureRecognizer to the UITableView in the viewDidLoad method of my view controller.
Finally, in viewDidAppear of my view controller I check to see if the gesture recognizer has been added to the section index view. If it hasn't, then I add it there. You need to do this at some point after the section index has been added to the table view - i.e. not in viewDidLoad. And you need to make sure you're not adding the gesture recognizer multiple times.

Single tap gesture to multiple View using storyboard

How to have single tap gesture to multiple Views using storyboard.
I drag 3 views and one tap gesture into UIView class.
Contacted three view to tap gesture and added action class handleGesture Method on tap on any one the tree view it should trigger the method action.
using story board.
But i want to do it with single tap gesture is it possible or not.
try with this, define a variable UITapGestureRecognizer with your method, after that, in a method or wherever you want, you can add this gesture to your multiple views
var recognizerMovements: UITapGestureRecognizer {
get { UITapGestureRecognizer(target: self, action: #selector(self.myActionMethod)) }
}
self.myFirstView.addGestureRecognizer(myActionMethod)
self.mySecondView.addGestureRecognizer(myActionMethod)

Recognize swipe gesture in view not in subview

I have added a subview to a View Controller's view. This subview is the view of QLPreviewController.
What I am trying to achieve is to recognize swipe gestures on the subview in the parent view, i.e. the View Controller's view. In the end, I want to be able to swipe left /right on the view to load the next document for preview.
I'm aware of hit testing and understand that by just attaching a gesture recognizer to the parent view, those will not be recognized, since the subview will be the "hit-test" view.
Now what is the best (or easiest) way to recognize those gestures?
Note: I didn't manage to attach the gesture recognizers to the subview, this doesn't seem to work.
* UPDATE *
To make this more clear - this is the code from my ViewController. vContent is just a view in my ViewController, where I add the view of the QLPreviewController:
let pvVc = QLPreviewController()
pvVc.dataSource = self
vContent.addSubview(pvVc.view)
I tried adding the swipe recognizers both to the vContent and the pvVc.view. In both cases no event was fired.
let sgrLeft: UISwipeGestureRecognizer = UISwipeGestureRecognizer(target: self, action:Selector("handleSwipe:"))
sgrLeft.direction = UISwipeGestureRecognizerDirection.Left
sgrLeft.delegate = self
On some other view the code works fine.
Any hint is appreciated!
Thx
Eau
Well, the responder chain, the unknown animal … ;-)
You can subclass the superview and override -hitTest:forEvent:.
You rarely need to call this method yourself, but you might override it to hide touch events from subviews.
Gesture Recognizers Get the First Opportunity to Recognize a Touch, so even the subview is hitTest view. the gestureRecognizer attached on superView can recognizer touch event.

touches methods not getting called on UIView placed inside a UIScrollView

I have a Custom Scroll View, subclassing UIScrollView. I have added a scroll view in my viewcontroller nib file and changed its class to CustomScrollView. Now, this custom scroll view (made from xib) is added as a subview on self.view.
In this scroll view, I have 3 text fields and 1 UIImageView(named signImageView) added from xib. On clicking UIImageView (added a TapGestureRecogniser), a UIView named signView is added on the custom scroll view. I want to allow User to sign on this view, So I have created a class Signature.m and .h, subclassing UIView and implemented the touches methods (touchesBegan, touchesMoved and touchesEnded) and initialised the signView as follows:
signView = [[Signature alloc]initWithFrame:signImageView.frame];
[customScrollView addSubview:signView];
But when I start signing on the signView, the view gets scrolled and hence the touches methods don't get called.
I have tried adding signView on self.view instead of custom scroll view, but in that case the view remains glued to a fixed position when I start scrolling. (Its frame remains fixed in this case)
Try setting canCancelContentTouches of the scrollView to NO and delaysContentTouches to YES.
EDIT:
I see that similiar question was answered here Drag & sweep with Cocoa on iPhone (the answer is exactly the same).
If the user tap-n-holds the signView (for about 0.3-0.5 seconds) then view's touchesBegan: method gets fired and all events from that moment on go to the signView until touchesEnded: is called.
If user quickly swipes trough the signView then UIScrollView takes over.
Since you already have UIView subclassed with touchesBegan: method implemented maybe you could somehow indicate to user that your app is prepared for him to sign ('green light' equivalent).
You could also use touchesEnded: to turn off this green light.
It might be better if you add signImageView as as subView of signView (instead of to customScrollView) and hide it when touchesBegan: is fired). You would add signView to customScrollview at the same place where you add signImageView in existing code instead.
With this you achieve that there is effectively only one subView on that place (for better touch-passing efficiency. And you could achieve that green light effect by un-hiding signImageView in touchesBegan:/touchesEnded:
If this app-behaviour (0.3-0.5s delay) is unacceptable then you'd also need to subclass UIScrollView. There Vignesh's method of overriding UIScrollView's touchesShouldBegin: could come to the rescue. There you could possibly detect if the touch accoured in signView and pass it to that view immediately.
When ever you add a scrollview in your view hierarchy it swallows all touches.Hence you are not getting the touches began. So to get the touches in your signon view you will have to pass the touches to signon view. This is how you do it.
We achieved this with a UIScrollView subclass that disables the pan gesture recogniser for a list of views that you provide.
class PanGestureSelectiveScrollView: UIScrollView {
var disablePanOnViews: [UIView]?
override func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
guard let disablePanOnViews = disablePanOnViews else {
return super.gestureRecognizerShouldBegin(gestureRecognizer)
}
let touchPoint = gestureRecognizer.location(in: self)
let isTouchingAnyDisablingView = disablePanOnViews.first { $0.frame.contains(touchPoint) } != nil
if gestureRecognizer === panGestureRecognizer && isTouchingAnyDisablingView {
return false
}
return true
}
}

Resources