Swift Programmatically Launch a UILongPressGesture - ios

I would like to programmatically launch a UILongPressGesture when a user touches a button. This was asked years ago at How can I send a UILongPressGesture programmatically? but I'm wondering if there's a cleaner or more modern solution.
My existing UILongPressGestureRecognizer code (which maps actual user interactions to functionality) works as follows:
view.addGestureRecognizer(UILongPressGestureRecognizer(target: self, action: #selector(longPress)))
where longPress is defined as:
#objc func longPress(sender: UILongPressGestureRecognizer) {
switch sender.state {
case .began:
// Display a 'hover' animation for a specific UIView
....
case .changed:
// Move existing hover animation
...
default:
// Complete the hover animation
...
}
}
Desired Functionality
I am using this long press to display a 'hovering' effect for whatever UIView is selected by the user. I also want to provide a button to start the long press automatically (bypassing the longPress where sender.state == .began). My projected solution is programmatically creating the .began long press, creating a gesture object that users can drag around the screen (possibly even translating the UILongPressGestureRecognizer to a UIPanGestureRecognizer if possible), and then continuing the hover animation logic with that new gesture.

I found a much cleaner solution to the problem at hand - Replacing the desired button with a UIView containing its own UILongPressGestureRecognizer. I set this gesture's minimumPressDuration to 0 so it behaves equivalently to a button's touchDown event. This new gesture uses the same longPress function from the original question without requiring any additional code to trigger.

Related

Why is the UITapGestureRecognizer never getting called with state began?

If you assign a UITapGestureRecognizer to a UIView the UIGestureRecognizerStateBegan doesn't appear when the user has touched the view.
// Tap
_tapGestureRecognizer =
[[UITapGestureRecognizer alloc] initWithTarget:self
action:#selector(tap:)];
[_someView addGestureRecognizer:_tapGestureRecognizer];
Instead the recognizer jumps straight to UIGestureRecognizerStateEnded when the user performs the tap.
I have to change that view to a UIButton and listen to the touchDown method.
_someButton = [[UIButton alloc] init];
[_someButton addTarget:self action:#selector(touchDown:) forControlEvents:UIControlEventTouchDown];
[self addSubview: _someButton];
I don't like changing the UIView to a UIButton just for this.
Can I use the UITapGestureRecognizer instead?
Let me start by saying that UITapGestureRecognizer docs clearly tell to expect a callback for all states.
For gesture recognition, the specified number of fingers must tap the view a specified number of times. Although taps are discrete gestures, they are discrete for each state of the gesture recognizer. The system sends the associated action message when the gesture begins and then again for each intermediate state until (and including) the ending state of the gesture. Code that handles tap gestures should test for the state of the gesture, for example:
func handleTap(sender: UITapGestureRecognizer) {
if sender.state == .ended {
// handling code
}
}
Hower it makes little to no sense (specially in case of single tap recognizer). You touched a view (that had the tap gesture added to it), you haven't yet lifted your finger, moved it etc. System can't know at the time of .touchDown event that this interaction is going to turn into a successful recognition of a tap (which requires lifting the finger up).
Essentially UITapGestureRecognizer (for a single touch tap) is a .touchDown + .touchUp combination. If anything else happens after .touchDown like a drag (.touchDragInside OR .touchDragExit), it may lead to successful recognition of a pan gesture (tableView scrolling etc.)
You can think of UITapGestureRecognizer roughly equivalent to .touchUpInside event for a button. A .touchUpInside event for a button doesn't call your function for .touchDown event, It is only possible to receive that event by explicitly asking for the same.
Why do the docs say so?
Maybe system is able to identify the .began state for other scenarios
a multi-tap gesture - double/triple tap (see UITapGestureReconizer.numberOfTapsRequired)
a multi-touch tap - 2/3 finger tap (see UITapGestureReconizer.numberOfTouchesRequired)
You have to test other scenarios for this if you want to know more.

Is there a way to make a UITextField move when user drags across screen?

I'm new to coding so I'm trying some small projects in swift. Right now, I'm trying to make a text box inside the ViewController move when the user drags it along the screen. For the text box, I am currently using a UITextField but I have no idea how to program its movement according to drag.
You'll want to add a UIPanGestureRecognizer to your view. There's all sorts of built in gesture recognizers for detecting various gestures like a tap or in this case a pan (drag). You can check them out here: https://developer.apple.com/documentation/uikit/uigesturerecognizer
Here we'll create a pan gesture recognizer, and add it to our view. Assume myView is your UITextField. A good place to do this is in your view controller's viewDidLoad() method.
let pan = UIPanGestureRecognizer(target: self, action: #selector(handlePan(sender:)))
myView.addGestureRecognizer(pan)
The moment your finger touches the screen, we say that a touch sequence has begun. The touch sequence ends when there are no more fingers on the screen. The pan gesture will determine if this touch sequence looks like a pan, and if so, the method handlePan will be called at various stages. Here, the gesture itself will be passed into the method, which we use to determine translation and move our view accordingly. Add this as a method of your view controller.
#objc func handlePan(sender: UIPanGestureRecognizer) {
let translation = sender.translation(in: sender.view)
self.myView.center.x += translation.x
self.myView.center.y += translation.y
sender.setTranslation(CGPoint.zero, in: sender.view)
}
The first line gets the translation in the view which the gesture is attached to (myView). We then adjust myView's position based on this translation, and then we set the translation to zero. This is so that the next time this method is called, the translation will be a delta relative to the previous call.
The property sender.state will tell you the state the gesture is currently in, for example, .began, .changed, .ended. Since a pan is a continuous gesture, our method will be called many times, whenever there's a finger movement.

Create a Bounce Effect on UIView when it is touched

How do we create a bounce effect when user touch a UIView?
User touch and hold the view, the view shrink in size.
When user release it, it pops back and bounce to it's normal size.
I have no idea how to explain this. But i would say It's like the one on the Tinder user's picture when it's pressed..
To have that effect you can you can use UIKit Dynamics and here is a sample code provided by Apple itself. You can use UIDynamicAnimator and UIAttachmentBehavior to achieve what you want.
You can also use UIControlEventTouchDown and UIControlEventTouchUpInside event of UIButton to handle this case, if not UIDynamicBehavior.
Thanks, hope this helps.
For handling the touch gesture i would suggest adding a Long Press Gesture Recogniser to the view in question. Control drag to create a function like this
#IBAction func longPress(sender: AnyObject)
{
if sender.state == UIGestureRecognizerState.Began
{
//Shrink animation
}
else if sender.state == UIGestureRecognizerState.Ended
{
//Bounce animation
}
}
I'm not that familiair with creating my own animations, but I have used this library called Spring (https://github.com/MengTo/Spring) in the past which was really easy to install and use to add good looking animations.

UISwipeGestureRecognizer interferes with slider

I have a view in an iOS application (Obj-C) which has an image view in the centre, and immediately below that a slider.
The image view shows album artwork, and the slider can be used to adjust the now-playing track position.
There is also a pair of left and right Swipe Gesture Recognizers. These are used to skip to the next or previous tracks.
The problem is that the swipe gesture recognizers seem to over-ride the users moving the slider thumb.
In my gesture recognizer code I check that the point touched was inside the image view, but it still stops the slider from being moved. (The thumb moves, but jumps back to it's original position when you remove your finger).
This is the code I use to reject the gesture if it's not inside the image view.
- (IBAction)swipeLeftGestureAction:(UISwipeGestureRecognizer *)sender {
// Get the location of the gesture.
CGPoint tapPoint = [sender locationInView:_artworkImageView];
// Make sure tap was INSIDE the artwork image frame.
if( (tapPoint.x <0)
|| (tapPoint.y < 0 )
|| (tapPoint.x > _artworkImageView.frame.size.width)
|| (tapPoint.y>_artworkImageView.frame.size.height))
{
NSLog(#"Outside!");
return;
}
NSLog(#"Swipe LEFT");
[_mediaController skipNext];
}
So my question is, how do I limit the gesture to work ONLY when swiped across the image view?
Try to put the code that restricts the gesture's area in gestureRecognizer:shouldReceiveTouch: and return NO in case you don't want the gesture to receive this touch. It should prevent the gesture from over taking the slider interaction.
If you're only interested in swipes that are inside the image view, then you should add the swipe gesture recognizer to the image view instead of adding it to your entire view. Then you won't need any special logic.
There is a dedicated method for checking if a point is inside a view
BOOL isPointInsideView = [_artworkImageViewpointInside:tapPoint withEvent:nil];
But I think what is happening is that if you will look at the tapPoint is that it actually outside of your imageView
And if your slider is really close to the imageView so the slider captures the movement, what you should do is check on the slider if it is intended for the slider and propagate on to the imageView if needed
Or inherit the UISlider and reduce its response rect

How implement longPressGestureRecognizer for subview with disabled user interaction?

I need UI like this:
with 2 buttons (yellow and red) and background view (grey), which will have next behaviour:
- highlight button when i press on it;
- execute when i release on button;
- when i press and move in on button from any other view, button became highlighted (ex: press on grey rect and release on red, or press on yellow and release on red);
- support gestures for buttons (like long press and swipe)
So for solving my problem i found only next way:
I redefine for my GrayView touch methods: touchesCancelled, touchesMoved, touchesBegan, and there are i check if current touch position is belong to some rect - i execute appropriate action. But for this solution i have to make my buttons with userInteractionEnabled = false, which means they doesn't support gestures or other events anymore. So if i what to use support it, i have to implement it by myself, what i don't what to do.
So how can i solve this?
If i understand correctly, you can add the gesture recognizers to the gray view as well. And when the gesture recognizer fires find which colored view was in the touch area:
- (void)tapAction:(UITapGestureRecognizer*)recognizer{
if(recognizer.state == UIGestureRecognizerStateEnded){
CGPoint position = [recognizer locationInView:grayView];
if(CGRectContainsPoint(redView.frame, position) {
...
}
}
}

Resources