Elegant way to implement press and hold continuous event firing? - objective-c

I often have a need to fire a sequence of events as a result of holding down a button. Think of a + button that increments a field: tapping it should increment it by 1, but tap & hold should say increment this by 1 every second until the button is released. Another example is the scrubbing function when holding down the backwards or forwards button in an audio player type app.
I usually resort to the following strategy:
On touchDownInside I set up a repeating timer with my desired interval.
On touchUpInside I invalidate and release the timer.
But for every such button I need a separate timer instance variable, and 2 target-actions, and 2 method implementations. (This is assuming I'm writing a generic class and don't want to impose restrictions on the max number of simultaneous touches).
Is there a more elegant way to solve this that I'm missing?

Register the events for every button by:
[button addTarget:self action:#selector(touchDown:withEvent:) forControlEvents:UIControlEventTouchDown];
[button addTarget:self action:#selector(touchUpInside:withEvent:) forControlEvents:UIControlEventTouchUpInside];
For each button, set the tag attribute:
button.tag = 1; // 2, 3, 4 ... etc
In the handler, do whatever you need. Identify a button by the tag:
- (IBAction) touchDown:(Button *)button withEvent:(UIEvent *) event
{
NSLog("%d", button.tag);
}

I suggest UILongPressGestureRecognizer.
UILongPressGestureRecognizer *longPress = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:#selector(addOpenInService:)];
longPress.delegate = self;
longPress.minimumPressDuration = 0.7;
[aView addGestureRecognizer:longPress];
[longPress release];
longPress = nil;
On firing the event, you can get the call in
- (void) addOpenInService: (UILongPressGestureRecognizer *) objRecognizer
{
// Do Something
}
Likewise you can use UITapGestureRecognizer for recognizing user taps.
Hope this helps. :)

Related

How to move an image with a button?

I have an image(clubcow.png) that is passed from an nsarray string to make a picture. Then facedowncow represents the picture. I was wondering how to make a button that will move facedowncow to position (100,100) when button is tapped. Any tips will be appreciated. Also, there is more to this code, I just posted the important parts to give an idea on what is going on.
cardKeys = [[NSArray alloc] initWithObjects:#"clubcow", nil];
currentName = [NSMutableString stringWithFormat:#"%#.png", [cowsShuffled objectAtIndex:currentcow]];
faceDowncow = (UIImageView *)[self.view viewWithTag:1];
faceDowncow.userInteractionEnabled = YES;
I would start by creating a UIButton and adding it to your view controller's view.
UIButton *button = [UIButton buttonWithType:UIButtonTypeRoundedRect];
button.frame = CGRectMake(0, 0, 100, 20);
[button setTitle:#"Tap Me" forState:UIControlStateNormal];
[button addTarget:self action:#selector(animateImage:)
forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:button];
Then, link this button to a function that will animate your faceDowncow object. You could add your faceDowncow as a property of the view controller so the following function can easily reference it:
- (void)animateImage:(UIButton *)sender {
[UIView animateWithDuration:0.2
animations:^{
// change origin of frame
faceDowncow.frame = CGRectMake(100, 100, faceDowncow.frame.size.width, faceDowncow.frame.size.height);
} completion:^(BOOL finished){
// do something after animation
}];
}
First of all, it looks like this code is from a view controller subclass, and the cow is a subview. In that case, you should probably have a property for it rather than obtaining it by its tag all the time. If it's instantiated in a storyboard scene/nib then you can hook up an outlet to a property/ivar in your subclass fairly easily.
The easiest way to do what you want is to create the button and use target action so that when it is tapped, it calls a method in your view controller. In the method body, obtain a reference to your cow and set it's frame property, like so:
[faceDowncow setFrame: CGRectMake(100,100,faceDowncow.bounds.size.width,faceDowncow.bounds.size.height)];
If you don't know how target action works, I suggest reading Apple's documentation on the matter. It's as simple as getting a button, calling one method to tell it what events should make a certain method get called, and then implementing that method.

Detecting touches on a UISlider?

I have a UISlider on screen, and I need to be able to detect when the user stops touching it. (so I can fade some elements away).
I have tried using:
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
but this did not work when ending touches on a slider.
You can detect when a touch ends using two control events; try
[slider addTarget:self action:#selector(touchEnded:)
forControlEvents:UIControlEventTouchUpInside];
or
[slider addTarget:self action:#selector(touchEnded:)
forControlEvents:UIControlEventTouchUpOutside];
If you want to detect both types of the touchesEnd event, use
[slider addTarget:self action:#selector(touchEnded:)
forControlEvents:UIControlEventTouchUpInside | UIControlEventTouchUpOutside];
Instead of using touchesEnded: (which shouldn't be used for this purpose anyway), attach an action to the UISlider's UIControlEventValueChanged event and set the continuous property of the UISlider to NO, so the event will fire when the user finishes selecting a value.
mySlider.continuous = NO;
[mySlider addTarget:self
action:#selector(myMethodThatFadesObjects)
forControlEvents:UIControlEventValueChanged];
I couldn't get anything to capture both the start and end of the touches, but upon RTFD-ing, I came up with something that will do both.
#IBAction func sliderAction(_ sender: UISlider, forEvent event: UIEvent) {
if let touchEvent = event.allTouches?.first {
switch touchEvent.phase {
case .began:
print("touches began")
sliderTouchBegan()
case .ended:
print("touches ended")
sliderTouchEnded()
default:
delegate?.sliderValueUpdated(sender.value)
}
}
}
sliderTouchBegan() and sliderTouchEnded() are just methods I wrote that handle animations when the touch begins and when it ends. If it's not a begin or end, it's a default and the slider value updates.

Objective C: Having buttons work inside a mutable array

I'm making a game that involves have buttons move across the screen until the user taps them. What I have so far is buttons moving across being generated randomly. My problem is when a new button is created, the last one stops moving. I'm trying to stick them into an array, but I'm not sure that's really gonna keep them moving.
-(void) generateStickFig:(NSTimer *)timer {
int x = random() % 100;
NSMutableArray *enemies = (NSMutableArray *)timer.userInfo;
if (x == 1) {
stickFig = [[UIButton alloc] initWithFrame:CGRectMake(0, 650, 50, 50)];
[stickFig setBackgroundColor:[UIColor blackColor]];
[stickFig addTarget:self action:#selector(tapFig:) forControlEvents:UIControlEventTouchUpInside];
// [enemies setObject:object forKey:[NSString stringWithFormat:#"object%i",i]];
[enemies addObject:stickFig];
int b = [enemies count];
[self.view addSubview:stickFig];
arrayNum++;
}
CGPoint oldPosition = stickFig.center;
stickFig.center = CGPointMake(oldPosition.x + 1 , oldPosition.y);
}
Where the buttons are stored is immaterial to moving them. You need to have a routine which animates them. You can do this by hand by moving them a small amount in response to a timer tick. The easiest method there would be fast enumeration over your array.
So you'd have the generation routine you have in your statement, a movement routine, and a touch IBAction handler which removes the button from the array. The generation routine, and the movement routine would both be called in your timer tick handler. (I tend to call that method "handleTick")
In high level psuedo-code it'd be something like this:
//tick handler
handleTick:
one out a hundred times, make a new button
give it a random starting location
store it in the buttons array
every time:
for button in buttons:
move button a few pixels
//button touch handler
buttonWasTouched:button :
[buttons removeObject: button];
I'm just getting into the system provided animation, so I don't know if your button can accept touches while being animated, but I wouldn't be surprised.
In order to know what button was touched from the array, you can use the tag option for the button:
- (IBAction) buttonTouched:(id) sender withEvent:(UIEvent *) event
{
UIButton *btn = (UIButton *)sender;
// in my case, I store the position on the array as a tag for the button
NSUInteger index = btn.tag;
UIControl *control = sender;
// Now you know what button was touched
// Apply actions on to it: remove from superview, animate explosion...
}
Cheers

target-action uicontrolevents

I must be missing something obvious here but ...
UIControl has a method
- (void)addTarget:(id)target action:(SEL)action forControlEvents: (UIControlEvents)controlEvents
which lets you add an action to be called when any of the given controlEvents occur. ControlEvents are a bitmask of events which tell you if a touch went down, or up inside, or was dragged etc., there's about 16 of them, you or them together and get called when any of them occur.
The selector can have one of the following signatures
- (void)action
- (void)action:(id)sender
- (void)action:(id)sender forEvent:(UIEvent *)
none of those tell you what the control event bitmask was. The UIEvent is something slightly different, it's related to the actual touch event and doesn't (I think) contain the UIControlEvent. The sender (UIControl) doesn't have a way to find the control events either.
I'd like to have one method which deals with a number of control events as I have some common code regardless of which event or events happened but I still need to know what the UIControlEvents were for some specific processing.
Am I missing a way to find out what UIControlEvents were used when the action was called or do I really have to separate my code into
- (void)actionWithUIControlEventX;
- (void)actionWithUIControlEventY;
I encountered the same problem, and came up with a solution. It's not amazingly pretty, but it works quite well. It is a UIControl category that stores the last UIControlEvent fired to its own tag property. You can get it from the link below. For further reference, here's the doc from my category, for a more detailed description of what's going on.
Hopefully this helps! Cheers/J
UIControl+CaptureUIControlEvents
git gist at: http://gist.github.com/513796
PROBLEM: upon firing, UIControlEvents are not passed into the target action
assigned to the particular event. This would be useful in order to have only
one action that switches based on the UIControlEvent fired.
SOLUTION: add a way to store the UIControlEvent triggered in the UIEvent.
PROBLEM: But we cannot override private APIs, so:
(WORSE) SOLUTION: have the UIControl store the UIControlEvent last fired.
The UIControl documentation states that:
When a user touches the control in a way that corresponds to one or more
specified events, UIControl sends itself sendActionsForControlEvents:.
This results in UIControl sending the action to UIApplication in a
sendAction:to:from:forEvent: message.
One would think that sendActionsForControlEvents: can be overridden (or
subclassed) to store the flag, but it is not so. It seems that
sendActionsForControlEvents: is mainly there for clients to trigger events
programatically.
Instead, I had to set up a scheme that registers an action for each control
event that one wants to track. I decided not to track all the events (or in
all UIControls) for performance and ease of use.
USAGE EXAMPLE:
On UIControl setup:
UIControlEvents capture = UIControlEventTouchDown;
capture |= UIControlEventTouchDown;
capture |= UIControlEventTouchUpInside;
capture |= UIControlEventTouchUpOutside;
[myControl captureEvents:capture];
[myControl addTarget:self action:#selector(touch:) forControlEvents:capture];
And the target action:
- (void) touch:(UIControl *)sender {
UIColor *color = [UIColor clearColor];
switch (sender.tag) {
case UIControlEventTouchDown: color = [UIColor redColor]; break;
case UIControlEventTouchUpInside: color = [UIColor blueColor]; break;
case UIControlEventTouchUpOutside: color = [UIColor redColor]; break;
}
sender.backgroundColor = color;
}
When you create your UIControl, set a value for the tag property. Then in your action function, you can determine the tag of the UIControl that called it using [sender tag]. Here's an example:
-(void)viewDidLoad {
UIButton *button1 = [[UIButton alloc] initWithFrame(CGRectMake(0.0,0.0,100.0,100.0)];
button1.tag = 42;
[button1 addTarget:self action:#selector(actionWithUIControlEvent:) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:button1];
UIButton *button2 = [[UIButton alloc] initWithFrame(CGRectMake(100.0,0.0,100.0,100.0)];
button2.tag = 43;
[button2 addTarget:self action:#selector(actionWithUIControlEvent:) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:button2];
}
-(void)actionWithUIControlEvent:(id)sender {
switch([sender tag]) {
case 42:
//Respond to button 1
break;
case 43:
//Respond to button 2
break;
default:
break;
}
}

NSButtonCell created programmatically doesn't highlight when clicked

I wanted to have a button appear in a table view as the first element on the last row instead of the normal data, so I created a subclass of NSTableView and overwrote the preparedCellAtColumn:row: method to generate the desired behaviour on the last row.
- (NSCell *)preparedCellAtColumn:(NSInteger)column row:(NSInteger)row {
if(row < [self numberOfRows]-1) {
return [super preparedCellAtColumn:column row:row];
} else {
// if we're on the last row, we're going to add the button instead of data
if(column == 0) {
NSButtonCell *button = [[NSButtonCell alloc] initTextCell:self.buttonTitle];
[button setEditable:YES];
[button setSelectable:YES];
[button setBezelStyle:NSTexturedSquareBezelStyle];
[button setGradientType:NSGradientConvexWeak];
[button setButtonType:NSMomentaryPushInButton];
[button setHighlightsBy:NSBackgroundStyleLowered];
[button setAction:#selector(openAddDialog)];
[button setControlView:self];
return button;
} else {
NSTextFieldCell *emptyCell = [[NSTextFieldCell alloc] initTextCell:#""];
[emptyCell setEditable:NO];
[emptyCell setSelectable:NO];
return emptyCell;
}
}
}
Fortunately, the functionality works--the button appears on the last row, and it calls the openAddDialog selector when it is clicked. There are two visual problems, however:
1. The button doesn't highlight (press down) when it's clicked.
2. The table selects the row with the button on it when it is clicked.
I'm still pretty new to Objective C and Cocoa, so I'm not really sure where to look for the solutions to these. Why isn't the button's setHighlighted method getting called when it is clicked? What method is called just before the NSTableView selects a row so I can override it and tell it not to select the last row?
I ended up solving my second question by overriding the selectRowIndexes:byExtendingSelection: function in my table subclass and simply not passing the last row index on to the super function.
I'm pretty sure you have to make the column editable in order to activate a UI element inside of it.
BTW, it's usually a bad idea to put some kind of control at the end of a table, especially one that affects the entire table. Tables should be composed of logical repeating units. Putting something unique in a table causes user confusion and can hide functionality. You most likely want to move the button outside the table itself.