I am trying to achieve an effect in an application I am writing and maybe you can help.
The view hierarchy is reasonably complicated at this point so for simplicity let’s just say I have a UILabel with it’s Alpha value set to 0.
Currently I’m using a UILongPressGestureRecognizer which is after a couple of seconds, calling a method that updates the text of the UILabel and executes a UIView Animation block to display it nicely (set's the Alpha back to 1 over the course of 1 second - fading it in).
This Block fades in the newly updated label, then in it’s completion block fades it back out over the course of another second.
This is OK, but what I really want is to have the label fade in (via UIView Animations or whatever) after a long press (say 3 seconds). Then stay on screen while the press is still active, then fade out a second after the touch is lifted.
How can I achieve this effect?
Should I forget about gesture recognizers and move to UIView animation blocks and pole the various touch up touch down states?
Thanks for reading,
Regards,
John
Every UIGestureRecognizer have state. When handle long press, just check state.
- (void)handleLongPress:(UILongPressGestureRecognizer*)longPress
{
if(longPress.state == UIGestureRecognizerStateEnded){
//do what you want
NSLog(#"%#", #"end");
}
}
The long press recogniser will work, you just need to check the state to know what to do. When the gesture is first recognised, start the fade in. When the gesture is completed, start the fade out, but using the method which allows you to set the start delay.
- (void)handleLongPress:(UILongPressGestureRecognizer*)sender
{
if (sender.state == UIGestureRecognizerStateEnded) {
// fade out, delayed
}
else if (sender.state == UIGestureRecognizerStateBegan) {
// fade in
}
}
Related
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
I have the following code in a viewDidLoad on a UIViewController:
UIScreenEdgePanGestureRecognizer *edgeRecognizer = [[UIScreenEdgePanGestureRecognizer alloc] initWithTarget:self action:#selector(handleRightEdgeSwipe:)];
edgeRecognizer.edges = UIRectEdgeRight;
[self.view addGestureRecognizer:edgeRecognizer];
and the purposes is to trigger a view to slide in when a right edge gesture is detected.
-(void)handleRightEdgeSwipe:(UIGestureRecognizer*)sender
{
NSLog(#"Showing Side Bar");
[self presentPanelViewController:_lightPanelViewController withDirection:MCPanelAnimationDirectionRight];
}
But I am seeing that the "handleRightEdgeSwipe" function is triggered multiple times - sometimes 5 times which makes the side bar view that should smoothly animate slide in to flash multiple times.
(NOTE: I tried triggering the view to appear from a UIButton and it works fine).
Why is the right edge gesture triggered multiple times and how can I fix it?
As noted, the UIScreenEdgePanGestureRecognizer invokes your action multiple times as the state of the GestureRecognizer changes. See the documentation for the state property of the UIGestureRecognizer class. So, in your case I believe the answer you're looking for is to check if the state is "ended". Thus:
-(void)handleRightEdgeSwipe:(UIGestureRecognizer*)sender
{
if (sender.state == UIGestureRecognizerStateEnded)
{
NSLog(#"Showing Side Bar");
[self presentPanelViewController:_lightPanelViewController withDirection:MCPanelAnimationDirectionRight];
}
}
This gesture is not a single-shot event but continuous.
handleRightEdgeSwipe: is called once whenever sender.state changes or the touch moved around. You have to move the UIButton depending on the gesture's state and locationInView:.
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) {
...
}
}
}
here's an odd one..
I've got a UIView xib file that looks like this:
I've connected every UIButton touchDown and touchUpInside events to two IBAction methods:
- (IBAction)touchUpInside:(id)sender
{
NSLog(#"touch up inside");
if (((UIButton *)sender == _enter) | ((UIButton *)sender == _back)) {
[(UIButton *)sender setBackgroundColor:_color2];
}
else {
[(UIButton *)sender setBackgroundColor:_color1];
}
}
- (IBAction)touchDown:(id)sender
{
NSLog(#"touch down");
[(UIButton *)sender setBackgroundColor:_color2];
}
Everything works except for the bottom-most row of UIButton's, that's the odd part:
The touch down event is fired, but the button must be held for 0.5 second for it to change background color, whereas it is instantaneous for the other buttons.
It ONLY happens for the bottom-most row of UIButton's, as I've tried to switch buttons 7, 8, 9 with buttons #back, 0, #enter like this:
I've checked in Interface Builder all the UIButton attributes are the same, and I've tried moving the UIButton's objects order around as you can see on the left side of the picture, and I'm about out of ideas already. Basically what's odd is the UIControl behavior differs based on its position on the parent view...
UPDATE: I made the parent UIView height value large enough that there is 50 free pixels below the last row and the UIButton's work fine now. The only reason I can think of now is that there is a UITabBar there 2 view controllers level underneath. Even so it doesn't make sense.
The document says:
Expect users to swipe up from the bottom of the screen to reveal
Control Center. If iOS determines that a touch that begins at the
bottom of the screen should reveal Control Center, it doesn’t deliver
the gesture to the currently running app. If iOS determines that the
touch should not reveal Control Center, the touch may be slightly
delayed before it reaches the app.
One solution is here:
UIButton fails to properly register touch in bottom region of iPhone screen
But, in your case, I think you should use inputView in UIResponder.
See: https://developer.apple.com/library/ios/documentation/StringsTextFonts/Conceptual/TextAndWebiPhoneOS/InputViews/InputViews.html
The inputView is not affected by that problem.
I want to make my scrollview less sensitive. This means that I want the user to have to move the finger a bit more before the view starts scrolling. Does anyone knows how to achieve this? I remember seeing a property that would let me set a required number of pixels before a scroll is detected. But it could just be my imagination. Perhaps what is required is to modify the underlying UIPanGestureRecognizer inside the scroll view?
The reason for this is that I am detecting a press on the view, so as long as the user has the finger there a function is constantly running. This function has to have the absolute priority, but the user might want to scroll the view instead, so I am canceling the function by detecting if the scrollview was scrolled. Everything works perfectly, except when the user is moving his hand/arm, his finger "might slip" a little bit, thus canceling the function since the scrollview starts scrolling.
Edit: (to address some of the confusion in the question)
How do I delay the scrolling in a scrollview by requiring the user to move the finger more before the scrollview starts scrolling?
I write an Answer so i can write code, but i am not entirely sure why you want the finger-offset. Lets assume you have an Area on your scrollview in which the user can longpress. During longpress you want to do some calculating and you do not want the scrollview to move. In my opinion there are two possibilties: Either you want the longpress to cancel if the finger moves a certain amount ("difficult to archive") or you want to supress the scrollview movement as long as the finger is pressed down, no matter what the movement is ("simple to archive").
Simple Solution:
- (void)yourUIBuilder {
...
UILongPressGestureRecognizer *recognizer = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:#selector(longPressChanged:)];
[recognizer setDelegate:self];
[recognizer setMinimumPressDuration:0.3];
[yourScrollView addGestureRecognizer:recognizer];
}
- (void)longPressChanged:(UIGestureRecognizer *)recognizer {
switch (recognizer.state) {
case UIGestureRecognizerStateBegan:
// start your calculations
break;
case UIGestureRecognizerStateChanged:
break;
default:
// stop your calculations
break;
}
}