Creating a realistic Strum on iPhone? - iphone

So I have a guitar application that plays chords, like an autoharp. I have created the following UIView class to turn a swipe into a strum as the user's fingers swipe across the strings.
#import "swipetest.h"
#implementation swipetest
#synthesize eStringButton, aStringButton, dStringButton, gStringButton, bStringButton, ehStringButton;
- (void)viewDidLoad {
[NSThread detachNewThreadSelector:#selector(isolateStrums) toTarget:self withObject:nil]; }
- (void)isolateStrums {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
[self performSelectorOnMainThread:#selector(touchesMoved) withObject:nil waitUntilDone:NO];
[pool release];
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
CGPoint location = [[touches anyObject] locationInView:self];
UIView *subview = [self hitTest:location withEvent:event];
if ([subview isKindOfClass:[UIButton class]])
{
if(CGRectContainsPoint(eStringButton.frame, location)) {
[eStringButton setHighlighted:YES];
[eStringButton sendActionsForControlEvents:UIControlEventTouchDown];
[NSTimer scheduledTimerWithTimeInterval:strumDelay target:self selector:#selector(ETimer) userInfo:nil repeats:NO];
}
else {
[eStringButton setHighlighted:NO];
}
if(CGRectContainsPoint(ehStringButton.frame, location)) {
[ehStringButton setHighlighted:YES];
[ehStringButton sendActionsForControlEvents:UIControlEventTouchDown];
}
else {
[ehStringButton setHighlighted:NO];
}
if(CGRectContainsPoint(bStringButton.frame, location)) {
[bStringButton setHighlighted:YES];
[bStringButton sendActionsForControlEvents:UIControlEventTouchDown];
}
else {
[bStringButton setHighlighted:NO];
}
if(CGRectContainsPoint(aStringButton.frame, location)) {
[aStringButton sendActionsForControlEvents:UIControlEventTouchDown];
[aStringButton setHighlighted:YES];
}
else {
[aStringButton setHighlighted:NO];
}
if(CGRectContainsPoint(dStringButton.frame, location)) {
[dStringButton setHighlighted:YES];
[dStringButton sendActionsForControlEvents:UIControlEventTouchDown];
}
else {
[dStringButton setHighlighted:NO];
}
if(CGRectContainsPoint(gStringButton.frame, location)) {
[gStringButton setHighlighted:YES];
[gStringButton sendActionsForControlEvents:UIControlEventTouchDown];
}
else {
[gStringButton setHighlighted:NO];
}
}
}
- (void)dealloc {
[eStringButton release];
[aStringButton release];
[bStringButton release];
[gStringButton release];
[dStringButton release];
[ehStringButton release];
[super dealloc];
}
#end
The problem is that the view registers rapid fire "hits" even at normal strum speeds. How can I slow down the interface response? I cannot use any commands that pause the NSthread. Is there a way to disable the UIButton for a predetermined period of time after it receives the UIControlEventTouchDown message, say for 0.1 seconds? Should I create individual threads for each string and pause those threads? Right now it kind of works, but can easily turn into the "matrix" sounding stutter guitar. Not good.

Is there a way to disable the UIButton
for a predetermined period of time
after it receives the
UIControlEventTouchDown message, say
for 0.1 seconds?
Sure - you can use an NSTimer to change the button's .userInteractionEnabled property. Just set it to 'NO' as soon as you detect your touch down, and fire a timer to set it back to 'YES' after a set number/fractions of seconds.

Related

How to remove UIImageView from another UIImageView

In touchesBegan method i added stampBrush Image to drawImage where both are UIImageView
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
stampBrush = [[UIImageView alloc] initWithImage:[[PaintColor stampImages] objectAtIndex:[stamp_Default integerForKey:STAMP_TYPE]]];
[stampBrush setFrame:CGRectMake(lastPoint.x, lastPoint.y, stampBrush.image.size.width,stampBrush.image.size.height)];
[drawImage addSubview:stampBrush];
}
Now i am trying to remove one by one on removeStampBrush clicked!
which stampBrush need to remove from drawImage!
-(void)removeStampBrush:(UIButton *)sender{
}
Since you want to remove the stamps in reverse order, I would extend UIImageView as follows:
YourImageView.h
#interface YourImageView : UIImageView {
NSMutableArray *stamps;
}
- (void)addStamp:(UIImageView *)stamp;
- (void)removeLastStamp;
#end
YourImageView.m
#import "YourImageView.h"
#implementation YourImageView
-(void)dealloc {
[stamps release];
[super dealloc];
}
- (void)addStamp:(UIImageView *)stamp {
if (stamps == nil) {
stamps = [[NSMutableArray array] retain];
}
[stamps addObject:stamp];
[self addSubview:stamp];
}
- (void)removeLastStamp {
if (stamps.count > 0) {
UIImageView *stamp = [stamps lastObject];
[stamp removeFromSuperview];
[stamps removeLastObject];
}
}
#end
Now from your touch event call [drawImage addStamp:stampBrush] and to remove the last one [drawImage removeLastStamp]
if([stampBrush superView])
{
[stampBrush removeFromSuperView];
}

increase a value while uibutton is kept pressing in iphone

I am trying to increase a variable's value while uibutton is kept pressing. But when user leaves button, increasing of variable's value will be stopped.
i have tried using threads with touch down and touchupinside but couldn't make it work.
-(void) changeValueOfDepthFields:(UIButton *)sender {
if (pressing)
pressing = NO;
else
pressing = YES;
pressingTag = 0;
while (pressing) {
[NSThread detachNewThreadSelector:#selector(increaseValue) toTarget:self withObject:nil];
}
}
- (void) stopValueChange:(UIButton *)sender {
pressing = NO;
}
[fStopUp addTarget:self action:#selector(changeValueOfDepthFields:) forControlEvents:UIControlEventTouchDown];
[fStopUp addTarget:self action:#selector(stopValueChange:) forControlEvents:UIControlEventTouchUpInside];
- (void) increaseValue {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
fstopVal = fstopVal + 0.1;
[self performSelectorOnMainThread:#selector(changeTextOfValues) withObject:nil waitUntilDone:YES];
[pool release];
}
- (void) changeTextOfValues {
fStopField.text = [NSString stringWithFormat:#"%.02f", fstopVal];
}
I wonder if there is an alternative way to do this or not. It seems very simple but couldn't think of any other solution than this one.
It is much easier to use an NSTimer.
- (void)changeValueOfDepthFields:(UIButton *)sender
{
if (!self.timer) {
self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(increaseValue) userInfo:nil repeats:YES];
}
}
- (void)stopValueChange:(UIButton *)sender
{
if (self.timer) {
[self.timer invalidate];
self.timer = nil;
}
}
- (void)increaseValue
{
fstopVal = fstopVal + 0.1;
fStopField.text = [NSString stringWithFormat:#"%.02f", fstopVal];
}
Note: The previous code is just for reference, I didn't do any memory management for example.

UILabel with UIMenucontroller not resigning first responder with touch outside

I have subclassed UILabel to provide a copy menu and would like to add some type of effect that makes the UILabel stand out when this menu is displayed.
Right now I am trying to add and remove a border. It works fine however if the user touches the label and then touches outside of the label the border won't disappear although the copy menu does.
After adding some NSLog's it seems like resignfirstresponder is not being called when this occurs. What happens in the responder chain when this happens and how can I get the border to disappear in this event?
Code as follows :
#implementation CopyLabel
- (BOOL)canPerformAction:(SEL)action withSender:(id)sender {
if(action == #selector(copy:)) {
return YES;
}
else {
return [super canPerformAction:action withSender:sender];
}
}
- (BOOL) canBecomeFirstResponder {
return YES;
}
- (BOOL)becomeFirstResponder {
if([super becomeFirstResponder]) {
self.highlighted = YES;
UIMenuController *menu = [UIMenuController sharedMenuController];
[menu setTargetRect:self.bounds inView:self];
[menu setMenuVisible:YES animated:YES];
return YES;
}
return NO;
}
- (BOOL)resignFirstResponder {
if([super resignFirstResponder]) {
self.highlighted = NO;
UIMenuController *menu = [UIMenuController sharedMenuController];
[menu setMenuVisible:NO animated:YES];
[menu update];
NSLog(#"Resign");
return true;
}
return false;
}
- (void)copy:(id)sender {
UIPasteboard *board = [UIPasteboard generalPasteboard];
[board setString:self.text];
self.highlighted = NO;
[self resignFirstResponder];
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
if([self isFirstResponder]) {
[self resignFirstResponder];
}
else if([self becomeFirstResponder]) {
} else {
[self resignFirstResponder];
}
}
- (void)drawRect:(CGRect)rect {
[super drawRect:rect];
self.layer.borderColor = [UIColor blueColor].CGColor;
self.layer.borderWidth = 0.0;
if(self.highlighted) {
self.layer.borderWidth = 1.0;
}
}
#end
UIMenuController posts a UIMenuControllerDidHideMenuNotification. When you listen for that notification (using NSNotificationCenter) you can send resignFirstResponder to your Label at the right time.
Example:
- (id)init... {
...
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(editMenuHidden)
name:UIMenuControllerDidHideMenuNotification
object:nil];
...
}
- (void)dealloc {
...
[[NSNotificationCenter defaultCenter] removeObserver:self
name:UIMenuControllerDidHideMenuNotification
object:nil];
...
}
- (void)editMenuHidden {
[self resignFirstResponder];
}
...

Rapid tap doesn't fire touchesEnded

Ok, so I am hoping someone can shed some light on this. The thing is I have an app where the user can interact with a number of "pads" that are kind of like big icons, that can be dragged around on the screen. Tapping once on the pad opens it and tapping and holding brings up an alert view which asks if you want to delete it. I am using a timer to check that a user is tapping and holding and invalidating the timer in the touchesMoved and touchesEnded events. The thing is that if you tap very quickly on a pad the touchesEnded event is never fired and as such the delete dialog appears, which can get a little confusing for a user. Any thoughts on why this is?
- (void) startTouchTimer:(float)delay
{
self.touchTimer = [NSTimer scheduledTimerWithTimeInterval:delay target:self selector:#selector(touchHeld:) userInfo:nil repeats:NO];
}
- (void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [[event allTouches] anyObject];
if (touches.count == 1)
{
for(WishPad *pad in self.view.subviews)
{
if ([touch view] == pad)
{
//Animate Selection
[UIView animateWithDuration:0.03
delay:0
options:UIViewAnimationOptionBeginFromCurrentState
animations:^{
pad.highlight.alpha = 0.5;
}
completion:NULL];
self.touchedPad = pad.padName;
[self startTouchTimer:0.40];
[self.view bringSubviewToFront:pad];
}
}
}
}
- (void) touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
[self.touchTimer invalidate];
UITouch *touch = [[event allTouches] anyObject];
if (touches.count == 1)
{
for(WishPad *pad in self.view.subviews)
{
if ([touch view] == pad)
{
CGPoint location = [touch locationInView:self.view];
pad.center =location;
}
}
}
}
- (void) touchesEnded:(NSSet *)touches withEvent:(UIEvent*)event
{
[self.touchTimer invalidate];
UITouch *touch = [[event allTouches] anyObject];
if (touches.count == 1)
{
for(WishPad *pad in self.view.subviews)
{
if ([touch view] == pad)
{
//Animate deselection
[UIView animateWithDuration:0.03
delay:0
options:UIViewAnimationOptionBeginFromCurrentState
animations:^{
pad.highlight.alpha = 0;
}
completion:NULL];
CGPoint location = pad.frame.origin;
NSString *position = NSStringFromCGPoint(location);
iWishAppDelegate *delegate = (iWishAppDelegate *)[[UIApplication sharedApplication] delegate];
for (Wish in [delegate.wishes objectForKey:#"<none>"])
{
if ([position isEqual:Wish.position] && [[(WishPad*)[touch view] padName] isEqualToString:Wish.name])
{
self.goToAddWish = [[AddWish alloc] initWithNibName:#"AddWish" bundle:nil];
self.goToAddWish.editWish=YES;
self.goToAddWish.hidesBottomBarWhenPushed=YES;
[self.navigationController pushViewController:self.goToAddWish animated:YES];
[self.goToAddWish editDetailText:Wish];
[self.goToAddWish release];
}
else if ([pad.padName isEqual:Wish.name])
{
Wish.position = position;
[delegate save];
}
}
}
}
}
}
- (void) touchHeld:(NSTimer*)timer
{
[self.touchTimer invalidate];
NSString *wishToDel = [NSString stringWithFormat:#"Delete %#?", self.touchedPad];
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:wishToDel message:#"Do you really want to delete this wish?" delegate:self cancelButtonTitle:#"Cancel" otherButtonTitles:#"Delete", nil];
[alertView show];
[alertView release];
}
- (void) alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
if (buttonIndex == [alertView cancelButtonIndex])
{
//Lots of deleting from plist and deleting image stuff happening here
}
You can try to use UIGestureRecognizers instead.
You would have to set up the gesture recognizer for all your pads
for(WishPad *pad in self.view.subviews)
{
UITapGestureRecognizer *tapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(padTapped:)];
[pad addGestureRecognizer:tapRecognizer];
[tapRecognizer release];
UILongPressGestureRecognizer *pressRecognizer = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:#selector(padPressed:)];
[pad addGestureRecognizer:pressRecognizer];
[pressRecognizer release];
}
and the you can handle the taps and presses
- (void)padTapped:(UITapGestureRecognizer *)recognizer {
WishPad *pad = (WishPad *)recognizer.view;
// handle tap for pad
}
- (void)padPressed:(UILongPressGestureRecognizer *)recognizer {
WishPad *pad = (WishPad *)recognizer.view;
// handle long press for pad
}
You can control how long a press should take to be recognized as a press using minimumPressDuration. But just read up on UIGestureRecognizer. It can save you a ton of work.

How to get UIMenuController work for a custom view?

I'm trying to get the following code work:
UIMenuController * menu = [UIMenuController sharedMenuController];
[menu setTargetRect: CGRectMake(100, 100, 100, 100) inView: self.view];
[menu setMenuVisible: YES animated: YES];
The menu instance is ready but it doesn't show - the width is always zero.
Or is there some sample code on this UIPasteboard/UIMenuController topic?
I was not able to get it working even when I read all of your answers. I'm presenting ready code that will work for everyone.
Let's say we have a controller class named Controller. You can simply paste the following code to this controller to have the menu working on its view:
- (void)loadView {
[super loadView];
UILongPressGestureRecognizer *gr = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:#selector(longPress:)];
[self.view addGestureRecognizer:gr];
}
- (void) longPress:(UILongPressGestureRecognizer *) gestureRecognizer {
if ([gestureRecognizer state] == UIGestureRecognizerStateBegan) {
CGPoint location = [gestureRecognizer locationInView:[gestureRecognizer view]];
UIMenuController *menuController = [UIMenuController sharedMenuController];
UIMenuItem *resetMenuItem = [[UIMenuItem alloc] initWithTitle:#"Item" action:#selector(menuItemClicked:)];
NSAssert([self becomeFirstResponder], #"Sorry, UIMenuController will not work with %# since it cannot become first responder", self);
[menuController setMenuItems:[NSArray arrayWithObject:resetMenuItem]];
[menuController setTargetRect:CGRectMake(location.x, location.y, 0.0f, 0.0f) inView:[gestureRecognizer view]];
[menuController setMenuVisible:YES animated:YES];
}
}
- (void) copy:(id) sender {
// called when copy clicked in menu
}
- (void) menuItemClicked:(id) sender {
// called when Item clicked in menu
}
- (BOOL) canPerformAction:(SEL)selector withSender:(id) sender {
if (selector == #selector(menuItemClicked:) || selector == #selector(copy:)) {
return YES;
}
return NO;
}
- (BOOL) canBecomeFirstResponder {
return YES;
}
What has to be done in order for menu to work is that the firstResponder(in our case our controller - see line with [self becomeFirstResponder]) has to be able to become first responder (override method canBecomeFirstResponder cause default implementation returns NO) as well as - (BOOL) canPerformAction:(SEL)selector withSender:(id) sender which should return YES to any action that can be performed by firstResponder
If you're implementing a custom view and that view is supposed to be the responder (as opposed to some other view, like a UITextField), you need to override the canBecomeFirstResponder function in your view and return YES:
- (BOOL)canBecomeFirstResponder {
return YES;
}
then, when you're displaying the menu, you should do something like the following:
- (void)myMenuFunc {
if (![self becomeFirstResponder]) {
NSLog(#"couldn't become first responder");
return;
}
UIMenuController *theMenu = [UIMenuController sharedMenuController];
CGRect selectionRect = CGRectMake(0, 0, 0, 0);
[theMenu setTargetRect:selectionRect inView:self];
[theMenu setMenuVisible:YES animated:YES];
}
In case somebody still has problems: My menu used to work and some day stopped working miraculously. Everything else in my app still worked. Now I had removed the [window makeKeyAndVisible] method from application:didFinishLaunchingWithOptions: method, and while everything else still worked, this breaks UIMenuController!
Stupid error on my side, difficult to find the culprit...
to display UIMenuController one has to add following
- (BOOL)canPerformAction:(SEL)action withSender:(id)sender
{
if (action == #selector(cut:))
return NO;
else if (action == #selector(copy:))
return YES;
else if (action == #selector(paste:))
return NO;
else if (action == #selector(select:) || action == #selector(selectAll:))
return NO;
else
return [super canPerformAction:action withSender:sender];
}
I think Cam is right, need override both canPerformAction and canBecomeFirstResponder
- (BOOL) canPerformAction:(SEL)action withSender:(id)sender
{
if (action == #selector(doSomething:)) {
return YES;
}
return NO;
}
- (BOOL)canBecomeFirstResponder {
return YES;
}
// MyView.h
#interface MyView : UIView {
IBOutlet UITextField * textField_;
}
#end
// MyView.m
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
NSLog(#"show menu");
[textField_ becomeFirstResponder];
// [self.window becomeFirstResponder];
UIMenuController * menu = [UIMenuController sharedMenuController];
[menu setTargetRect: CGRectMake(0, 0, 100, 10) inView: self];
[menu setMenuVisible: YES animated: YES];
NSLog(#"menu width %f, visible %d", menu.menuFrame.size.width, menu.menuVisible);
}
- (BOOL)canPerformAction:(SEL)action withSender:(id)sender{
return YES;
}
You need to add more code in canPerformAction:withSender: - it should check the pasteboard and your selection state. Apple's iPhone Application Programming Guide provide several code snippets.
Don't you have to add the UIMenuController* menu to the main or subview, E.G. self.view?
I think it's something like [self.view addSubView:menu.view]; Or am I missing the point of your question. You might also want to set the frame of the menu's view.
UIMenuController doesn't have a view. I just searched some code from apple's iPhone Application Programming Guide: Event Handling:
Listing 3-4 Displaying the editing menu
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *theTouch = [touches anyObject];
if ([theTouch tapCount] == 2 && [self becomeFirstResponder]) {
// selection management code goes here...
// bring up editing menu.
UIMenuController *theMenu = [UIMenuController sharedMenuController];
CGRect selectionRect = CGRectMake(currentSelection.x, currentSelection.y, SIDE, SIDE);
[theMenu setTargetRect:selectionRect inView:self];
[theMenu setMenuVisible:YES animated:YES];
}
}
I did it in the following way below. Just call the method that shows the menu after very short delay in init. I didn't want to call it from View Controller and also didn't find an event that indicates that my custom view appeared and i'm ready to show menu. So this way OK from my perspective. Delay can be less, but its up to you.
#implementation DTSignatureImageView
- (id)initWithImage:(UIImage *)image
{
self = [super initWithImage:image];
if(self){
self.contentMode = UIViewContentModeScaleAspectFit;
self.frame = CGRectMake(0, 0, image.size.width / 2.5, image.size.height / 2.5);
self.userInteractionEnabled = YES;
UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(signatureDidPan:)];
[self addGestureRecognizer:pan];
[self becomeFirstResponder];
[self performSelector:#selector(showMenu) withObject:nil afterDelay:0.5];
}
return self;
}
- (BOOL)canBecomeFirstResponder
{
return YES;
}
- (void)showMenu
{
UIMenuController *menu = [UIMenuController sharedMenuController];
menu.menuItems = #[
[[UIMenuItem alloc] initWithTitle:#"Apply" action:#selector(applySignature)],
[[UIMenuItem alloc] initWithTitle:#"Update" action:#selector(updateSignature)],
[[UIMenuItem alloc] initWithTitle:#"Clear" action:#selector(delegateSignature)]];
[menu setTargetRect:self.bounds inView:self];
[menu setMenuVisible:YES animated:YES];
}
- (NSArray *)menuActions
{
static NSArray *actions = nil;
if (actions == nil){
actions = #[
NSStringFromSelector(#selector(applySignature)),
NSStringFromSelector(#selector(updateSignature)),
NSStringFromSelector(#selector(delegateSignature))];
}
return actions;
}
- (void) signatureDidPan: (UIPanGestureRecognizer *)gesture
{
switch (gesture.state) {
case UIGestureRecognizerStateBegan: {
[[UIMenuController sharedMenuController] setMenuVisible:NO animated:YES];
break;
}
case UIGestureRecognizerStateEnded: {
[self becomeFirstResponder];
[self showMenu];
}
default:
break;
}
CGPoint point = [gesture locationInView:gesture.view.superview];
gesture.view.center = point;
}