I have a button. I want to change the image on initial touch. If the touch is less then 1 seconds, I want it to do X. If the touch is longer then 1 second, I want it to do Y.
I'm having trouble figuring out how to handle this. A UIButton has proved troublesome so I thought I could do it with UIGestureRecognizers or touchesBegin:
Initial thought was have a UITapGestureRecognizer to detected just a quick tap to do X, and use a UILongTapGestureRecognizer to handle a longer press to do Y.
The issue is a UITapGestureRecognizer does not flag UIGestureRecognizerStateBegan, it only ever sends a notice for UIGestureRecognizerStateEnd.
So I decided trying a combination of overridding the touchesBegin: and touchesEnd: methods and using a UILongPressGestureRecognizer:
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
// change image
}
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
// do X
// change image to original image
}
-(IBAction)longPressDetected:(UILongPressGestureRecognizer *)recognizer {
DLog(#"fired");
if (recognizer.state == UIGestureRecognizerStateBegan) {
// Do y
// change image to original image
}
else if (recognizer.state == UIGestureRecognizerStateCancelled) {
}
else if (recognizer.state == UIGestureRecognizerStateEnded) {
}
}
If the UILongPressGestureRecognzier fires, it cancels the inital touchesBegan: (does not fire touchesEnded: method).
But I have run into the issue where the touchesBegin: method is slow to fire. There is a .5 second delay in the method being fired. What baffles me, if I use UILongPressGestureRecognizer with a longTap.minimumPressDuration = 0, it fires instantly.
This is with in the program where I need it to be. Playing around with it in a dummy area, the touchesBegins: fires instantly as well.
What could cause it to lag within the program?
Is there a different way I can obtain the desired effect?
maybe you could use a timer and a Long Press Gesture Recognizer
UILongPressGestureRecognizer *longPress = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:#selector(didRecognizeLongPressGesture:)];
longPress.minimumPressDuration = 0;
[self.button addGestureRecognizer:longPress];
...
- (void)didRecognizeLongPressGesture:(UILongPressGestureRecognizer*)gesture
{
static NSTimer *timer = nil;
if (gesture.state == UIGestureRecognizerStateBegan)
{
timer = [NSTimer scheduledTimerWithTimeInterval:1.1 target:self selector:#selector(changeImage:) userInfo:nil repeats:NO];
//change the image here
}
else if ( gesture.state == UIGestureRecognizerStateEnded)
{
if ([timer isValid])
{
//the user has pressed the button less than a second
[timer invalidate];
timer = nil;
}
}
}
- (void)changeImage:(NSTimer*)timer
{
[timer invalidate];
timer = nil;
//change to the other image here
}
Related
I want to make voice recorder app. The
recording should start when long touch begins and the
recording must end when user stops the gesture on the button.
UIGestureRecognizer *longGesture=[[UILongPressGestureRecognizer alloc]initWithTarget:self action:#selector(startrecording)];
How can I handle that when the user leaves the button?
in viewDidLoad method
UILongPressGestureRecognizer *longPressOnButton = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:#selector(longPressOnButton:)];
longPressOnButton.delegate = self;
btn.userInteractionEnabled = YES;
[btn addGestureRecognizer:longPressOnButton];
- (void)longPressOnButton:(UILongPressGestureRecognizer*)gesture
{
// When you start touch the button
if (gesture.state == UIGestureRecognizerStateBegan)
{
//start recording
}
// When you stop touch the button
if (gesture.state == UIGestureRecognizerStateEnded)
{
//end recording
}
}
Also you can try or use the touchEvent concept
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)e {
// show touch-began state
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)e {
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)e {
UITouch *touch = [touches anyObject];
.....
}
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)e {
}
As user3182143 said, using a UILongPressGestureRecorgnizer will solve your problem, but if you are interested there is another way using a UIButton. No need to add a UILongPressGestureRecorgnizer!
From your storyboard, drag an IBAction for a UIButton. And while you are adding its name, change the event to Touch Down.
Now drag another IBAction for the same UIButton and while changing its name, change the event to Touch up Inside (if it isn't that already).
- (IBAction)touchDownButtonAction:(UIButton *)sender {
NSLog(#"Start");
}
- (IBAction)touchUpInsideButtonAction:(UIButton *)sender {
NSLog(#"End");
}
Handle your recording based on the actions!
Here is a screenshot just in case:
Hi I have some buttons that trigger action methods.
In addition, I would like the user to be able to swipe left or right over the area with the buttons to show some different buttons
My idea was to add a UIView with a clear background color on top of the buttons so that the user could still see the buttons but could also swipe to the right or left.
However, when the view is over the buttons the buttons no longer work and when it is under the buttons, the swipe is not detected.
In addition to changing the view color to clear, I also tried adjusting the alpha however this seems to work the same way. When alpha of the UIView is zero, swipes are no longer detected and when alpha is greater than zero, the buttons no longer work.
Can anyone suggest a way to do this?
Thanks in advance for any suggestions.
Code I am using for buttons and to detect swipes seems standard. Trying to figure out a way to expose view and buttons at the same time.
Action methods to which buttons are wired:
//.h file
- (IBAction)showIncoming:(id)sender;
- (IBAction)showOutcoming:(id)sender;
/.m file
- (IBAction)showIncoming:(id)sender {
_showIncoming=YES;
_fetchedResultsController=nil;
[self.tableView reloadData];
[self updateInterface];
}
- (IBAction)showOutgoing:(id)sender {
_showIncoming=NO;
_fetchedResultsController=nil;
[self.tableView reloadData];
[self updateInterface];
}
Swipes:
//in viewWillAppear
UISwipeGestureRecognizer *rightRecognizer = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:#selector(rightSwipeHandle:)];
rightRecognizer.direction = UISwipeGestureRecognizerDirectionRight;
[rightRecognizer setNumberOfTouchesRequired:1];
rightRecognizer.delegate = self;
[_topView addGestureRecognizer:rightRecognizer];
[_topView setUserInteractionEnabled:YES];
// [rightRecognizer release];
//........towards left Gesture recogniser for swiping.....//
UISwipeGestureRecognizer *leftRecognizer = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:#selector(leftSwipeHandle:)];
leftRecognizer.direction = UISwipeGestureRecognizerDirectionLeft;
[leftRecognizer setNumberOfTouchesRequired:1];
leftRecognizer.delegate = self;
[_topView addGestureRecognizer:leftRecognizer];
[_topView setUserInteractionEnabled:YES];
}
- (void)rightSwipeHandle:(UISwipeGestureRecognizer*)gestureRecognizer
{
//Do moving
NSLog(#"Right Swipe performed");
_showOld=YES;
[self updateInterface];
}
- (void)leftSwipeHandle:(UISwipeGestureRecognizer*)gestureRecognizer
{
// do moving
NSLog(#"Left Swipe performed");
_showOld=NO;
[self updateInterface];
}
The approach I suggest is listening to a user moving a finger events and firing specified method that would check if user's finger is on your button's view. Sample code:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
[self touchedViewWithTouches:touches andEvent:event];
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
[self touchedViewWithTouches:touches andEvent:event];
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
[self touchedViewWithTouches:touches andEvent:event];
}
- (void)didTouchedButton:(NSSet *)touches andEvent:(UIEvent *)event {
UITouch *touch = [[event allTouches] anyObject];
CGPoint touchLocation = [touch locationInView:touch.view];
UIView *touchedView;
for (UIView *view in self.subviews) {
if(CGRectContainsPoint(view.frame, touchLocation)) {
touchedView = view;
break;
}
}
if (view == self.showIncomingButton) {
[self showIncoming:self.showIncomingButton];
} else if (view == self.showOutcomingButton) {
[self howOutcoming:self.showOutcomingButton];
}
}
Note that you will have to add two outlets for your buttons (if you don't have any). Also, you can get rid off (id)sender in your IBAction calls.
I'm trying to make a quiz game. I turn the xib view into Image View to place one of my pre-made question images. After I placed the image I choose a button and place it on the image. However, the button completely supersedes the image. Even after I set the button type to custom, I can't see the image. The idea is just to place four invisible buttons and according to the answer proceed to the next question or display the wrong answer image. Thanks for any help.
Why not use a tap gesture recognizer?
Option 1
Step 1: Add tapGestureRecognizer to image views
for (UIImageView * v in #[v1, v2, v3, v4]) {
UITapGestureRecognizer * tap = [[UITapGestureRecognizer alloc]initWithTarget:self action:#selector(handleTap:)];
tap.numberOfTapsRequired = 1;
[self.view addGestureRecognizer:tap];
}
Step 2: Recieve Taps
- (void) handleTap:(UITapGestureRecognizer *)tap {
if (tap.view == v1) {
NSLog(#"V1 was tapped");
}
else if (tap.view == v2) {
NSLog(#"V2 was tapped");
}
else if (tap.view == v3) {
NSLog(#"V3 was tapped");
}
else if (tap.view == v4) {
NSLog(#"V4 was tapped");
}
}
Option 2 - Michael Knudsen's Answer
Here is the alternative method suggested by Michael
Step 1: Add tapGestureRecognizer to image views
UITapGestureRecognizer * tap = [[UITapGestureRecognizer alloc]initWithTarget:self action:#selector(handleTap:)];
tap.numberOfTapsRequired = 1;
[imageViewsParentView addGestureRecognizer:tap];
Step 2: Recieve Taps
- (void) handleTap:(UITapGestureRecognizer *)tap {
CGPoint positionInView = [tap locationInView:tap.view];
if (CGRectContainsPoint(v1.frame, positionInView)) {
NSLog(#"V1 was tapped");
}
else if (CGRectContainsPoint(v2.frame, positionInView)) {
NSLog(#"V2 was tapped");
}
else if (CGRectContainsPoint(v3.frame, positionInView)) {
NSLog(#"V3 was tapped");
}
else if (CGRectContainsPoint(v4.frame, positionInView)) {
NSLog(#"V4 was tapped");
}
}
You may be able to solve it by somehow making your view transparent. However, I would suggest another solution: Add a UITapGestureRecognizer and, whenever it senses a tap, check if its coordinates are inside one of the desired areas (corresponding to your imaginary buttons).
You can use touchesBegan method to detect which image was tapped.
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
if([touch view] == answerImg1) //answerImg1 as your first image
{
// Do whatever you want when first image tapped
}
else if([touch view] == answerImg2)
{
// Do whatever you want when second image tapped
}
else if([touch view] == answerImg3)
{
// Do whatever you want when third image tapped
}
else
{
NSLog(#"Other view tapped");
}
}
I tried using InterfaceBuilder to we the touchDownRepeat of my UISegmentedControl to an IBAction in its parent viewController, but the IBAction never fires no matter how many times in a row you tap on the UISegmentedControl. Why do they show this output from the UISegmentedControl in the pop-up menu in InterfaceBuilder, if it does nothing? How do you make it do what it's supposed to do? (Sorry if this is a noob question.)
Also, can each segment of the UISegmentedControl be directly referenced as an object? How about subclassing thereof? If not, why not, and what's a good workaround?
I want to have it where double-tapping a segment will launch a view for editing the mode represented by that segment. Thanks.
Why dont you add UIButtons on top of the segmented controls? You can detect the double tap on them with UIControlEventTouchDownRepeat and then manually select the segmented control with this:
- (void)setEnabled:(BOOL)enabled forSegmentAtIndex:(NSUInteger)segment
-(void)initTouchesRecognizer{
DLog(#"");
recognizer = [[UIGestureRecognizer alloc] init];
[self addGestureRecognizer:recognizer];
}
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
DLog(#"");
NSSet *allTouches = [event allTouches];
for (UITouch *touch in allTouches) {
}
}
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [[event allTouches] anyObject];
CGPoint location = [touch locationInView:touch.view];
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
DLog(#"");
}
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {
[self touchesEnded:touches withEvent:event];
}
This is the code for a gesture recognizer. Gesture recognizers are designed for UIViews, so really you can only have one gesture recognizer type per view. You can use single tap, double tap, triple tap, swipe, etc. all for different things on the same page but getting two of the same type to do two different things is a feat because they weren't designed to work like that. You should use buttons, like the previous answer said. I was trying to do the same thing and ended up using buttons instead, which turned out to be way more convenient. Even if you could get the gesture recognizer to work the way you wanted, it's very difficult and tedious. The code for a UIButton is WAY shorter.
EDIT
The gesture recognizer is typically used for things like making the keyboard disappear when the UIView is tapped; things like that. It's not supposed to be used as a set of custom buttons on a page.
EDIT #2
this is how you get it to recognize the gesture so that it calls the appropriate functions:
-(void)viewDidLoad {
[super viewDidLoad];
UIButton *button=[UIButton buttonWithType:UIButtonTypeRoundedRect];
button.frame=CGRectMake(0, 0, 100, 100);
[self.view addSubview:button];
UIGestureRecognizer *swipe=[[UIGestureRecognizer alloc]initWithTarget:button action:#selector(detectSwipe)];
[button addGestureRecognizer:swipe];
}
you have to initialize it and tell it where to go.
Actually I finally figured out this solution. Subclass the UISegmentedControl. Inside there:
- (customUISegmentedControl *)init {
NSTimeInterval interval = 0.0;
_lastSameTouchTime = [NSDate dateWithTimeIntervalSince1970:interval];
return self;
}
+ (BOOL)isIOS7 {
static BOOL isIOS7 = NO;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSUInteger deviceSystemMajorVersion = [[[[[UIDevice currentDevice] systemVersion] componentsSeparatedByString:#"."] objectAtIndex:0] intValue];
if (deviceSystemMajorVersion >= 7) {
isIOS7 = YES;
}
else {
isIOS7 = NO;
}
});
return isIOS7;
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
NSInteger previousSelectedSegmentIndex = self.selectedSegmentIndex;
[super touchesBegan:touches withEvent:event];
if (![[self class] isIOS7]) {
// before iOS7 the segment is selected in touchesBegan
if (previousSelectedSegmentIndex == self.selectedSegmentIndex) {
// if the selectedSegmentIndex before the selection process is equal to the selectedSegmentIndex
// after the selection process the superclass won't send a UIControlEventValueChanged event.
// So we have to do this in here.
NSTimeInterval diff = [[NSDate date] timeIntervalSinceDate:_lastSameTouchTime];
_lastSameTouchTime = [NSDate date];
if(diff <= 1.0)
NSLog(#"doubletapped!");
diff = [[NSDate date] timeIntervalSinceDate:_changedTime];
if(diff <= 1.0)
NSLog(#"doubletapped!");
}
else {
_changedTime = [NSDate date];
}
}
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
NSInteger previousSelectedSegmentIndex = self.selectedSegmentIndex;
[super touchesEnded:touches withEvent:event];
if ([[self class] isIOS7]) {
// on iOS7 the segment is selected in touchesEnded
if (previousSelectedSegmentIndex == self.selectedSegmentIndex) {
NSTimeInterval diff = [[NSDate date] timeIntervalSinceDate:_lastSameTouchTime];
_lastSameTouchTime = [NSDate date];
if(diff <= 1.0)
NSLog(#"doubletapped!");
diff = [[NSDate date] timeIntervalSinceDate:_changedTime];
if(diff <= 1.0)
NSLog(#"doubletapped!");
}
else {
_changedTime = [NSDate date];
}
}
}
Basically now replace the code NSLog(#"doubletapped!") with code that does whatever needs to happen when the doubletap has occurred :D And of course declare the relevant NSDate properties for _changedTime and _lastSameTouchTime.
I'm trying to make a vertical line from the top of my main view to the bottom, and it will trigger an action when someone swipes and crosses this line from either direction. I've already tried creating a tall thin label and putting a swipe recognizer inside of this, but it does not work the way I want. It only detects a swipe when you start inside of the label and swipe but not if you start outside the label and cross through it.
Here is my code so far,
UISwipeGestureRecognizer *swipe = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:#selector(doSomething)];
swipe.direction = UISwipeGestureRecognizerDirectionLeft | UISwipeGestureRecognizerDirectionRight;
swipe.numberOfTouchesRequired = 1;
[self.myLabel setUserInteractionEnabled:YES];
[self.myLabel addGestureRecognizer:swipe];
The easiest way is to use the touchesBegan: and touchesMoved: method on a UIView to decide if it's crossed the midway line. You would then just add a view (your line) on top of this view, and you may want to disable user interaction on your "line" view. BTW, a quick dirty way to create a line is to create a view that's 2px wide (or whatever width you want) and set it's background color to your "line" color...
You need to decide in touchesBegan: which side they started on, and then do something like this in touchesMoved::
- (void) touchesMoved: (NSSet *)touches withEvent:(UIEvent *)event {
CGPoint tappedPt = [[touches anyObject] locationInView: self];
if (tappedPt.x > self.frame.width/2 && self.userStartedOnLeftSide)
// User crossed the line...
else if (tappedPt.x < self.frame.width/2 && !self.userStartedOnLeftSide)
// User crossed the line...
else
// You probably don't need this else, the user DID NOT cross the line...
}
Similarly, you could do this in a UIViewController with a swipe gesture recognizer something like this:
- (void)swipe:(UISwipeGestureRecognizer *)recognizer{
CGPoint point = [recognizer locationInView:[recognizer view]];
if (recognizer.state == UIGestureRecognizerStateBegan)
// The user began their swipe at point
else if (recognizer.state == UIGestureRecognizerStateEnded)
// The user ended their swipe at point
// Add some logic to decide if they crossed the line...
}
If you're just trying to detect left and right swipes, you can put it on your main view like so:
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
UISwipeGestureRecognizer *swipe;
swipe = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:#selector(handleSwipe:)];
swipe.direction = UISwipeGestureRecognizerDirectionLeft;
[self.view addGestureRecognizer:swipe];
swipe = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:#selector(handleSwipe:)];
swipe.direction = UISwipeGestureRecognizerDirectionRight;
[self.view addGestureRecognizer:swipe];
}
- (void)handleSwipe:(UISwipeGestureRecognizer *)gesture
{
NSLog(#"%d", [gesture numberOfTouches]);
if (gesture.direction == UISwipeGestureRecognizerDirectionLeft)
{
NSLog(#"LEFT");
}
if (gesture.direction == UISwipeGestureRecognizerDirectionRight)
{
NSLog(#"RIGHT");
}
}
If you wanted to really test to see if you crossed the midpoint, you could use aUIPanGestureRecognizer:
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
[self.view addGestureRecognizer:[[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(handlePan:)]];
}
- (void)handlePan:(UIPanGestureRecognizer *)gesture
{
static CGPoint startLocation;
static BOOL startAtLeft;
CGPoint location = [gesture locationInView:gesture.view];
if (gesture.state == UIGestureRecognizerStateBegan)
{
startLocation = location;
startAtLeft = (location.x < (gesture.view.frame.size.width / 2.0));
}
else if (gesture.state == UIGestureRecognizerStateEnded)
{
BOOL nowAtLeft = (location.x < (gesture.view.frame.size.width / 2.0));
if (startAtLeft != nowAtLeft)
{
if (startAtLeft)
NSLog(#"swiped across midpoint, left to right");
else
NSLog(#"swiped across midpoint, right to left");
}
}
}
With the pan gesture, you can customize this to your heart's content (e.g. recognize gesture as soon as the user's finger crosses rather than waiting for them to end the gesture, etc.).