I have code in place which lets you tap buttons and play a sound for each button, as well as sliding your fingers across the buttons and having each button play it's corresponding sound as you enter the button's area. It's working pretty good, except for a strange bug:
If you press your fingers on two buttons at the same time it will play both sounds initially. However, if you continue leaving your fingers on the button, and then release your fingers off the screen while moving one of them ever so slightly (still within the confines of each button), it will play one of the sounds of the two buttons. How can I prevent this from happening? I only want the sounds to play if you're sliding a finger across the buttons or hitting them initially, not if you're just moving them slightly while hitting two buttons at the same time and then releasing your finger.
Below is my code:
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
NSSet *allTouches = [event allTouches];
touchesCount = [allTouches count];
for(UITouch *t in touches) {
CGPoint location = [t locationInView:t.view];
if(CGRectContainsPoint(soundButton1.frame, location))
{
if (!soundButton1.isHighlighted && touchesCount < 2){
soundID = #"0";
[self playSound];
[soundButton1 setHighlighted:YES];
}
}
else {
[soundButton1 setHighlighted:NO];
}
if(CGRectContainsPoint(soundButton2.frame, location))
{
if (!soundButton2.isHighlighted && touchesCount < 2){
soundID = #"1";
[self playSound];
[soundButton2 setHighlighted:YES];
}
}
else {
[soundButton2 setHighlighted:NO];
}
}
}
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
for(UITouch *t in touches) {
CGPoint location = [t locationInView:t.view];
if(CGRectContainsPoint(soundButton1.frame, location))
{
[soundButton1 setHighlighted:YES];
soundID = #"0";
[self playSound];
}
if(CGRectContainsPoint(soundButton2.frame, location))
{
[soundButton2 setHighlighted:YES];
soundID = #"1";
[self playSound];
}
}
}
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
for(UITouch *t in touches) {
CGPoint location = [t locationInView:t.view];
if(CGRectContainsPoint(soundButton1.frame, location))
{
[soundButton1 setHighlighted:NO];
}
if(CGRectContainsPoint(soundButton2.frame, location))
{
[soundButton2 setHighlighted:NO];
}
}
}
Try disabling the sounds for a short (very) period. In touchesEnded:withEvent:, check whether it has two touches, then do something like this,
soundDisabled = YES;
You can either request a selector to run after delay
[self performSelector:#selector(enableSound) withObject:nil afterDelay:0.2];
and then
- (void)enableSound {
soundDisabled = NO;
}
Or you could wait for the second finger to go up and then
soundDisabled = NO;
Related
I've been stuck on this problem for days. What I have is multiple SKSpriteNode's, one for a left arrow, right arrow and up arrow. When I hold down the right arrow I want my character to continue moving right while its being held down, on the other hand if you press the up Arrow then you will only jump once regardless of if you hold it down.
So my problem for example is when i hold the right arrow and then i press the up arrow, touchesEnded is called and it stops my character from moving right even though I still have my finger on the right arrow
-(void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
[super touchesBegan:touches withEvent:event];
for (UITouch *touch in touches){
CGPoint location = [touch locationInNode:self];
if (CGRectContainsPoint(rightArrow.frame, location)){
[wizard setTexture:[SKTexture textureWithImageNamed:#"wizardRight"]];
didTouchRightArrow = YES;
isLookingRight = YES;
isLookingLeft = NO;
rightArrow.alpha = 0.5;
NSLog(#"Touching right");
}
if (CGRectContainsPoint(leftArrow.frame, location)){
[wizard setTexture:[SKTexture textureWithImageNamed:#"wizardLeft"]];
isLookingRight = NO;
isLookingLeft = YES;
didTouchLeftArrow = YES;
leftArrow.alpha = 0.5;
NSLog(#"Touching left");
}
if (CGRectContainsPoint(upArrow.frame, location)){
didTouchUpArrow = YES;
upArrow.alpha = 0.5;
NSLog(#"Touching up");
}
-(void) touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
[super touchesBegan:touches withEvent:event];
if (rightArrow.alpha != 1.0){
rightArrow.alpha = 1.0;
}
if (leftArrow.alpha != 1.0){
leftArrow.alpha = 1.0;
}
if (upArrow.alpha != 1.0){
upArrow.alpha = 1.0;
for (UITouch *touch in touches){
CGPoint location = [touch locationInNode:self];
if (CGRectContainsPoint(rightArrow.frame, location)){
NSLog(#"Touching right");
didTouchRightArrow = YES;
} {
NSLog(#"Not touching right");
didTouchRightArrow = NO;
}
if (CGRectContainsPoint(leftArrow.frame, location)){
NSLog(#"Touching Left");
didTouchLeftArrow = YES;
} else {
NSLog(#"not touching left");
didTouchLeftArrow = NO;
}
didTouchUpArrow = NO;
}
This may not be the right way to approach the problem, but in touchesEnded I am trying to see if the touch is still in the desired Rect.
You need a way to identify the different nodes which are registering a touch. There is more than one way to do this but I have always found using the name property of a node to be the simplest and easiest to work with.
You already have the right idea by using the BOOLs to register the touch states.
I wrote some code to handle what you are trying to accomplish:
#import "GameScene.h"
#implementation GameScene {
SKSpriteNode *node0;
SKSpriteNode *node1;
BOOL node0touch;
BOOL node1touch;
}
-(void)didMoveToView:(SKView *)view {
self.backgroundColor = [SKColor blackColor];
node0 = [SKSpriteNode spriteNodeWithColor:[SKColor redColor] size:CGSizeMake(100, 100)];
node0.name = #"node0";
node0.position = CGPointMake(100, 300);
[self addChild:node0];
node1 = [SKSpriteNode spriteNodeWithColor:[SKColor blueColor] size:CGSizeMake(100, 100)];
node1.name = #"node1";
node1.position = CGPointMake(400, 300);
[self addChild:node1];
node0touch = false;
node1touch = false;
}
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
for (UITouch *touch in touches) {
CGPoint touchLocation = [touch locationInNode:self];
SKNode *node = [self nodeAtPoint:touchLocation];
if([node.name isEqualToString:#"node0"])
node0touch = true;
if([node.name isEqualToString:#"node1"])
node1touch = true;
}
}
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
for (UITouch *touch in touches) {
CGPoint touchLocation = [touch locationInNode:self];
SKNode *node = [self nodeAtPoint:touchLocation];
if([node.name isEqualToString:#"node0"])
node0touch = false;
if([node.name isEqualToString:#"node1"])
node1touch = false;
}
}
-(void)update:(CFTimeInterval)currentTime {
if(node0touch)
NSLog(#"node0 touch");
if(node1touch)
NSLog(#"node1 touch");
}
I am creating an iOS-Game in Sprite Kit in which you can fire a water gun. As long as the water gun is fired, a slash-sound is played. Since I implemented this feature, I have massive fps-problems when the sound effect ist triggered repeatedly with the touches-began-method.
Is there any possibility to fix that issue?
#property (nonatomic) AVAudioPlayer *splashSound;
-(id)initWithSize:(CGSize)size {
NSError *error3;
NSURL *splashURL = [[NSBundle mainBundle] URLForResource:#"splash" withExtension:#"caf"];
self.splashSound = [[AVAudioPlayer alloc]initWithContentsOfURL:splashURL error:&error3];
self.splashSound.numberOfLoops = 1;
[self.splashSound prepareToPlay];
}
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
/* Called when a touch begins */
startGamePlay = YES;
if (self.waterCapacity != 0){
waterSprayed = YES;
[self.splashSound play]; // sound starts when screen is touched
[self.currentWasser sprayWater];
}
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event{
[self.currentWasser removeActionForKey:#"water"];
[self.splashSound pause]; // sound is paused when touches ended
waterSprayed = NO;
}
Fixed it with an UILongPressGestureRecognizer: works perfectly. So happy!
- (void)didMoveToView:(SKView *)view
{
UILongPressGestureRecognizer *longGesture = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:#selector(delayedSplashSound:)];
longGesture.minimumPressDuration = 0.15;
[view addGestureRecognizer:longGesture];
}
- (void)delayedSplashSound:(UITapGestureRecognizer *)recognizer
{
if (recognizer.state == UIGestureRecognizerStateBegan) {
[self.splashSound play];
}else if (recognizer.state == UIGestureRecognizerStateEnded){
[self.splashSound pause];
[self.currentWater removeActionForKey:#"water"];
waterSprayed = NO;
}
}
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
startGamePlay = YES;
if (self.waterCapacity != 0){
waterSprayed = YES;
[self.currentWater sprayWater];
}
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event{
[self.currentWater removeActionForKey:#"water"];
waterSprayed = NO;
}
I have another work-around for the frame-drops when using AVAudioPlayer.play() and AVAudioPlayer.stop() with an infinite loop. Instead of calling play() and stop(), just call play() once and set yourPlayerInstance.volume=0.0 and yourPlayerInstance.volume=1.0. This solved the frame-glitches/-drops for me - perfect 60 fps instead of drops to ~40 fps.
I am using MHRotaryKnob to make Rotary dial like animation. I am using it for making old telephone dial pad.. At present it is just showing animation when i dial the pad it just come back with animation. even the first circle from lower not even getting touch detected and touch is working there also where there is no circle to dial. Below i am posting the code i am using.
- (BOOL)beginTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event
{
CGPoint point = [touch locationInView:self];
if (self.interactionStyle == MHRotaryKnobInteractionStyleRotating)
{
// If the touch is too close to the center, we can't calculate a decent
// angle and the knob becomes too jumpy.
if ([self squaredDistanceToCenter:point] < MinDistanceSquared)
return NO;
// Calculate starting angle between touch and center of control.
_angle = [self angleBetweenCenterAndPoint:point];
}
else
{
_touchOrigin = point;
_angle = [self angleForValue:self.value];
}
self.highlighted = YES;
[self showHighlighedKnobImage];
_canReset = NO;
return YES;
}
- (BOOL)handleTouch:(UITouch *)touch
{
if (touch.tapCount > 1 && self.resetsToDefault && _canReset)
{
[self setValue:self.defaultValue animated:YES];
return NO;
}
CGPoint point = [touch locationInView:self];
if (self.interactionStyle == MHRotaryKnobInteractionStyleRotating)
{
if ([self squaredDistanceToCenter:point] < MinDistanceSquared)
return NO;
// Calculate how much the angle has changed since the last event.
float newAngle = [self angleBetweenCenterAndPoint:point];
float delta = newAngle - _angle;
_angle = newAngle;
// We don't want the knob to jump from minimum to maximum or vice versa
// so disallow huge changes.
if (fabsf(delta) > 45.0f)
return NO;
self.value += (self.maximumValue - self.minimumValue) * delta / (MaxAngle*2.0f);
// Note that the above is equivalent to:
//self.value += [self valueForAngle:newAngle] - [self valueForAngle:angle];
}
else
{
self.value = [self valueForPosition:point];
}
return YES;
}
- (BOOL)continueTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event
{
if ([self handleTouch:touch] && self.continuous)
[self sendActionsForControlEvents:UIControlEventValueChanged];
return YES;
}
- (void)endTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event
{
self.highlighted = NO;
[self showNormalKnobImage];
// You can only reset the knob's position if you immediately stop dragging
// the knob after double-tapping it, i.e. when tracking ends.
_canReset = YES;
[self handleTouch:touch];
[self sendActionsForControlEvents:UIControlEventValueChanged];
[self setValue:self.defaultValue animated:YES];
}
I am trying to achieve that which circle is selected to dial that can be detected and only circles can be used to dial not outside the circles. Thanks in advance, any sort of help is appreciated.
I'm implementing touchesMoved, touchesBegan, and touchesEnded on a few UIButtons, so that I can slide my fingers over them and have them call the appropriate actions.
It seems to be working almost as intended, however, if I press two fingers outside of the two buttons' frames, and then slide them into the buttons' frames at the same time, the function within touchesMoved gets called multiple times. Instead, it should only call each button's function once while in the button's frame.
Below is my code.
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
for(UITouch *t in touches) {
CGPoint location = [t locationInView:t.view];
if(CGRectContainsPoint(Button1.frame, location))
{
if (!Button1.isHighlighted){
if(!button1Highlighted) {
[self doAction1];
}
[Button1 setHighlighted:YES];
button1Highlighted = YES;
}
}
else {
[Button1 setHighlighted:NO];
button1Highlighted = NO;
}
if(CGRectContainsPoint(Button2.frame, location))
{
if (!Button2.isHighlighted){
if(!button2Highlighted) {
[self doAction2];
}
[Button2 setHighlighted:YES];
button2Highlighted = YES;
}
}
else {
[Button2 setHighlighted:NO];
button2Highlighted = NO;
}
}
}
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
for(UITouch *t in touches) {
CGPoint location = [t locationInView:t.view];
if(CGRectContainsPoint(Button1.frame, location))
{
[Button1 setHighlighted:YES];
button1Highlighted = YES;
[self doAction1];
}
if(CGRectContainsPoint(Button2.frame, location))
{
[Button2 setHighlighted:YES];
button2Highlighted = YES;
[self doAction2];
}
}
}
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
for(UITouch *t in touches) {
CGPoint location = [t locationInView:t.view];
if(CGRectContainsPoint(Button1.frame, location))
{
[Button1 setHighlighted:NO];
button1Highlighted = NO;
}
if(CGRectContainsPoint(Button2.frame, location))
{
[Button2 setHighlighted:NO];
Button2Highlighted = NO;
}
}
}
Any help is greatly appeciated. Thanks!
If -touchesMoved: is getting called with multiple touches such that there's one touch on Button1 and another on Button2 and both buttons are not highlighted, then the touch over Button1 will highlight Button1 and unhighlight Button2. Meanwhile, in the same loop in the same call to -touchesMoved:, the touch over Button2 will essentially reset Button1's highlight state back to unhighlighted.
-touchesMoved: will be called as long as there are touches, and each call will cycle the two buttons again.
Maybe you need to add a 'hasBeenHighlighted' property to your buttons. I am not sure how best to initialize this property to NO for all your buttons. But it would need to be set to YES inside -setHighlighted: and it would need to be checked before calling -doActionX.
I am not sure that I understand exactly what you are trying to achieve, but I hope this is of some help.
I ended up getting it to work by storing the number of touches on the screen in a variable called touchesCount. I then increment it in touchesBegan, and decrease it in touchesEnded. Then in the touchesMoved before calling doActionX, I checked to make sure touchesCount < 2.
I have two buttons (Button 1 and button 2) and if I press button 1, note1 starts and if i press button 2, note 2 starts.
So what I try to do is: press button one (note1 starts) and SLIDE to button2 and than should note2 start. As in, you slide without lifting your finger over a piano keyboard and all notes from c to h sound.
I did this with UIImageViews and I did it also with UIButtons.
If i press the c1pianoview it sounds a "c". If I press the d1pianoview it sounds a "d".
But if I slide without lifting my finger from c1pianoview to d1pianoview it only sounds a "c". What did I wrong? Can I also do this with UIButtons?
Touch down works but sliding doesn't work.
Can somebody help me, please? Here my code:
-(void)touchesBeganNSSet *)touches withEventUIEvent *)event
{
UITouch *touch = [[event allTouches] anyObject];
CGPoint location = [touch locationInView:touch.view];
if(CGRectContainsPoint(c1pianoview.frame, location))
{
NSString *path = [[NSBundle mainBundle]
pathForResource: #"c1piano" ofType:#"mp3"];
AVAudioPlayer* theAudio = [[AVAudioPlayer alloc]
initWithContentsOfURL:[NSURL fileURLWithPathath] error:NULL];
[theAudio play];
}
if(CGRectContainsPoint(d1pianoview.frame, location))
{
NSString *path = [[NSBundle mainBundle]
pathForResource: #"d1piano" ofType:#"mp3"];
AVAudioPlayer* theAudio=[[AVAudioPlayer alloc]
initWithContentsOfURL: [NSURL fileURLWithPathath] error:NULL];
[theAudio play];
}
}
Update:
I have a further question. Now I have two UIImageVIews on each other. A black piano key over two white piano keys. If I press on the black key it also sound the note from the white key under it.
How do I prevent this?
I think a simpler solution is to create a superview that intercepts all the touches and decides what to do. Then in IB, you make your view an instance of PianoView, and make all the keys subviews. Each key would have a tag that identifies which key it is, so PianoView knows what note to play. PianoView has a currentView property which keeps track of the last view in touchesMoved so it doesn't keep trying to play the same key. I have a keyboard with similar but different requirements, and this approach works well.
The sample code below intercepts touches in hitTest:withEvent:. Then, in the touches* methods, it uses UIView's default implementation of hitTest:withEvent: to determine which key is being played. You'll have to modify it a bit if you want to support playing keys simultaneously with multi-touch.
#implementation PianoView
-(UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
// intercept touches
if ([self pointInside:point withEvent:event]) {
return self;
}
return nil;
}
-(void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
UIView* view = [super hitTest: [[touches anyObject] locationInView: self] withEvent: nil];
// highlight subview under touch
if (view != nil && view != self) {
if ([view isKindOfClass:[UIButton class]]) {
[(UIControl *)view setHighlighted:YES];
}
[self setCurrentView:view];
[self playKey:view];
}
}
-(void) touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {
// un-highlight everything
for (UIView *subview in [self subviews]) {
if ([subview isKindOfClass:[UIButton class]]) {
[(UIControl *)subview setHighlighted:NO];
}
}
[self stopPlaying];
[self setCurrentView:nil];
}
-(void) touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
UIView* view = [super hitTest: [[touches anyObject] locationInView: self] withEvent: nil];
if (view != [self currentView]) {
UIView *oldKey = [self currentView];
// un-highlight
if ([oldKey isKindOfClass:[UIButton class]]) {
[(UIControl *)oldKey setHighlighted:NO];
}
if ([view isKindOfClass:[UIButton class]]) {
[(UIControl *)view setHighlighted:YES];
}
[self stopPlaying];
[self playKey:view];
[self setCurrentView:view];
}
}
-(void) touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
UIView* view = [super hitTest: [[touches anyObject] locationInView: self] withEvent: nil];
for (UIView *subview in [self subviews]) {
if ([subview isKindOfClass:[UIButton class]]) {
[(UIControl *)subview setHighlighted:NO];
}
}
[self stopPlaying];
[self setCurrentView:nil];
}
#end
[Merged further question into original post]
Don't you need the:
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
You can probably achieve this easily by using the "Touch Down" and "Touch Drag Inside" events of the UIButton.
If you want to use the -touchesMoved: approach, you can use a variable to keep track of the last button that triggered a sound and only play a sound if the current button is not the one that last played a sound.
In more detail:
Declare a UIView *lastButton; in your header and then:
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [[event allTouches] anyObject];
CGPoint location = [touch locationInView:touch.view];
if(CGRectContainsPoint(c1pianoview.frame, location) && lastButton != c1pianoview) {
//play c1
lastButton = c1pianoview;
}
else if(CGRectContainsPoint(d1pianoview.frame, location) && lastButton != d1pianoview) {
//play d1
lastButton = d1pianoview;
}
}
- (void)touchedEnded:(NSSet *)touches withEvent:(UIEvent *)event {
lastButton = nil;
}
- (void)touchedCancelled:(NSSet *)touches withEvent:(UIEvent *)event {
[self touchesEnded:touches withEvent:event];
}