Massive fps drops when AVAudioPlayer ist triggered repeatedly (Sprite Kit) - sprite-kit

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.

Related

How to detect in touchesEnded which nodes are still being pressed

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");
}

How to recognize oneTap/doubleTap at moment?

I Know filtering oneTap/doubleTap using a Apple API. code are follows.
UITapGestureRecognizer *doubleTapGestureRecognizer = [[UITapGestureRecognizer alloc]
initWithTarget:self action:#selector(handleDoubleTap:)];
doubleTapGestureRecognizer.numberOfTapsRequired = 2;
[self addGestureRecognizer:doubleTapGestureRecognizer];
UITapGestureRecognizer *singleTapGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(handleSingleTap:)];
singleTapGestureRecognizer.numberOfTapsRequired = 1;
**[singleTapGestureRecognizer requireGestureRecognizerToFail: doubleTapGestureRecognizer];**
[self addGestureRecognizer:singleTapGestureRecognizer];
but oneTap/doubleTap checkDelayTime is feeling a so Long (About 0.5sec?).
Generally App Users of the reaction is very fast. Although 0.5 seconds is typically short-time. but In Mobile Device Environment is long-time, because users react is very important.
Speaking to the point, YouTubeApp have a very Perfectly algorithm about filtering at a moment oneTap/doubleTap. oneTap-doubleTap checkDelay is VeryVeryShort Perfectly Optimization.
oneTap(show/hidden controlBar)
doubleTap(full/default videoScreenSize)
How to implement like YoutubeApp? about oneTap-doubleTap filtering Not Using a requireGestureRecognizerToFail Selector. about very short delay oneTap-doubleTap distinguishing.
I think YoutubeApp is Not Use a requireGestureRecognizer Selector.
The easiest way to do this is to subclass UITapGestureRecognizer and not a general UIGestureRecognizer.
Like this:
#import <UIKit/UIGestureRecognizerSubclass.h>
#define UISHORT_TAP_MAX_DELAY 0.2
#interface UIShortTapGestureRecognizer : UITapGestureRecognizer
#end
And simply implement:
#implementation UIShortTapGestureRecognizer
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
[super touchesBegan:touches withEvent:event];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(UISHORT_TAP_MAX_DELAY * NSEC_PER_SEC)), dispatch_get_main_queue(), ^
{
// Enough time has passed and the gesture was not recognized -> It has failed.
if (self.state != UIGestureRecognizerStateRecognized)
{
self.state = UIGestureRecognizerStateFailed;
}
});
}
#end
This is easiest to do without gesture recognizers. Then you can control the delay. The code below is a variation of Apple's original documentation that I use in one of my projects. I have blog post that talks about it as well.
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
UITouch *touch = [touches anyObject];
if (touch.tapCount == 2) {
//This will cancel the singleTap action
[NSObject cancelPreviousPerformRequestsWithTarget:self];
}
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event{
UITouch *touch = [touches anyObject];
if (touch.tapCount == 1) {
//if they tapped within the coin then place the single tap action to fire after a delay of 0.3
if (CGRectContainsPoint(coin.frame,[touch locationInView:self.view])){
//this is the single tap action being set on a delay
[self performSelector:#selector(onFlip) withObject:nil afterDelay:0.3];
}else{
//I change the background image here
}
} else if (touch.tapCount == 2) {
//this is the double tap action
[theCoin changeCoin:coin];
}
}
only thing you need to do is add extra line of code to use requireGestureRecognizerToFail
[singleTapRecognizer requireGestureRecognizerToFail:doubleTapRecognizer];
then whole code become to:
UITapGestureRecognizer *doubleTapRecognizer = [[[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(beginComicTransitions:)] autorelease];
doubleTapRecognizer.numberOfTapsRequired = 2;
doubleTapRecognizer.numberOfTouchesRequired = 1;
doubleTapRecognizer.delegate = self;
UITapGestureRecognizer *singleTapRecognizer = [[[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(bringMenu:)] autorelease];
singleTapRecognizer.numberOfTapsRequired = 1;
singleTapRecognizer.numberOfTouchesRequired = 1;
singleTapRecognizer.delegate = self;
[singleTapRecognizer requireGestureRecognizerToFail:doubleTapRecognizer];
here's requireGestureRecognizerToFail means:
if not recognized double tap, then single tap be recognized
if recognized double tap, will not recognize single tap
swift version code is:
let doubleTap = UITapGestureRecognizer(target: self, action: "doubleTapped:")
doubleTap.numberOfTapsRequired = 2
doubleTap.numberOfTouchesRequired = 1
self.scrollView.addGestureRecognizer(doubleTap)
let singleTap = UITapGestureRecognizer(target: self, action: "singleTap:")
singleTap.numberOfTapsRequired = 1
singleTap.numberOfTouchesRequired = 1
self.scrollView.addGestureRecognizer(singleTap)
singleTap.requireGestureRecognizerToFail(doubleTap)
Here is a simple custom gesture recognizers for double taps where you can specify the maximum allowed time between taps. This is based on #Walters answer.
PbDoubleTapGestureRecognizer.h :
#interface PbDoubleTapGestureRecognizer : UIGestureRecognizer
#property (nonatomic) NSTimeInterval maximumDoubleTapDuration;
#end
PbDoubleTapGestureRecognizer.m :
#import "PbDoubleTapGestureRecognizer.h"
#import <UIKit/UIGestureRecognizerSubclass.h>
#interface PbDoubleTapGestureRecognizer ()
#property (nonatomic) int tapCount;
#property (nonatomic) NSTimeInterval startTimestamp;
#end
#implementation PbDoubleTapGestureRecognizer
- (id)initWithTarget:(id)target action:(SEL)action {
self = [super initWithTarget:target action:action];
if (self) {
_maximumDoubleTapDuration = 0.3f; // assign default value
}
return self;
}
-(void)dealloc {
[NSObject cancelPreviousPerformRequestsWithTarget:self];
}
- (void)reset {
[super reset];
[NSObject cancelPreviousPerformRequestsWithTarget:self];
self.tapCount = 0;
self.startTimestamp = 0.f;
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
[super touchesBegan:touches withEvent:event];
if (touches.count != 1 ) {
self.state = UIGestureRecognizerStateFailed;
} else {
if (self.tapCount == 0) {
self.startTimestamp = event.timestamp;
[self performSelector:#selector(timeoutMethod) withObject:self afterDelay:self.maximumDoubleTapDuration];
}
self.tapCount++;
}
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
[super touchesMoved:touches withEvent:event];
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
[super touchesEnded:touches withEvent:event];
if (self.tapCount > 2) {
self.state = UIGestureRecognizerStateFailed;
} else if (self.tapCount == 2 && event.timestamp < self.startTimestamp + self.maximumDoubleTapDuration) {
[NSObject cancelPreviousPerformRequestsWithTarget:self];
NSLog(#"Recognized in %f", event.timestamp - self.startTimestamp);
self.state = UIGestureRecognizerStateRecognized;
}
}
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {
[super touchesCancelled:touches withEvent:event];
self.state = UIGestureRecognizerStateFailed;
}
- (void)timeoutMethod {
self.state = UIGestureRecognizerStateFailed;
}
#end
You can use it like this:
PbDoubleTapGestureRecognizer *doubleTapGr = [[PbDoubleTapGestureRecognizer alloc]initWithTarget:self action:#selector(_doubleTapAction)];
doubleTapGr.maximumDoubleTapDuration = 0.4;
[yourView addGestureRecognizer:doubleTapGr];
You can combine this with requireGestureRecognizerToFail: to get the behavior that was asked for.
Swift 3.1 version of eladleb's answer.
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent) {
super.touchesBegan(touches, with: event)
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) { [weak self] in
if self?.state != .recognized {
self?.state = .failed
}
}
}
Here's a simpler answer to this problem, if you already have an action attached to the button or view. First, change the IBAction so that it includes the UIEvent (don't forget to reconnect it to your button or view in the Storyboard):
-(IBAction)buttonAction:(id)sender forEvent:(UIEvent*)event
Next, in your you can capture the touches from the event, and then easily test for the tap count:
-(IBAction)buttonAction:(id)sender forEvent:(UIEvent*)event {
UITouch* firstTouch = nil;
if ((nil != ((firstTouch = event.allTouches.allObjects.firstObject)))
&& (2 == firstTouch.tapCount))
{
// do something for double-tap
} else {
// do something for single-tap
}
}
You can extend this solution with other cases for different event parameters, such as long taps.
#interface NaMeClass ()
#property (nonatomic, strong) UITapGestureRecognizer * singleTap;
#property (nonatomic, strong) NSTimer *timer;
#end
//...code...
//viewDidLoad
self.singleTap = [[UITapGestureRecognizer alloc]initWithTarget:self action:#selector(tapIcon:)];
self.singleTap.numberOfTapsRequired = 1;
self.singleTap.cancelsTouchesInView = YES;
self.singleTap.delaysTouchesBegan = YES;
[self addGestureRecognizer:self.singleTap];
//.....code
-(void)tapIcon:(UITapGestureRecognizer *)tapGesture
{
if (tapGesture.state == UIGestureRecognizerStateEnded){
if (!self.timer) {
self.timer = [NSTimer scheduledTimerWithTimeInterval:0.2
target:self selector:#selector(singleTap) userInfo:nil repeats:NO];
}else{
[self doubleTap];
}
}
}
-(void)singleTap{
[self.timer invalidate];
self.timer = nil;
NSLog(#"1111111111111");
}
-(void)doubleTap{
[self.timer invalidate];
self.timer = nil;
NSLog(#"22222222222");
}

Gesture detection on ios 3.1.3

In ios 3.1 and above how I can detect the Gesture in an UIView...
In ios >= 3.2.3 I use this code...(for example):
UISwipeGestureRecognizer *oneFingerSwipeLeft =
[[[UISwipeGestureRecognizer alloc] initWithTarget:self action:#selector(oneFingerSwipeLeft:)] autorelease];
[oneFingerSwipeLeft setDirection:UISwipeGestureRecognizerDirectionLeft];
[[self view] addGestureRecognizer:oneFingerSwipeLeft];
UISwipeGestureRecognizer *oneFingerSwipeRight =
[[[UISwipeGestureRecognizer alloc] initWithTarget:self action:#selector(oneFingerSwipeRight:)] autorelease];
[oneFingerSwipeRight setDirection:UISwipeGestureRecognizerDirectionRight];
[[self view] addGestureRecognizer:oneFingerSwipeRight];
Someone have an idea/example...
Thanks
You're going to have to subclass the UIView (or implement the stuff within the view controller if the layout isn't too complicated) and track the gestures you want for yourself using ye olde UIResponder methods touchesBegan:withEvent:, etc.
For example:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
if(!trackingTouch && [touches count] == 1)
{
trackingTouch = [touches anyObject];
startingPoint = [trackingTouch locationInView:relevantView];
}
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
if(trackingTouch && [touches containsObject:trackingTouch])
{
CGPoint endingPoint = [trackingTouch locationInView:relevantView];
trackingTouch = nil;
if(endingPoint.x < startingPoint.x)
NSLog(#"swipe left");
else
NSLog(#"swipe right");
}
}
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
{
[self touchesEnded:touches withEvent:event];
}
// don't really care about touchesMoved:withEvent:
That's an imperfect solution because it assumes that all finger down to finger up progressions are necessarily swipes. You'll probably want to implement some sort of maximum time duration or track velocities in touchesMoved:withEvent: or check that the touch moved at least a minimum distance. I think it's because people were making different decisions for all of those sorts of things that Apple ended up providing the UIGestureRecognizers.

Weird issue with touchesMoved with multiple touches

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;

Slide from one button to another without lifting your finger

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];
}