I have one main view and 4 subviews of the mainview they all have their UITapGestureRecognizer, when I tap on one subview how can it be triggered both views. Example below,
if I tap to subview 1 desired log would be:
subview1 is clicked
MainView is clicked
My Code
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
let mainGesture = UITapGestureRecognizer(target: self, action: #selector(mainGestureActivated))
self.view.addGestureRecognizer(mainGesture)
let subGesture = UITapGestureRecognizer(target: self, action: #selector(subViewGestureActivated))
self.subview1.addGestureRecognizer(subGesture)
}
#objc func mainGestureActivated(){
print("MainView Clicked")
}
#objc func subViewGestureActivated(){
print("Subview Clicked")
}
it prints only subview clicked! Is it possible to trigger both gestureRecognizers since main is encapsulating other.
First you should conform to UIGestureRecognizerDelegate in your VC, and then implement the delegate func of shouldRecognizeSimultaneouslyWith. Inside the function, you should detect if the gestureRecognizer, and the otherGestureRecognizer are the wants you want, and if so, you should allow them to work simultaneously,
Conform to delegate, and Declare gesture recognizers outside of viewDidLoad (because you need to access them in the delegate method later.)
var mainGestureRecognizer = UITapGestureRecognizer()
var subGestureRecognizer = UITapGestureRecognizer()
Initialize your recognizers, and set your VC as their delegate:
mainGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(mainGestureActivated))
subGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(subViewGestureActivated))
mainGestureRecognizer.delegate = self
subGestureRecognizer.delegate = self
Implement the delegate function mentioned above to allow simultaneous recognition for subView and mainView:
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
if gestureRecognizer == subGestureRecognizer && otherGestureRecognizer == mainGestureRecognizer {
return true
}
return false
}
And if you want it to work for 4 different subviews, then you should check with else if statements inside the delegate method.
Related
I have an app that manipulates view with gestures. View is connected to pan, pinch and rotate gestures. If I start interacting with the view with two fingers all gestures are working simultaneously (expected behavior). But if you start with one finger pan, pinch and rotate gestures do not work. None of the methods in the delegate are called when I am trying to start pinch or rotate while panning.
shouldRecognizeSimultaneouslyWith
is always true.
Expected behavior,
one finger pan on the view
add second finger and start pinch/rotate interaction
(similar to instagram stories editor)
From a clean project I added the following:
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
view.addGestureRecognizer({
let gesture = UIPanGestureRecognizer(target: self, action: #selector(onPan))
gesture.delegate = self
return gesture
}())
view.addGestureRecognizer({
let gesture = UIPinchGestureRecognizer(target: self, action: #selector(onPinch))
gesture.delegate = self
return gesture
}())
view.addGestureRecognizer({
let gesture = UIRotationGestureRecognizer(target: self, action: #selector(onRotate))
gesture.delegate = self
return gesture
}())
}
#objc private func onPan(_ sender: UIPanGestureRecognizer) {
print("Did pan to \(sender.translation(in: sender.view))")
}
#objc private func onPinch(_ sender: UIPinchGestureRecognizer) {
print("Did pinch to \(sender.scale)")
}
#objc private func onRotate(_ sender: UIRotationGestureRecognizer) {
print("Did rotate to: \(sender.rotation)")
}
}
extension ViewController: UIGestureRecognizerDelegate {
func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool { true }
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool { true }
}
All 3 methods report changes after the scenario you described:
I started with single touch drag gesture
I pressed another finger and started rotating gesture and pinching gesture
Does already this example not work for you? What is the behavior you are experiencing and what is expected behavior?
I have a built a UIView class called SetView. In its initializer I create multiple subviews and later in ViewController I want to determine which subView has been pressed. in my viewDidLoad method I iterate through all the subviews add them to a class array of UIView called mySubViews and it my getIndex method, I am trying to retrieve the value which is always retrieved as nil. I suppose that it is my main view that is passed as a sender rather than particular subviews but I don't know how to pass specific subviews since #selector does not accept argument. I would appreciate any suggestions on how I could determine which subview was pressed to update features of a given subview.
override func viewDidLoad() {
super.viewDidLoad()
for view in setView.subviews {
mySubViews.append(view)
let gestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(getIndex(_:)))
gestureRecognizer.delegate = self
view.addGestureRecognizer(gestureRecognizer)
}
}
#objc func getIndex(_ sender:UIView) {
print(mySubViews.index(of: sender))
}
The sender should be the gesture recognizer. Then give your views a tag and set the same tag for your gesture recognizer. Then you can get the view with viewWithTag.
Or with your array it could be like
override func viewDidLoad() {
super.viewDidLoad()
var index = 0
for view in setView.subviews {
mySubViews.append(view)
let gestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(getIndex(_:)))
gestureRecognizer.tag = index
gestureRecognizer.delegate = self
view.addGestureRecognizer(gestureRecognizer)
index += 1
}
}
#objc func getIndex(_ sender: UITapGestureRecognizer) {
print(mySubViews[sender.tag])
}
The sender in your target method is a gesture recognizer. The fact that it is only interpreted as UIView will always return nil in your call.
Try the following:
#objc func getIndex(_ sender: UIGestureRecognizer) {
print(mySubViews.index(of: sender.view))
}
Still I would prefer you would use a single gesture recognizer on the super view. Then you can check the hit view by checking if the gesture recognizer was within the view bounds:
#objc func getIndex(_ sender: UIGestureRecognizer) {
let allViewsAtGestureLocation = mySubViews.filter { $0.bounds.contains(sender.location(in: $0)) }
let firstHitView = mySubViews.first(where: { $0.bounds.contains(sender.location(in: $0)) })
}
I assume you would need the second one. From it you can again find an index.
Maybe try something like this:
for view in setView.subviews {
let gestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(getIndex(_:)))
gestureRecognizer.delegate = self
view.addGestureRecognizer(gestureRecognizer)
mySubViews.addSubview(view)
}
Don't use append if you're trying to add it in, and add it after you assign the gesture.
I have ViewController with UIGestureRecognizer implemented this way:
// Extension for GestureRecognizer
extension UIViewController {
func addGestureRecognizer() {
let singleTap = UITapGestureRecognizer(target: self, action: #selector(self.handleTap(_:)))
singleTap.cancelsTouchesInView = false
singleTap.numberOfTapsRequired = 1
self.view.addGestureRecognizer(singleTap)
}
#objc func handleTap(_ recognizer: UITapGestureRecognizer) {
self.view.endEditing(false)
}
}
As you can see, cancelsTouchesInView is set to false by default. It works fine with main view with searchBar and textFields. Keyboard is being dismissed and touches are translated to view's objects - buttons and so on.
But I have added subview that works like AlertView with several UITextFields and UIButtons. And here is a problem. When subview is presented and I'm tapping on any button of this subview - gestureRecognizer reacts and keyboard disappears. But nothing else happens. So I have to tap the second time on the button to make it pressed.
I have tried to change target of UITapGestureRecognizer from self to self.mySubViewName, like this:
func addGestureRecognizer() {
let singleTap = UITapGestureRecognizer(target: self.addingItemView, action: #selector(self.handleTap(_:)))
singleTap.cancelsTouchesInView = false
singleTap.numberOfTapsRequired = 1
self.view.addGestureRecognizer(singleTap)
}
#objc func handleTap(_ recognizer: UITapGestureRecognizer) {
self.view.endEditing(false)
}
This way GestureRecognizer started to work with subview buttons properly, but is not able to work with main view. I started getting "Unrecognized selector was sent" error when trying to dismiss keyboard for searchbar for example. I have tried to implement separate function addGestureRecognizerForSubView, but still getting "Unrecognized selector was sent..." error.
Also tried to add this:
extension MenuTableViewController: UIGestureRecognizerDelegate {
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
return touch.view == self.addingItemView
}
}
but nothing changes.
I've checked a lot of answers here, but was not able to find my case. Can anybody advise? I can share my ViewController class, but it's too heavy and looks ugly with programmatically added subview :)
Add a subview behind the alert and add the gesture to it not to self.view
let singleTap = UITapGestureRecognizer(target: self.otherView, action: #selector(self.handleTap(_:)))
singleTap.cancelsTouchesInView = false
singleTap.numberOfTapsRequired = 1
self.otherView.addGestureRecognizer(singleTap)
Edit: your suggest to add it to a subview is right
let singleTap = UITapGestureRecognizer(target: self.addingItemView, action: #selector(self.handleTap(_:)))
singleTap.cancelsTouchesInView = false
singleTap.numberOfTapsRequired = 1
self.view.addGestureRecognizer(singleTap)
But you still add the gesture to self.view here
self.view.addGestureRecognizer(singleTap)
change to
self.addingItemView.addGestureRecognizer(singleTap)
I want to customize my long press gesture recognizer in the following ways:
1) When I hold down on an object for 0.5 seconds, the object darkens, and
2) When I continue to hold down on the object for another second (total of 1.5 seconds), some action happens (e.g. the object disappears).
Essentially, by holding down on an object for a minimum of 1.5 seconds, two actions happened at two separate times. I also have a tap gesture recognizer, which might affect things.
The answer from #nathan is essentially fine but a detail is missing you need to implement the UIGestureRecognizerDelegate to allow both gestures works simultaneously, so this is my code
class ViewController: UIViewController, UIGestureRecognizerDelegate{
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
//this for .5 time
let firstGesture = UILongPressGestureRecognizer(target: self, action: #selector(firstMethod))
//this for 1.5
let secondGesture = UILongPressGestureRecognizer(target: self, action: #selector(secondMethod))
secondGesture.minimumPressDuration = 1.5
firstGesture.delegate = self
secondGesture.delegate = self
self.view.addGestureRecognizer(firstGesture)
self.view.addGestureRecognizer(secondGesture)
}
func firstMethod() {
debugPrint("short")
}
func secondMethod() {
debugPrint("long")
}
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool{
return true
}
}
Hope this help
See Reinier's solution as it's the correct one. This one adds a delay to satisfy require(toFail:)
You can set the timing using the property minimumPressDuration (in seconds, default is 0.5)
let quickActionPress = UILongPressGestureRecognizer(target: self, action: #selector(ViewController.zeroFiveSecondPress(gesture:))) // 0.5 seconds by default
let laterActionPress = UILongPressGestureRecognizer(target: self, action: #selector(ViewController.oneFiveSecondPress(gesture:)))
laterActionPress.minimumPressDuration = 1.5
someView.addGestureRecognizer(quickActionPress)
someView.addGestureRecognizer(laterActionPress)
// If 1.5 detected, only execute that one
quickActionPress.require(toFail: laterActionPress)
#objc func zeroFiveSecondPress(gesture: UIGestureRecognizer) {
// Do something
print("0.5 press")
}
#objc func oneFiveSecondPress(gesture: UIGestureRecognizer) {
zeroFiveSecondPress(gesture: gesture)
// Do something else
print("1.5 press")
}
I want to add a gesture recognizer to one of my views to detect taps,
here is my code:
class DateTimeContainer: UIView, UIGestureRecognizerDelegate {
override func awakeFromNib() {
let gesture = UITapGestureRecognizer(target: self, action: #selector(self.onTap))
gesture.delegate = self
self.addGestureRecognizer(gesture)
}
func onTap(_ gestureRecognizer: UITapGestureRecognizer) {
openDatePicker()
}
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
if touch.view?.tag != self.datePickerTag && !self.isDatePickerOpen() {
return true
}
return false
}
}
When I tap on my view the code enters into the gestureRecognizer shouldReceive method and enters the condition for which it returns true.
But the onTap method is never called, can someone tell me why?
EDIT
Adding self.isUserInteractionEnabled = true I managed to get it working.
But it had a strange behaviour: it was like it received the tap only in the main view and not in subviews.
So to make it simple I solved by adding a button inside my view and by using:
self.button?.addTarget(self, action: #selector(onTap), for: .touchUpInside)
You may have forgotten to do this:
self.isUserInteractionEnabled = true
Also, typically the UIViewController subclass is usually the target and contains the method used for the tap gesture.
You don't need to set a delegate for a tap gesture (usually)
You can You Gesture
let recognizer = UITapGestureRecognizer(target: self,action:#selector(self.handleTap(recognizer:)))
userImage.isUserInteractionEnabled = true
recognizer.delegate = self as? UIGestureRecognizerDelegate
userImage.addGestureRecognizer(recognizer)
And method call selector
func handleTap(recognizer:UITapGestureRecognizer) {
// do your coding here on method
}
Try Writing the codes
let gesture = UITapGestureRecognizer(target: self, action: #selector(self.onTap))
gesture.delegate = self
self.addGestureRecognizer(gesture)
inside init block not in awakeFromNib()
I noticed that you are adding your gesture on a UIView. As such, at that ViewController where you have added this ContainerView, inside the viewDidLoad method, add this line of code.
override func viewDidLoad() {
super.viewDidLoad()
self.YourContainerView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(YourCurrentViewController.openDatePicker())))
self.YourContainerView.isUserInteractionEnabled = true
}
Note. Do not forget to add isUserInteractionEnabled to true.