AVPlayer UITapGestureRecognizer not working - ios

I have this code for my AVPlayerViewController.
UITapGestureRecognizer *tap=[[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(tapAvPlayer)];
[self.avPlayerViewController.view addGestureRecognizer:tap];
but this is not working.. :S, I tried setting
[self.avPlayerViewController.view setUserInteractionEnabled:YES];
still no good..
The only working solution is to use UIGestureRecognizer and implement it's shouldReceiveTouch delegate and check if the av player is touched.. but the issue is, we wan't to capture the "tap release" event.. because if the av player view is just touched, it immediately executes the code and that is not what we wanted...
Please help us with this issue..
Thanks!

The correct place for gesture handling on an AVPlayerViewController is inside the controller's contentOverlayView .
contentOverlayView is a read-only property of AVPlayerViewController. It's a view that shows up over the video, but under the controller. Just the perfect place for touch handling. You can add subviews or gesture handlers to it at load time.
The following code gives your video controller touch support in two ways, to demonstrate both the gesture recognize and UIButton approaches.
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([segue.identifier isEqualToString:#"mySegue"])
{
// Set things up when we're about to go the the video
AVPlayerViewController *avpvc = segue.destinationViewController;
AVPlayer *p = nil;
p = [AVPlayer playerWithURL:[NSURL URLWithString:#"https://my-video"]];
avpvc.player = p;
// Method 1: Add a gesture recognizer to the view
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(myAction:)];
avpvc.contentOverlayView.gestureRecognizers = #[tap];
// Method 2: Add a button to the view
UIButton *cov = [UIButton buttonWithType:UIButtonTypeCustom];
[cov addTarget:self action:#selector(myAction:) forControlEvents:UIControlEventTouchUpInside];
cov.frame = self.view.bounds;
}
}
(not really up for writing a Swift version at the moment, sorry. :)

This should do it. Add the recognizer to the subview of the player instead:
[videoPlayerViewController.view.subviews[0] addGestureRecognizer:tap]
This was a quick fix for complex UI and gestures look into
contentOverlayView see TyR answer

Swift 4
Add a gesture recognizer to AVPlayerViewController's view:
func playVideo() {
let playerController = AVPlayerViewController()
playerController.player = AVPlayer(url: URL(fileURLWithPath: videoPath))
playerController.showsPlaybackControls = false
playerController.view.isUserInteractionEnabled = true
let swipeUp = UISwipeGestureRecognizer(target: self, action: #selector(self.respondToSwipeGesture))
swipeUp.direction = UISwipeGestureRecognizerDirection.up
playerController.view.addGestureRecognizer(swipeUp)
present(playerController, animated: false) {
playerController.player?.play()
}
}
#objc func respondToSwipeGesture(gesture: UIGestureRecognizer) {
print("swipe up")
}

As far as my knowledge is concerned what you really want to is
UILongPressGestureRecognizer
Because,
UITapGestureRecognizer
Does not have any release type of event, if u add the
UILongPressGestureRecognizer
to your
avPlayerViewController
Then in
-(BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer {
if ( [gestureRecognizer isKindOfClass:[UILongPressGestureRecognizer class]] ) {
}
return YES;
}
Then you can set the minimum duration of the gesture to have a better feel,
[holdGesture setMinimumPressDuration:0.01]
Then you can implement the method,
- (void)holdAction:(UILongPressGestureRecognizer *)holdRecognizer
Then check the state of the recognizer and perform desired functionality

You can add transparent view on avplayerViewController and can add tapgesturerecognizer on that view. hope this will help :)

The easy solution is add a gesture view on the same movie holder view after adding the movie player.
No need to add the gesture recognizer directly to the movie player.
[self.movieHolderView addSubview:self.moviePlayer.view];
[self.movieHolderView addSubview:self.gestureView];
[self.movieHolderView bringSubviewToFront:self.gestureView];
Now we can add our Gesture recognizer on the gesture view like this
self.fullScreenTapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(tapHandler:)];
self.fullScreenTapGesture.numberOfTapsRequired = 1;
[self.gestureView addGestureRecognizer:self.fullScreenTapGesture];

Gesture recognizer should be added differently for OS 11 and earlier OS:
Universal solution (Swift 4):
let tap = UITapGestureRecognizer(target: self, action: #selector(videoTap))
if #available(iOS 11.0, *) {
playerVC?.contentOverlayView?.gestureRecognizers = [tap]
} else {
playerVC?.view.subviews.first?.addGestureRecognizer(tap)
}
#objc func videoTap(_ button: UIButton) {
print("Tapped on video")
}

Or you can subclass the AVPlayerViewController and use folowwing method to
get the control : -
1.touchBegan
2.touchEnd
Make sure that you set this flag self.avPlayerContr.showsPlaybackControls = false
Hope this works for you.

Related

iOS distinguish single tap and double tap at first opening

I know that I can use requireGestureRecognizerToFail function to distinguish single tap and double tap, but I meet a tiny problem, and I want to fix it. My code is as below:
- (IBAction)singleTap:(UITapGestureRecognizer *)sender {
NSLOGD_METADATAONLY();
hideNavigationBar();
hideStatusBar();
[sender requireGestureRecognizerToFail:self.doubleTapRecognizer];
}
- (IBAction)doubleTap:(UITapGestureRecognizer *)sender {
NSLOGD_METADATAONLY();
//TODO
}
When I open a file and double tap(action1), single tap(result1) will be called firstly and then double tap(result2) will be called.
But if I open a file and single tap(action3), then double tap(action4), the result of action4 is working well,single tap will not be called only double tap will be called. I guess it is because in action3 the function requireGestureRecognizerToFail is called.
My question is HOW CAN I make action1 just call result2 and not call result1?
Write the below lines in ViewDidLoad
[singleTap requireGestureRecognizerToFail:doubleTap];
Extend your class as UIGestureRecognizerDelegate.
class ViewController: UIViewController, UIGestureRecognizerDelegate {
Then assign your view controller as delegate of gesture recognizer.
tapGesture .delegate = self;
doubleTapGesture .delegate = self;
Implementation of shouldRequireFailureOf prevents the one gesture from being recognized until after the other gesture recognizer explicitly reaches the failed state.
You need to recognize a single tap only after the failure of a double tap, which happens when the touch sequence contains only one tap.
This can be achieved implementing following.
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer,
shouldRequireFailureOf otherGestureRecognizer: UIGestureRecognizer) -> Bool {
// Don't recognize a single tap until a double-tap fails.
if gestureRecognizer == self.tapGesture &&
otherGestureRecognizer == self.doubleTapGesture {
return true
}
return false
}
Hope it helps.Happy Coding!!
Update your code with [singleTapRecognizer requireGestureRecognizerToFail: doubleTapRecognizer] when you creating UITapGestureRecognizer
Example:
// single tap
UITapGestureRecognizer *singleTapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget: tableViewController action: #selector(handleSingleTapOnView:)];
[singleTapRecognizer setNumberOfTouchesRequired:1];
[singleTapRecognizer requireGestureRecognizerToFail:doubleTapRecognizer];
[view addGestureRecognizer: singleTapRecognizer];
// double tap
UITapGestureRecognizer *doubleTapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget: tableViewController action: #selector (handleDoubleTapOnView:)];
[doubleTapRecognizer setNumberOfTouchesRequired:2];
[view addGestureRecognizer: doubleTapRecognizer];
When you set requireGestureRecognizerToFail: for a gesture recognizer, you're saying that it should only recognize the gesture if the other gesture recognizer did not.

Hide keyboard on touch anywhere outside UITextField when view includes UICollectionView

There are some answers out there such as this, but in the case where there is a UIScrollView or UICollectionView present, it doesn't work.
The touchesBegan method on the viewController will never get called.
On screen, I have a UITextField at the top.
Below that, filling up the rest of the screen is a UICollectionView.
I need to dismiss the keyboard if I touch anywhere besides the UITextField (including the collection view obviously)
So what is the best way to do this?
For such a common UI paradigm it seems like there should be a well-known solution, but I've yet to come across it.
To dismiss Keyboard on tap of the View: Add a Tap gesture to your ViewController.collectionView as follows:
//declare a property to store your current responder
#property (nonatomic, assign) id currentResponder;
//in viewDidLoad:
UITapGestureRecognizer *singleTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(resignOnTap:)];
[singleTap setNumberOfTapsRequired:1];
[singleTap setNumberOfTouchesRequired:1];
[self.collectionView addGestureRecognizer:singleTap];
//Implement the below delegate method:
- (void)textFieldDidBeginEditing:(UITextField *)textField {
self.currentResponder = textField;
}
//Implement resignOnTap:
- (void)resignOnTap:(id)sender {
[self.currentResponder resignFirstResponder];
}
The simple way to do it is:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
[self.view endEditing:YES];
}
Here's a better solution which doesn't require adding gesture recognisers individually to everything. It's in Swift, but could easily be converted to ObjC.
Add the following to your viewDidLoad():
let tap = UITapGestureRecognizer(target: self, action: "dismissKeyboard")
tap.cancelsTouchesInView = false
view.addGestureRecognizer(tap)
and then add the following method declaration:
func dismissKeyboard()
{
view.endEditing(true)
}
...where view is your text field.

Subview and UIWebView UIGesture doesn't work

I recently got into trouble with a View Controller which has a UIWebView in it and a subview which I would like to add to the View Controller.
That's the View with the UIWebView:
http://cl.ly/image/03473l0e3a2L
My target is, to add a Share Menu which does work without problems:
http://cl.ly/image/3b273t2o3P00
But now I have the problem, that I set gesture recognizers for the social icons + labels (twitter,facebook,mail) - but these gesture recognizers don't do anything.
The ShareView is a UIView Subclass and I add the Gesture Recognizers this way:
UITapGestureRecognizer *fbTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(fbTapped:)];
fbTap.numberOfTapsRequired = 1;
fbTap.numberOfTouchesRequired = 1;
fbTap.delegate = self;
[fbImage addGestureRecognizer:fbTap];
[fbLabel addGestureRecognizer:fbTap];
UITapGestureRecognizer *twTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(twTapped:)];
twTap.numberOfTapsRequired = 1;
twTap.numberOfTouchesRequired = 1;
twTap.delegate = self;
[twImage addGestureRecognizer:twTap];
[twLabel addGestureRecognizer:twTap];
UITapGestureRecognizer *mailTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(mailTapped:)];
mailTap.numberOfTapsRequired = 1;
mailTap.numberOfTouchesRequired = 1;
mailTap.delegate = self;
[mailImage addGestureRecognizer:mailTap];
[mailLabel addGestureRecognizer:mailTap];
I think the Label and the UIImageView names do explain themselves. Every Label and ImageView has set userInteractionEnabled to YES. The ShareView is also enabled for Userinteractions and I did set UIGestureRecognizerDelegate to it.
The fbTapped,mailTapped and twTapped functions do send a Notification to the Main View (the view which has the webview and the ShareView in it).
But now when I click on the labels or imageviews, nothing happendes.
I did read on stackoverflow that the UIWebView in the MainView could interrupt the recognization? But I don't know how to solve this problem.
Would be really happy If you could help me or point me into the right direction to solve this problem.
I hope this piece of code will point you to the right direction:
1.Add a gesture recognizer delegate :
#interface myclass <UIGestureRecognizerDelegate>
{
//Whatever you are doing with gestures
}
2.Implement the delegate method:
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
//Do your stuff
return YES;
}
check if the control reaches this delegate

UIPageViewController Gesture recognizers

I have a UIPageViewController load with my Viewcontroller.
The view controllers have buttons which are overridden by the PageViewControllers gesture recognizers.
For example I have a button on the right side of the viewcontroller and when you press the button, the PageViewController takes over and changes the page.
How can I make the button receive the touch and cancel the gesture recognizer in the PageViewController?
I think the PageViewController makes my ViewController a subview of its view.
I know I could turn off all of the Gestures, but this isn't the effect I'm looking for.
I would prefer not to subclass the PageViewController as apple says this class is not meant to be subclassed.
Here is another solution, which can be added in the viewDidLoad template right after the self.view.gestureRecognizers = self.pageViewController.gestureRecognizers part from the Xcode template. It avoids messing with the guts of the gesture recognizers or dealing with its delegates. It just removes the tap gesture recognizer from the views, leaving only the swipe recognizer.
self.view.gestureRecognizers = self.pageViewController.gestureRecognizers;
// Find the tap gesture recognizer so we can remove it!
UIGestureRecognizer* tapRecognizer = nil;
for (UIGestureRecognizer* recognizer in self.pageViewController.gestureRecognizers) {
if ( [recognizer isKindOfClass:[UITapGestureRecognizer class]] ) {
tapRecognizer = recognizer;
break;
}
}
if ( tapRecognizer ) {
[self.view removeGestureRecognizer:tapRecognizer];
[self.pageViewController.view removeGestureRecognizer:tapRecognizer];
}
Now to switch between pages, you have to swipe. Taps now only work on your controls on top of the page view (which is what I was after).
You can override
-(BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer
shouldReceiveTouch:(UITouch *)touch
to better control when the PageViewController should receive the touch and not. Look at "Preventing Gesture Recognizers from Analyzing Touches" in Dev API Gesture Recognizers
My solution looks like this in the RootViewController for the UIPageViewController:
In viewDidLoad:
//EDITED Need to take care of all gestureRecogizers. Got a bug when only setting the delegate for Tap
for (UIGestureRecognizer *gR in self.view.gestureRecognizers) {
gR.delegate = self;
}
The override:
-(BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch {
//Touch gestures below top bar should not make the page turn.
//EDITED Check for only Tap here instead.
if ([gestureRecognizer isKindOfClass:[UITapGestureRecognizer class]]) {
CGPoint touchPoint = [touch locationInView:self.view];
if (touchPoint.y > 40) {
return NO;
}
else if (touchPoint.x > 50 && touchPoint.x < 430) {//Let the buttons in the middle of the top bar receive the touch
return NO;
}
}
return YES;
}
And don't forget to set the RootViewController as UIGestureRecognizerDelegate.
(FYI, I'm only in Landscape mode.)
EDIT - The above code translated into Swift 2:
In viewDidLoad:
for gr in self.view.gestureRecognizers! {
gr.delegate = self
}
Make the page view controller inherit UIGestureRecognizerDelegate then add:
func gestureRecognizer(gestureRecognizer: UIGestureRecognizer, shouldReceiveTouch touch: UITouch) -> Bool {
if let _ = gestureRecognizer as? UITapGestureRecognizer {
let touchPoint = touch .locationInView(self.view)
if (touchPoint.y > 40 ){
return false
}else{
return true
}
}
return true
}
I had the same problem. The sample and documentation does this in loadView or viewDidLoad:
self.view.gestureRecognizers = self.pageViewController.gestureRecognizers;
This replaces the gesture recognizers from the UIViewControllers views with the gestureRecognizers of the UIPageViewController. Now when a touch occurs, they are first sent through the pageViewControllers gesture recognizers - if they do not match, they are sent to the subviews.
Just uncomment that line, and everything is working as expected.
Phillip
Setting the gestureRecognizers delegate to a viewController as below no longer work on ios6
for (UIGestureRecognizer *gR in self.view.gestureRecognizers) {
gR.delegate = self;
}
In ios6, setting your pageViewController's gestureRecognizers delegate to a viewController causes a crash
In newer versions (I am in Xcode 7.3 targeting iOS 8.1+), none of these solutions seem to work.
The accepted answer would crash with error:
UIScrollView's built-in pan gesture recognizer must have its scroll view as its delegate.
The currently highest ranking answer (from Pat McG) no longer works as well because UIPageViewController's scrollview seems to be using odd gesture recognizer sub classes that you can't check for. Therefore, the statement if ( [recognizer isKindOfClass:[UITapGestureRecognizer class]] ) never executes.
I chose to just set cancelsTouchesInView on each recognizer to false, which allows subviews of the UIPageViewController to receive touches as well.
In viewDidLoad:
guard let recognizers = self.pageViewController.view.subviews[0].gestureRecognizers else {
print("No gesture recognizers on scrollview.")
return
}
for recognizer in recognizers {
recognizer.cancelsTouchesInView = false
}
I used
for (UIScrollView *view in _pageViewController.view.subviews) {
if ([view isKindOfClass:[UIScrollView class]]) {
view.delaysContentTouches = NO;
}
}
to allow clicks to go through to buttons inside a UIPageViewController
In my case I wanted to disable tapping on the UIPageControl and let tapping being received by another button on the screen. Swipe still works. I have tried numerous ways and I believe that was the simplest working solution:
for (UIPageControl *view in _pageController.view.subviews) {
if ([view isKindOfClass:[UIPageControl class]]) {
view.enabled = NO;
}
}
This is getting the UIPageControl view from the UIPageController subviews and disabling user interaction.
Just create a subview (linked to a new IBOutlet gesturesView) in your RootViewController and assign the gestures to this new view. This view cover the part of the screen you want the gesture enable.
in viewDidLoad change :
self.view.gestureRecognizers = self.pageViewController.gestureRecognizers;
to :
self.gesturesView.gestureRecognizers = self.pageViewController.gestureRecognizers;
If you're using a button that you've subclassed, you could override touchesBegan, touchesMoved, and touchesEnded, invoking your own programmatic page turn as appropriate but not calling super and passing the touches up the notification chain.
Also can use this (thanks for help, with say about delegate):
// add UIGestureRecognizerDelegate
NSPredicate *tp = [NSPredicate predicateWithFormat:#"self isKindOfClass: %#", [UITapGestureRecognizer class]];
UITapGestureRecognizer *tgr = (UITapGestureRecognizer *)[self.pageViewController.view.gestureRecognizers filteredArrayUsingPredicate:tp][0];
tgr.delegate = self; // tap delegating
NSPredicate *pp = [NSPredicate predicateWithFormat:#"self isKindOfClass: %#", [UIPanGestureRecognizer class]];
UIPanGestureRecognizer *pgr = (UIPanGestureRecognizer *)[self.pageViewController.view.gestureRecognizers filteredArrayUsingPredicate:pp][0];
pgr.delegate = self; // pan delegating
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch
{
CGPoint touchPoint = [touch locationInView:self.view];
if (UIInterfaceOrientationIsPortrait([[UIApplication sharedApplication] statusBarOrientation]) && touchPoint.y > 915 ) {
return NO; // if y > 915 px in portrait mode
}
if (UIInterfaceOrientationIsLandscape([[UIApplication sharedApplication] statusBarOrientation]) && touchPoint.y > 680 ) {
return NO; // if y > 680 px in landscape mode
}
return YES;
}
Work perfectly for me :)
This is the solution which worked best for me I tried JRAMER answer with was fine except I would get an Error when paging beyond the bounds (page -1 or page 23 for me)
PatMCG solution did not give me enough flexibility since it cancelled all the taprecognizers, I still wanted the tap but not within my label
In my UILabel I simply overrode as follows, this cancelled tap for my label only
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer
{
if ([gestureRecognizer isKindOfClass:[UITapGestureRecognizer class]]) {
return NO;
} else {
return YES;
}
}
I create pageviewcontrollers regularly as my user jumps, curls, and slides to various different page views. In the routine that creates a new pageviewcontroller, I use a slightly simpler version of the excellent code shown above:
UIPageViewController *npVC = [[UIPageViewController alloc]
initWithTransitionStyle:UIPageViewControllerTransitionStylePageCurl
navigationOrientation:UIPageViewControllerNavigationOrientationHorizontal
options: options];
...
// Find the pageView tap gesture recognizer so we can remove it!
for (UIGestureRecognizer* recognizer in npVC.gestureRecognizers) {
if ( [recognizer isKindOfClass:[UITapGestureRecognizer class]] ) {
UIGestureRecognizer* tapRecognizer = recognizer;
[npVC.view removeGestureRecognizer:tapRecognizer];
break;
}
}
Now the taps work as I wish (with left and right taps jumping a page, and the curls work fine.
Swift 3 extension for removing tap recognizer:
import UIKit
extension UIPageViewController {
func removeTapRecognizer() {
let gestureRecognizers = self.gestureRecognizers
var tapGesture: UIGestureRecognizer?
gestureRecognizers.forEach { recognizer in
if recognizer.isKind(of: UITapGestureRecognizer.self) {
tapGesture = recognizer
}
}
if let tapGesture = tapGesture {
self.view.removeGestureRecognizer(tapGesture)
}
}
}
I ended up here while looking for a simple, catch-all way to respond to taps on my child view controllers within a UIPageViewController. The core of my solution (Swift 4, Xcode 9) wound up being as simple as this, in my RootViewController.swift (same structure as Xcode's "Page-Based App" template):
override func viewDidLoad() {
super.viewDidLoad()
...
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(pageTapped(sender:)))
self.pageViewController?.view.subviews[0].addGestureRecognizer(tapGesture)
}
...
#objc func pageTapped(sender: UITapGestureRecognizer) {
print("pageTapped")
}
(I also made use of this answer to let me keep track of which page was actually tapped, ie. the current one.)
I worked out a working solution.
Add another UIGestureRecognizer to UIPageViewController and implement delegate method provided below.
In every moment that you have to resolve which gesture should be locked or passed further this method will be called. Remember to provide a reference to confictingView, which in my case it was UITableView, which also recognizes pan gesture. This view was placed inside UIPageViewController, so a pan gesture was recognized twice or just in randomly way. Now in this method, I check if pan gesture is inside both my UITableView and UIPageViewController, and I decide that UIPanGestureRecognizer is primary.
This approach doesn't override directly any of another gesture recognizers so we don't have to worry about mentioned 'NSInvalidArgumentException'.
Keep in mind that pattern actually is not approved by Apple :)
var conflictingView:UIView?
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
if otherGestureRecognizer.view === pageViewController?.view {
if let view = conflictingView {
var point = otherGestureRecognizer.location(in: self.view)
if view.frame.contains(point) {
print("Touch in conflicting view")
return false
}
}
print("Touch outside conficting view")
return true
}
print("Another view passed out")
return true
}

UIButton Long Press Event

I want to emulate a long a press button, how can I do this? I think a timer is needed.
I see UILongPressGestureRecognizer but how can I utilize this type?
You can start off by creating and attaching the UILongPressGestureRecognizer instance to the button.
UILongPressGestureRecognizer *longPress = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:#selector(longPress:)];
[self.button addGestureRecognizer:longPress];
[longPress release];
And then implement the method that handles the gesture
- (void)longPress:(UILongPressGestureRecognizer*)gesture {
if ( gesture.state == UIGestureRecognizerStateEnded ) {
NSLog(#"Long Press");
}
}
Now this would be the basic approach. You can also set the minimum duration of the press and how much error is tolerable. And also note that the method is called few times if you after recognizing the gesture so if you want to do something at the end of it, you will have to check its state and handle it.
As an alternative to the accepted answer, this can be done very easily in Xcode using Interface Builder.
Just drag a Long Press Gesture Recognizer from the Object Library and drop it on top of the button where you want the long press action.
Next, connect an Action from the Long Press Gesture Recognizer just added, to your view controller, selecting the sender to be of type UILongPressGestureRecognizer. In the code of that IBAction use this, which is very similar to the code suggested in the accepted answer:
In Objective-C:
if ( sender.state == UIGestureRecognizerStateEnded ) {
// Do your stuff here
}
Or in Swift:
if sender.state == .Ended {
// Do your stuff here
}
But I have to admit that after trying it, I prefer the suggestion made by #shengbinmeng as a comment to the accepted answer, which was to use:
In Objective-C:
if ( sender.state == UIGestureRecognizerStateBegan ) {
// Do your stuff here
}
Or in Swift:
if sender.state == .Began {
// Do your stuff here
}
The difference is that with Ended, you see the effect of the long press when you lift your finger. With Began, you see the effect of the long press as soon as the long press is caught by the system, even before you lift the finger off the screen.
Swift version of the accepted answer
I made the additional modification of using UIGestureRecognizerState.Began rather than .Ended since that is probably what most users would naturally expect. Try them both and see for yourself, though.
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var button: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
// add gesture recognizer
let longPress = UILongPressGestureRecognizer(target: self, action: #selector(longPress(_:)))
self.button.addGestureRecognizer(longPress)
}
func longPress(gesture: UILongPressGestureRecognizer) {
if gesture.state == UIGestureRecognizerState.began {
print("Long Press")
}
}
#IBAction func normalButtonTap(sender: UIButton) {
print("Button tapped")
}
}
Try this:
Adding button in viewDidLoad: like below
-(void)viewDidLoad {
UIButton *btn = [UIButton buttonWithType:UIButtonTypeRoundedRect];
[btn setTag:1]; //you can set any integer value as tag number
btn.title = #"Press Me";
[btn setFrame:CGRectMake(50.0, 50.0, 60.0, 60.0)];
// now create a long press gesture
UILongPressGestureRecognizer *longPress = [[UILongPressGestureRecognizer alloc]initWithTarget:self action:#selector(longPressTap:)];
[btn addGestureRecognizer:longPress];
}
Now call the gesture method like this
-(void)longPressTap:(id)sender {
UIGestureRecognizer *recognizer = (UIGestureRecognizer*) sender
// Recogniser have all property of button on which you have clicked
// Now you can compare button's tag with recogniser's view.tag
// View frame for getting the info on which button the click event happened
// Then compare tag like this
if(recognizer.view.tag == 1) {
// Put your button's click code here
}
// And you can also compare the frame of your button with recogniser's view
CGRect btnRect = CGRectMake(50.0, 50.0, 60.0, 60.0);
if(recogniser.view.frame == btnRect) {
//put your button's click code here
}
// Remember frame comparing is alternative method you don't need to write frame comparing code if you are matching the tag number of button
}
I think you need my solution.
you should have this code for single press
- (IBAction)buttonDidPress:(id)sender {
NSLog("buttonDidPress");
}
first, add long press gesture to button
- (void)viewWillAppear:(BOOL)animated
{
UILongPressGestureRecognizer *longPress = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:#selector(buttonDidLongPress:)];
[self.button addGestureRecognizer:longPress];
}
then call single press event repeatedly if long press gesture is recognized.
- (void)buttonDidLongPress:(UILongPressGestureRecognizer*)gesture
{
switch (gesture.state) {
case UIGestureRecognizerStateBegan:
{
self.timer = [NSTimer timerWithTimeInterval:0.1 target:self selector:#selector(buttonDidPress:) userInfo:nil repeats:YES];
NSRunLoop * theRunLoop = [NSRunLoop currentRunLoop];
[theRunLoop addTimer:self.timer forMode:NSDefaultRunLoopMode];
}
break;
case UIGestureRecognizerStateEnded:
{
[self.timer invalidate];
self.timer = nil;
}
break;
default:
break;
}
}
For Swift 4, the "func longPress" needs to be changed to make it work:
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var button: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
// add guesture recognizer
let longPress = UILongPressGestureRecognizer(target: self, action: #selector(longPress(_:)))
self.button.addGestureRecognizer(longPress)
}
#objc func longPress(_ guesture: UILongPressGestureRecognizer) {
if guesture.state == UIGestureRecognizerState.began {
print("Long Press")
}
}
#IBAction func normalButtonTap(sender: UIButton) {
print("Button tapped")
}
}
One-line answer, with no gestures:
[btn addTarget:self action:#selector(handleTouch:) forControlEvents:UIControlEventTouchDown | UIControlEventTouchUpInside | UIControlEventTouchUpOutside];
Details:
This triggers your target on three events:
1- Immediately once finger touches down the button: UIControlEventTouchDown. This captures long presses start.
2 & 3- When user lifts finger up: UIControlEventTouchUpOutside & UIControlEventTouchUpInside. This captures end of the user press.
Note: this works well if you don't care about the extra info provided by the gesture recognizer (e.g. location of touch, etc.)
You can add more intermediate events if needed see them all here https://developer.apple.com/documentation/uikit/uicontrolevents?language=objc.
In Storyboard:
Connect your button to the 3 events, not just the default one that Storyboard selects (Touch Up Inside).
I have a subclassed UIButton for my app, so I've pulled out my implementation. You can add this to your subclass or this could just as easily be recoded as a UIButton category.
My goal was to add the long press to my button without cluttering my view controllers with all of the code. I've decided that the action should be called when the gesture recognizer state begins.
There is a warning that comes out that I've never bothered to solve. Says it is a possible leak, thought I've tested the code and it doesn't leak.
#interface MYLongButton ()
#property (nonatomic, strong) UILongPressGestureRecognizer *gestureRecognizer;
#property (nonatomic, strong) id gestureRecognizerTarget;
#property (nonatomic, assign) SEL gestureRecognizerSelector;
#end
#implementation MYLongButton
- (void)addLongPressTarget:(CGFloat)interval target:(id)target action:(SEL)selector
{
_gestureRecognizerTarget = target;
_gestureRecognizerSelector = selector;
_gestureRecognizer = [[UILongPressGestureRecognizer alloc]initWithTarget:self action:#selector(handleLongPressGestureRecognizer:)];
_gestureRecognizer.minimumPressDuration = interval;
[self addGestureRecognizer:_gestureRecognizer];
}
- (void)handleLongPressGestureRecognizer:(UIGestureRecognizer *)gestureRecognizer
{
if (gestureRecognizer.state == UIGestureRecognizerStateBegan) {
NSAssert([_gestureRecognizerTarget respondsToSelector:_gestureRecognizerSelector], #"target does not respond to selector");
self.highlighted = NO;
// warning on possible leak -- can anybody fix it?
[_gestureRecognizerTarget performSelector:_gestureRecognizerSelector withObject:self];
}
}
To assign the action add this line to your viewDidLoad method.
[_myLongButton addLongPressTarget:0.75 target:self selector:#selector(longPressAction:)];
The action should be defined like all IBActions (without the IBAction).
- (void)longPressAction:(id)sender {
// sender is the button
}
None worked hence I tried writing longpress code in IBAction or button click from storyboard in Controller instead of writing in viewDidLoad
- (IBAction)btnClick:(id)sender {
tag = (int)((UIButton *)sender).tag;
// Long press here instead of in viewDidLoad
UILongPressGestureRecognizer *longPress = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:#selector(longPress:)];
longPress.cancelsTouchesInView = NO;
[sender addGestureRecognizer:longPress];
}

Resources