Slide from one button to another without lifting your finger - iphone

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

Related

How to hide drop down list when clicked outside in iphone

Programmaticaly how can we close or hide drop down list when clicked outside in iphone?
-(void)RecommendDropDownSelect
{
dropDown=[[DropDownView alloc]initWithArrayData:arr_RecommendName cellHeight:30 heightTableView:100 paddingTop:-100 paddingLeft:10 paddingRight:20 refView:txt_Recommend animation:BLENDIN openAnimationDuration:0.5 closeAnimationDuration:0.5];
dropDown.delegate=self;
[scr_View addSubview:dropDown.view];
[dropDown openAnimation];
btn_RecommendDropDown.enabled=NO;
}
-(void)dropDownCellSelected:(NSInteger)returnIndex
{
btn_RecommendDropDown.enabled=YES;
txt_Recommend.text=[arr_RecommendName objectAtIndex:returnIndex];
}
Besides subclassing UIView and override touchesBegan, using UITapGestureRecognizer seems to be easier if you are using UIViewController
First, setup a tap gesture for your view:
UITapGestureRecognizer *gestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(hideDropDown)];
[gestureRecognizer setCancelsTouchesInView:NO];
[self.view addGestureRecognizer:gestureRecognizer];
then implement the method for hiding the dropdown:
- (void)hideDropDown
{
if ((dropDown != nil) && (dropDown.view != nil))
{
[drdropDown.view removeFromSuperview];
}
}
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
if (touch.view==self.view) {
[dropDown removeFromSuperView];
}
}
or
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
if (touch.view==self.view) {
dropDown.alpha=0;
}
}
Try this:
-(void)touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event
{
if(dropDown)
{
[drdropDown.view removeFromSuperView];
}
}

touchesBegan and touchesMoved stuck

i am using this code in my app to recognize touch and drag on specific UIView,
now i have a problem that if i make the touch and immediate make a drag the touchesMoved called only 3-4 times and stop and then the touchesCancelled called, but if i touch the screen wait a second and then make a drag it call touchesMoved every time the finger move.
Edit
Ijust tested it on iphone 4s and with this device it work perfect,in ipod4 it still have the problem. both device are with 5.01 .
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
NSArray* touchArray = [touches allObjects];
UITouch* touch = [touchArray objectAtIndex:0];
CGPoint point = [touch locationInView:self.view];
UIView *tmp = [self.view hitTest:point withEvent:event];
if (tmp == volumeViewBackground) {
//do something
}else {
NSLog(#"err");
}
}
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event{
NSArray* touchArray = [touches allObjects];
UITouch* touch = [touchArray objectAtIndex:0];
CGPoint point = [touch locationInView:self.view];
UIView *tmp = [self.view hitTest:point withEvent:event];
if (tmp == volumeViewBackground) {
//do something
}else {
NSLog(#"err");
}
}
-(void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event{
NSLog(#"error");
}
Try calling super according to UIResponder Class Reference.
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
[super touchesBegan: touches withEvent: event];
NSArray* touchArray = [touches allObjects];
UITouch* touch = [touchArray objectAtIndex:0];
CGPoint point = [touch locationInView:self.view];
UIView *tmp = [self.view hitTest:point withEvent:event];
if (tmp == volumeViewBackground) {
//do something
}else {
NSLog(#"err");
}
}
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event{
[super touchesMoved: touches withEvent: event];
NSArray* touchArray = [touches allObjects];
UITouch* touch = [touchArray objectAtIndex:0];
CGPoint point = [touch locationInView:self.view];
UIView *tmp = [self.view hitTest:point withEvent:event];
if (tmp == volumeViewBackground) {
//do something
}else {
NSLog(#"err");
}
}
-(void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event{
[super touchesCancelled: touches withEvent: event];
NSLog(#"error");
}

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;

How would I drag UITextView around the view screen?

I have a project where a UITextView (for multilines) can be dragged around the screen. So far my solution to this has been an overlay of an invisible UIButton which when dragged its center is the same as the UITextView's center.
However I've seen apps that seem to just allow the UITextView to be dragged and edited on the fly so it seems there might not be an overlay in those but I'm not sure.
Thoughts?
By the way, c in this code is the UIButton and this is how I have moved it thus far:
- (void) draggedOut: (UIControl *) c withEvent: (UIEvent *) ev
{
if(self.interfaceOrientation == UIInterfaceOrientationPortrait)
{
c.center = [[[ev allTouches] anyObject] locationInView:self.view];
AddedText.center = c.center;
}
else if(self.interfaceOrientation == UIInterfaceOrientationPortraitUpsideDown)
{
c.center = [[[ev allTouches] anyObject] locationInView:self.view];
AddedText.center = c.center;
}
else if(self.interfaceOrientation == UIInterfaceOrientationLandscapeLeft)
{
c.center = [[[ev allTouches] anyObject] locationInView:self.view];
AddedText.center = c.center;
}
else if(self.interfaceOrientation == UIInterfaceOrientationLandscapeRight)
{
c.center = [[[ev allTouches] anyObject] locationInView:self.view];
AddedText.center = c.center;
}
}
- (void)panTextView:(UIPanGestureRecognizer *)recognizer {
NSLog(#"panning");
location1 = [recognizer translationInView:draggableTextView];
recognizer.view.center = CGPointMake(recognizer.view.center.x + location1.x,
recognizer.view.center.y + location1.y);
[recognizer setTranslation:CGPointMake(0,0) inView:draggableTextView];
location1 =[recognizer locationInView:draggableTextView];
NSLog(#"tranlation %#",NSStringFromCGPoint(location1));
[_imgpic addSubview:recognizer.view];
appDelegate.txt=draggableTextView.text;
}
call this method after creating textview.
Well have not been able to manipulate the actual uitextview.
First tried making a button overlay that could be moved and could be pressed to start editing, but it wasn't centered properly.
Then tried the above method to move the UITextView itself. But it would only work on touches or drags. (Note this was a modified form of touchesBegan & touchesMoved)
Ended up with a UIScrollView with the UITextView as a subview. Now it can move smoothly just that it can be moved from any place on the screen. Not optimal but is the best result to thus keep everything else intact.
Does the textView need to support scrolling? If so, this could get complicated.
But if not, there are two approaches. 1) subclass the textview and override touchesBegan, touchesMoved, touchesEnded. 2) write a gesture recognizer that processes the same messages and attach it to the textview.
Here's an example of a Gesture recognizer that will do the job:
#interface TouchMoveGestureRecognizer : UIGestureRecognizer
{
CGPoint _ptOffset;
}
#end
#implementation TouchMoveGestureRecognizer
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch* t = [touches anyObject];
_ptOffset = [t locationInView: self.view];
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch* t = [touches anyObject];
CGPoint pt = [t locationInView: self.view.superview];
pt.x -= _ptOffset.x;
pt.y -= _ptOffset.y;
CGRect r = self.view.frame;
r.origin = pt;
self.view.frame = r;
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
_ptOffset = CGPointMake(-1, -1);
}
#end
and, how to use it:
- (void)viewDidLoad {
[super viewDidLoad];
_textView.scrollEnabled = NO;
TouchMoveGestureRecognizer* gr = [[[TouchMoveGestureRecognizer alloc] init] autorelease];
[_textView addGestureRecognizer: gr];
}