the UIView in question is headerView:
if isShown {
stack.alpha = 1.0
self.isStackShown = true
DispatchQueue.main.async {
self.headerView.isHidden = !isShown
self.stack.addArrangedSubview(self.headerView)
// add touch gesture recognizer to stack view header
let tapFind = UIGestureRecognizer(target: self, action: #selector(self.handleFindTap))
self.headerView.addGestureRecognizer(tapFind)
}
} else {
stack.alpha = 0.0
self.isStackShown = false
DispatchQueue.main.async {
self.headerView.isHidden = isShown
self.stack.removeArrangedSubview(self.headerView)
}
}
The tap gesture recognizer is not registering any taps
self.stack is the stack view which contains the headerView
The condition for either showing or hiding the headerView is being handled in a different method and just passes the boolean self.isStackShown to this method to show/hide accordingly.
You are using a UIGestureRecognizer. UIGestureRecognizer is a polymorphic base class and should really be subclassed. UITapGestureRecognizer is the concrete subclass for handling taps. Use it instead.
let tapFind = UITapGestureRecognizer(target: self, action: #selector(self.handleFindTap))
self.headerView.addGestureRecognizer(tapFind)
Your action is never getting called because UIGestureRecognizer has no inherent information about what kind of gesture to recognize. Only a concrete subclass of it does.
Looks like you are adding multiple gesture recognizers when changing back the alpha to 1.0 and they aren't able to recognize simultaneously. Remove all gesture recognizers when hiding and removing the headerView since you don't need one anymore and add one back when adding back the headerView and it should work. Or you can also let the gesture recognizer be when hiding the headerView since it won't work anyway and check if one exists before adding another.
if isShown {
stack.alpha = 1.0
self.isStackShown = true
DispatchQueue.main.async {
self.headerView.isHidden = !isShown
self.stack.addArrangedSubview(self.headerView)
// add touch gesture recognizer to stack view header
let tapFind = UIGestureRecognizer(target: self, action: #selector(self.handleFindTap))
self.headerView.addGestureRecognizer(tapFind)
}
} else {
stack.alpha = 0.0
self.isStackShown = false
DispatchQueue.main.async {
self.headerView.isHidden = isShown
self.headerView.gestureRecognizers?.forEach({self.headerView.removeGestureRecognizer($0)})
self.stack.removeArrangedSubview(self.headerView)
}
}
or
if isShown {
stack.alpha = 1.0
self.isStackShown = true
DispatchQueue.main.async {
self.headerView.isHidden = !isShown
self.stack.addArrangedSubview(self.headerView)
if self.headerView.gestureRecognizers?.isEmpty != false{
// add touch gesture recognizer to stack view header
let tapFind = UIGestureRecognizer(target: self, action: #selector(self.handleFindTap))
self.headerView.addGestureRecognizer(tapFind)
}
}
} else {
stack.alpha = 0.0
self.isStackShown = false
DispatchQueue.main.async {
self.headerView.isHidden = isShown
self.stack.removeArrangedSubview(self.headerView)
}
}
I want to remove UIView from screen after user tap something except that view. (to visualize it for you I will upload sketch of my view)
And I want to remove blue UIView after user tap on something except buttons in this view. What should I use?
EDIT:
In blue UIView are two buttons and I want to remove that view when user tap on background image
I did what #yerpy told me to do but it isn't working.
func test(gestureRecognizer: UITapGestureRecognizer) {
print("test")
}
func setUpBackgroundImageView() {
self.view.addSubview(backgroundImageView)
backgroundImageView.widthAnchor.constraint(equalTo: view.widthAnchor).isActive = true
backgroundImageView.heightAnchor.constraint(equalTo: view.heightAnchor).isActive = true
backgroundImageView.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
backgroundImageView.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
let tap = UITapGestureRecognizer(target: self, action: #selector(test(gestureRecognizer:)))
backgroundImageView.addGestureRecognizer(tap)
tap.delegate = self
}
And I also add shouldReceiveTouch function to UIGestureRecognizerDelegate. What am I doing wrong?
Add UIGestureRecognizer to the super view :
As you said you have image view as a background.
let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(tapped(gestureRecognizer:)))
imageView.addGestureRecognizer(tapRecognizer)
tapRecognizer.delegate = self
Adding target action :
func tapped(gestureRecognizer: UITapGestureRecognizer) {
// Remove the blue view.
}
And then inside UITapGestureRecognizerDelegate :
extension ViewController : UIGestureRecognizerDelegate {
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
if touch.view!.superview!.superclass! .isSubclass(of: UIButton.self) {
return false
}
return true
}
}
Hope it helps !
EDIT
Make sure that user can touch on the view by enabling : self.view.userInteractionEnabled = true
1- Add a view below your view, let call it overlay (gray one)
2- Add your container view with all your buttons inside (green one)
3- Add a tap gesture to the overlay (drag tap to overlay view)
4- Create a #IBAction of the tap to the viewcontroller
5- Write code to hide your green view inside the #IBAction
Image
Image
you can use UITapGestureRecognizer for that view
override func viewDidLoad() {
super.viewDidLoad()
// Add "tap" press gesture recognizer
let tap = UITapGestureRecognizer(target: self, action: #selector(tapHandler))
tap.numberOfTapsRequired = 1
backgroundimage.addGestureRecognizer(tap)
}
// called by gesture recognizer
func tapHandler(gesture: UITapGestureRecognizer) {
// handle touch down and touch up events separately
if gesture.state == .began {
} else if gesture.state == .ended {
}
}
You can
subclass with that background view, and implement an inherited method -beganTouches:(there is another para but I forget what it is)
Add a UITapGestureRecognizer to that view
Add a giant button that covers the entire screen under all the buttons. Anytime the user presses on the giant button underneath the smaller buttons, do what you want it to do.
Not the most elegant but it works :P
I want to add a gesture that will only fire if a person has been pressing for a second or so. Not a tap but a long press. If I use uilongpressgesturerecognizer it keeps firing until I release my finger. How can I get around this.
Set minimumPressDuration when you create and add gesture as below:
UILongPressGestureRecognizer *longPress = [[UILongPressGestureRecognizer alloc]
initWithTarget:self action:#selector(handleLongPress:)];
longPress.minimumPressDuration = 1.0;
Write your code in UIGestureRecognizerStateEnded state as below:
-(void)handleLongPress:(UILongPressGestureRecognizer *)Gesture{
if (Gesture.state == UIGestureRecognizerStateEnded) {
//Do any thing after long press ended,which will be 1.0 second as set above
}
else if (Gesture.state == UIGestureRecognizerStateBegan){
}
}
Swift 5
Declare a UILongPressGestureRecognizer:
let longPressRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(gestureAction(gesture:)))
Set its .minimumPressDuration to 1.0 or any interval you want.
Set the recognizers .delegate to your ViewController and add it to your view using .addGestureRecognizer().
Use the following function to handle the gesture:
#objc func gestureAction(gesture: UIGestureRecognizer) {
if let longPress = gesture as? UILongPressGestureRecognizer {
if longPress.state == UIGestureRecognizer.State.began {
} else {
}
}
}
Set value of minimumPressDuration property of UILongPressGestureRecognizer.
I'm trying to create a keyboard the allows single tap, double tap, and triple tap. So I want to add a UITapGestureRecognizer() to each button in my keyboard. I know how to do this manually from the xib file (add each letter it's own gesture, which would take ages) but not quite sure how to do it in the Controller.
I wrote this for double tap in the viewDidLoad() method:
let doubleTap: UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: "doubleTapCharacter:")
doubleTap.numberOfTapsRequired = 2
for button in self.view.subviews{
button.addGestureRecognizer(doubleTap)
}
and created a doubleTapCharacter() method but it's still not working. I also want to be able to send information to the doubleTapCharacter method.
Any help would be much appreciated. Also, I'm very new to swift so if the instructions are complicated, I'd highly appreciate it if you can break it down a little.
create and add the gesture recognizers:
for button in view.subviews {
// create the gesture recognizer
let doubleTapRecognizer = UITapGestureRecognizer(target: self, action: "doubleTapCharacter:")
doubleTapRecognizer.numberOfTapsRequired = 2
// add gesture recognizer to button
button.addGestureRecognizer(doubleTapRecognizer)
}
then implement the target method:
func doubleTapCharacter(doubleTapRecognizer: UITapGestureRecognizer) {
let tappedButton = doubleTapRecognizer.view as! UIButton
print(tappedButton.titleForState(UIControlState.Normal))
}
UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(tapTapTap:)];
[self.view1 addGestureRecognizer:tapGesture];
[self.view2 addGestureRecognizer:tapGesture];
[tapGesture release];
In the above code only taps on view2 are recognized. If I comment out the third line then taps on view1 are recognized. If I'm right and you can only use a gesture recognizer once, I'm not sure if this is a bug or it just needs some more documentation.
A UIGestureRecognizer is to be used with a single view. I agree the documentation is spotty. That UIGestureRecognizer has a single view property gives it away:
view
The view the gesture recognizer is attached to. (read-only)
#property(nonatomic, readonly) UIView *view
Discussion You attach (or add) a gesture recognizer to a UIView object
using the addGestureRecognizer:
method.
I got around it by using the below.
for (UIButton *aButton in myButtons) {
UILongPressGestureRecognizer *longPress = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:#selector(handleLongPress:)];
longPress.minimumPressDuration=1.0;
[aButton addGestureRecognizer:longPress];
[longPress release];
}
Then in my handleLongPress method I just set a UIButton equal to the view of the gesture recognizer and branch what I do based upon that button
- (void)handleLongPress:(UILongPressGestureRecognizer*)gesture {
if ( gesture.state == UIGestureRecognizerStateEnded ) {
UIButton *whichButton=(UIButton *)[gesture view];
selectedButton=(UIButton *)[gesture view];
....
}
For Swift 3 in case anyone requires this:
Based on Bhavik Rathod Answer above.
func setGestureRecognizer() -> UIPanGestureRecognizer {
var panRecognizer = UIPanGestureRecognizer()
panRecognizer = UIPanGestureRecognizer (target: self, action: #selector(pan(panGesture:)))
panRecognizer.minimumNumberOfTouches = 1
panRecognizer.maximumNumberOfTouches = 1
return panRecognizer
}
///set the recognize in multiple views
view1.addGestureRecognizer(setGestureRecognizer())
view2.addGestureRecognizer(setGestureRecognizer())
No you should not attach gesture recognizers to more than one view.
There is this explicit information in the Apple documentation:
Gesture Recognizers Are Attached to a View
Every gesture recognizer is associated with one view. By contrast, a
view can have multiple gesture recognizers, because a single view
might respond to many different gestures. For a gesture recognizer to
recognize touches that occur in a particular view, you must attach the
gesture recognizer to that view.
Event Handling Guide for iOS - Gesture Recognizers Apple Developer Library
While as others mention they might work in some cases it is clearly against the documentation and could change in any future iOS version.
What you can do is add separate gesture recognisers to the views you want to monitor and they can share a common action.
We can do something Like this, it's easy and simple
1) create function as below in your controller (this function will return GestureRecognizer)
-(UITapGestureRecognizer*)setRecognizer{
UITapGestureRecognizer *gestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(openProfile)];
[gestureRecognizer setNumberOfTapsRequired:1];
return gestureRecognizer;
}
2) now set this recognizer in multiple views
[self.view1 addGestureRecognizer:[self setRecognizer]];
[self.view2 addGestureRecognizer:[self setRecognizer]];
Well if someone does not want to code for adding gesture view for multiple buttons like kwalker has answered above, and want to do it via Interface Builder this may help you.
1) You can add Long Press gesture Recognizer from Object Library like you add other objects like UIButtons and UILabels.
Initially what I ended up using was I took only one
2) Set referencing outlets to UIButton and sent actions with File's Owner.
Note: If you have multiple UIButton or any other object you will need separate gesture recognizer for each of them. For more details please refer to this question of mine.Getting wrong UIButton tag on Long press gesture recognizer
if you have fixed view I suggest you doing something like this
[self.view1 addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(tapTapTap:)]];
[self.view2 addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(tapTapTap:)]];
that way will reduce multiple different useless variable
You could create a generic extension on view to add gesture recognizers easily.
This is just an example but it could look like this
extension UIView {
func setGestureRecognizer<Gesture: UIGestureRecognizer>(of type: Gesture.Type, target: Any, actionSelector: Selector, swipeDirection: UISwipeGestureRecognizer.Direction? = nil, numOfTaps: Int = 1) {
let getRecognizer = type.init(target: target, action: actionSelector)
switch getRecognizer {
case let swipeGesture as UISwipeGestureRecognizer:
guard let direction = swipeDirection else { return }
swipeGesture.direction = direction
self.addGestureRecognizer(swipeGesture)
case let tapGesture as UITapGestureRecognizer:
tapGesture.numberOfTapsRequired = numOfTaps
self.addGestureRecognizer(tapGesture)
default:
self.addGestureRecognizer(getRecognizer)
}
}
}
To add a 2 tap recognizer on a view you would just call:
let actionSelector = #selector(actionToExecute)
view.setGestureRecognizer(of: UITapGestureRecognizer.self, target: self, actionSelector: actionSelector, numOfTaps: 2)
You could also easily add a swipe recognizer
view.setGestureRecognizer(of: UISwipeGestureRecognizer.self, target: self, actionSelector: actionSelector, swipeDirection: .down)
and so on.
Just remember that the target must be linked to the selector.
Override class by '<UIScrollViewDelegate>'
And use this method in .m class:
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer{
return YES;
}
This method will help you to enable multiple swipe on a single view..
What about re write (recreate) your GestureRecognize every time that you add a gesture recognizer pointing to the same func.
In below case it works. I am using IBOutletCollection
Swift 2:
#IBOutlet var topicView: [UIView]!
override func viewDidLoad() {
for view in self.topicView as [UIView] {
view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: "viewClicked:"))
}
}
func viewClicked(recognizer: UITapGestureRecognizer) {
print("tap")
}
I know this is an old post but I figured something similar and hopefully it's useful someone else. I simply stored my imageViews in an array and assigned it to to the same gesture recognizer in a function to set up each image view.
In my viewDidLoad():
imageViewList = [imageView, imageView2, imageView3]
setupImageViews(imageViews: imageViewList)
Function to setup image views:
func setupImageViews(imageViews: [UIImageView]) {
for imageView in imageViews {
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(imageTapped(tapGestureRecognizer:)))
imageView.isUserInteractionEnabled = true
imageView.addGestureRecognizer(tapGestureRecognizer)
//set up image to be displayed with the right aspect
imageView.autoresizingMask = [.flexibleWidth, .flexibleHeight, .flexibleBottomMargin, .flexibleRightMargin, .flexibleLeftMargin, .flexibleTopMargin]
imageView.contentMode = .scaleAspectFit // OR .scaleAspectFill
imageView.clipsToBounds = true
}
}
And in the action selector imageTapped(), you can have corresponding code for whichever image view tapped.
#objc func imageTapped(tapGestureRecognizer: UITapGestureRecognizer)
{
switch tapGestureRecognizer.view {
case imageView:
print("tapped Image View 1") //add your actions here
case imageView2:
print("tapped Image View 2") //add your actions here
case imageView3:
print("tapped Image View 3") //add your actions here
default:
print("Tap not detected")
}
_ = tapGestureRecognizer.view as! UIImageView
//additional code...
}
You can do it using this code my views which are imageviews in the xib.
- (void)viewDidLoad
{
firstIV.tag = 501;
secondIV.tag = 502;
thirdIV.tag = 503;
forthIV.tag = 504;
[self addTapGesturetoImageView: firstIV];
[self addTapGesturetoImageView: secondIV];
[self addTapGesturetoImageView: thirdIV];
[self addTapGesturetoImageView: forthIV];
}
-(void)addTapGesturetoImageView:(UIImageView*)iv
{
iv.userInteractionEnabled = YES;
UITapGestureRecognizer * textfielBGIVTapGasture = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(textfielBGIVTapped:)];
textfielBGIVTapGasture.numberOfTapsRequired = 1;
[iv addGestureRecognizer:textfielBGIVTapGasture];
}
- (void)textfielBGIVTapped:(UITapGestureRecognizer *)recognizer {
int tag = recognizer.view.tag-500;
switch (tag) {
case 1:
{
//firstIV tapped;
break;
}
case 2:
{
//secondIV tapped;
break;
}
case 3:
{
//thirdIV tapped;
break;
}
case 4:
{
//forthIV tapped;
break;
}
default: {
break;
}
}
}