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.
Related
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.
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 have a view that added some UIButton.
given a CGPoint how can I get the button laid on.
sample code
let location = tapRecognizer.location(in: view)
let tapView = view.hitTest(location, with: nil)
the code below won't work how can I get the UIbutton form tapView
if let button = tapView as? UIButton {
print("text")
}
help appreciated
While adding button to the view add target to the button and assign a different tag to each button and perform action according to the tag
For the this purpose you can use the gesture delegate like
Example:-
func addGestureOnContentView () -> Void {
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(ExploreViewController.hideTableView))
tapGesture.delegate = self
self.view.addGestureRecognizer(tapGesture)
}
Now use gesture delegate like :-
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
if gestureRecognizer is UITapGestureRecognizer {
if (touch.view?.isDescendant(of: UIButton))! {
return false //Perform your task in this block
}
}
return true
}
I have this Setup in my Storyboard.
In my first ViewController Scene I have a MapView from MapBox. In there I have put a TextField (AddressTextField). On that TextField when touching the view, i'm running self.addressTextField.resignFirstResponder(), but after that neither the mapview, nor any other element in there or in the Embedded Segues react on a touch or click. Probably this is because I didn't completely understand the system of the First Responder. I'm thankful for every help.
Edit 1:
I think I know what's going on now, but I don't know how to fix it. When I add the Gesture Recognizer to the View (or to the mapView, that doesn't matter), the other UIViews and the MapView do not recognize my Tap-Gestures anymore. When I am not adding the Recognizer everything works fine. It seems as if the Gesture Recognizer is recognizing every tap I make on either the UIViews or the MapView and therefore other gestures are not recognized.
Edit 2:
I just added a print() to dismissKeyboard(). As soon as any Touch Event gets recognized on the MapView or the other UIViews, dismissKeyboard() gets called. So I think my thought of Edit 1 was correct. Does anyone know how I can solve this, so that it's not only dismissKeyboard() that gets called ?
Some Code:
func dismissKeyboard(){
self.addressTextField.resignFirstResponder()
}
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
dismissKeyboard()
return true
}
//Class (only partially)
class ViewController: UIViewController, MGLMapViewDelegate, CLLocationManagerDelegate, UITextFieldDelegate {
override func viewDidLoad(){
mapView.delegate = self
addressTextField.delegate = self
let tap: UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(dismissKeyboard))
self.mapView.addGestureRecognizer(tap)
}
}
Others are just #IBActions linked to the Buttons, or other elements.
try this:
func dismissKeyboard(){
view.endEditing(true)
}
hope it helps!
After I knew the real issue I was able to solve the problem. I declared a var keyboardEnabled. Then I added these lines to my class.
class ViewController: UIViewController, UIGestureRecognizerDelegate {
var keyboardEnabled = false
override func viewDidLoad(){
super.viewDidLoad()
//Looks for single tap
let tap: UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(dismissKeyboard))
self.mapView.addGestureRecognizer(tap)
}
/* Setting keyboardEnabled */
//Editing Target did end
#IBAction func editingTargetDidEnd(_ sender: Any) {
keyboardEnabled = false
}
//Editing TextField Started
#IBAction func editingAdressBegin(_ sender: Any) {
keyboardEnabled = true
}
//Call this function when the tap is recognized.
func dismissKeyboard() {
self.mapView.endEditing(true)
keyboardEnabled = false
}
//Implementing the delegate method, so that I can add a statement
//decide when the gesture should be recognized or not
//Delegate Method of UITapGestureRecognizer
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
return keyboardEnabled
}
}
With this solution keyboardEnabled takes care of deciding wether my UIGestureRecognizer should react or not. If the Recognizer doesn't react, the Gesture is simply passed on to the UIViews or other Elements that are in my MapView.
Thanks for all your answers!