Closed. This question does not meet Stack Overflow guidelines. It is not currently accepting answers.
This question appears to be off-topic because it lacks sufficient information to diagnose the problem. Describe your problem in more detail or include a minimal example in the question itself.
Closed 9 years ago.
Improve this question
I have a button set up as an IBAction that i was to disable after it's pressed and re-enable it after 12 seconds. I have a bunch of timers set up in the IBAction. It's called moodButton. How do I disable this?
I like the flexibility of #iBuntyM's answer.
Here's an even more flexible approach, using Grand Central Dispatch:
- (IBAction)buttonPressed:(id)sender {
UIButton *button = (UIButton *)sender;
button.enabled = NO;
dispatch_after(
dispatch_time(DISPATCH_TIME_NOW, 12 * NSEC_PER_SEC),
dispatch_get_main_queue(), ^{
button.enabled = YES;
});
}
EDIT Of course alot can happen in 12 seconds and when the timer fires you might find you don't want to re-enable the button any more. Also it's difficult (impossible?) to cancel a dispatch timer if these conditions change before it fires.
Therefore perhaps using an NSTimer, stored in an instance variable, is a better approach as it can be cancelled (invalidated) if you change your mind/conditions change.
For example:
.h file:
#interface MyView : UIView {
NSTimer *_reenableButtonTimer;
}
#end
.m file:
- (IBAction)buttonPressed:(id)sender {
UIButton *button = (UIButton *)sender;
_reenableButtonTimer = [NSTimer scheduledTimerWithTimeInterval:12.0f
target:self
selector:#selector(reenableButton:)
userInfo:button
repeats:NO];
}
- (void)reenableButton:(NSTimer *)timer {
UIButton *button = (UIButton *)[timer userInfo];
button.enabled = YES;
}
The _reenableButtonTimer can then be invalidated before fire time to cancel this re-enabling.
On press action disable the UIButton and then you can use performSelector:withObject:afterDelay
call another function and enable the UIButton again with afterDelay pass the delay time.
- (IBAction)buttonPressed:(UIButton *)sender {
[sender setEnabled:YES];
[self performSelector:#selector(enableButton:) withObject:sender afterDelay:12.0];
}
-(void)enableButton:(UIButton *)button {
[button setEnabled:NO];
}
As there is no need to create IBOutlet for UIButton as another answer doing you can pass the UIButton object and change the property as required.
Try with following code;
-(IBAction) buttonClickEvent:(UIButton *) sender
{
sender.enable = NO;
[NSTimer scheduledTimerWithTimeInterval:12.f target:self selector:#selector(moodButton:) userInfo:nil repeats:NO];
}
Code of NSTimer method
- (void)moodButton:(NSTimer *)theTimer
{
self.moodButton.enable = YES;
}
Implemented the following:
- (IBAction)buttonPressed:(UIButton *)button {
button.enabled = NO; // Same as setting enabled property to self.moodButton
[self performSelector:#selector(enableMoodButtonButton) withObject:button afterDelay:12.0];
}
- (void)enableMoodButtonButton:(UIButton *)button {
button.enabled = YES;
}
You can put things conditional into your IBAction like
-(IBAction)pushMe {
if(isThisActionAvailable) {
[self doMyWorkNow];
}
}
Related
I have a UIViewController with a bunch of buttons that each have a (unique) tag. I wrote the following method:
- (void) highlightButtonWithTag: (NSInteger) tag
{
UIButton *btn = (UIButton *)[self.view viewWithTag: tag];
btn.highlighted = YES;
}
What I am trying to do is have a bunch of buttons that each function like a toggle: when I tap one, it should be come active (i.e. highlighted) and the one that was highlighted before should become "un"highlighted.
When the view comes up, I use the viewDidAppear method to set the initial selection:
- (void) viewDidAppear:(BOOL)animated
{
self.selectedIcon = 1;
[self highlightButtonWithTag: self.selectedIcon];
}
And this seems to work just fine: when the view comes up, the first button is selected. However, when I try to update stuff through the #selector connected to the buttons, the previous button is "un"highlighted but the button with sender.tag doesn't get highlighted.
- (IBAction) selectIcon:(UIButton *)sender
{
// "Un"highlight previous button
UIButton *prevButton = (UIButton *)[self.view viewWithTag: self.selectedIcon];
prevButton.highlighted = NO;
// Highlight tapped button:
self.selectedIcon = sender.tag;
[self highlightButtonWithTag: self.selectedIcon];
}
What am I missing here?
The problem is that the system automatically highlights then unhighlights the button on touchDown and touchUp respectively. So, you need to highlight the button again, after it's unhighlighted by the system. You can do by using performSelector:withObject:afterDelay: even with a 0 delay (because the selector is scheduled on the run loop which happens after the system has done it's unhighlighting). To use that method, you have to pass an object (not an integer), so If you modify your code slightly to use NSNumbers, it would look like this,
- (void) highlightButtonWithTag:(NSNumber *) tag {
UIButton *btn = (UIButton *)[self.view viewWithTag:tag.integerValue];
btn.highlighted = YES;
}
- (void) viewDidAppear:(BOOL)animated {
self.selectedIcon = 1;
[self highlightButtonWithTag: #(self.selectedIcon)];
}
- (IBAction) selectIcon:(UIButton *)sender {
// "Un"highlight previous button
UIButton *prevButton = (UIButton *)[self.view viewWithTag: self.selectedIcon];
prevButton.highlighted = NO;
// Highlight tapped button:
self.selectedIcon = sender.tag;
[self performSelector:#selector(highlightButtonWithTag:) withObject:#(self.selectedIcon) afterDelay:0];
}
Closed. This question does not meet Stack Overflow guidelines. It is not currently accepting answers.
This question appears to be off-topic because it lacks sufficient information to diagnose the problem. Describe your problem in more detail or include a minimal example in the question itself.
Closed 8 years ago.
Improve this question
I have a UIButton in Objective-C and I want to cancel the press if the button is let go before 10 seconds. How would I go about this?
Thanks In Advance
You can use something like this:
[yourButton addGestureRecognizer:[[UILongPressGestureRecognizer alloc]
initWithTarget:self action:#selector(handle:)]];
Create an NSTimer property called timer:
#property (nonatomic, strong) NSTimer *timer;
And a counter:
#property (nonatomic, strong) int counter;
- (void)incrementCounter {
self.counter++;
}
- (void)handle:(UILongPressGestureRecognizer)gesture {
if (gesture.state == UIGestureRecognizerStateBegan) {
self.counter = 0;
self.timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:#selector(incrementCounter) userInfo:nil repeats:yes];
}
if (gesture.state == UIGestureRecognizerStateEnded) {
[self.timer invalidate];
}
}
So when the gesture begins start a timer that fires the incrementation method every second until the gesture ends. In this case you'll want to set the minimumPressDuration to 0 otherwise the gesture won't start straight away.
In this way you can get your timer and put a condition on button pressed with self.counter = 10 seconds.
Please let me know if it helps.
To stick with a UIButton's intended design, I would suggest utilizing a UIButton's UIControlEventTouchUpInside and UIControlEventTouchDown control events.
(hint: -addTarget:action:forControlEvents: method)
Logically:
Start a timer on UIControlEventTouchDown
Stop the timer on UIControlEventTouchUpInside
In the UIControlEventTouchUpInside action method
check the timer duration (if 10seconds then perform action else don't)
Example:
Prerequisites:
Declare following objects (in the .h file of the class)
UIButton *btnTest;
NSTimer *tmrTest;
int i_tmrCount;
Code:
- (void)viewDidLoad
{
//...
i_tmrCount = 0;
btnTest = [UIButton buttonWithType:UIButtonTypeCustom];
[btnTest setFrame:CGRectMake(20, 100, 100, 30)];
[btnTest setTitle:#"Test" forState:UIControlStateNormal];
//method connected to the button pressed down event alone
[btnTest addTarget:self action:#selector(btnTestActStart:)
forControlEvents:UIControlEventTouchDown];
//method connected to the button pressed down and released event
[btnTest addTarget:self action:#selector(btnTestAct:)
forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:btnTest];
}
- (void)btnTestActStart:(UIButton *)sender
{
tmrTest = [NSTimer scheduledTimerWithTimeInterval:1
target:self
selector:#selector(timerDo)
userInfo:nil
repeats:YES];
}
-(void)timerDo
{
i_tmrCount++;
}
- (void)btnTestAct:(UIButton *)sender
{
//stop the timer
[tmrTest invalidate];
if (i_tmrCount >= 10) {
//do something
NSLog(#"action");
} else {
//do nothing
NSLog(#"reset");
}
//reset i_tmrCount to initial state (to handle next occurrence)
i_tmrCount = 0;
}
PS: If it were a UILabel then go with UILongPressGestureRecognizer but for a UIButton, it won't be ideal because you'll have to drop the implementation of the method connected to the button's UIControlEventTouchUpInside and instead make the method connected to the UILongPressGestureRecognizer as the main button method (it feels kinda dirty)
EDIT
Maybe I have described the problem incorrectly. Let me add that this segmented controller is controlling the speed of a NSTimer with the following code.
- (IBAction) segmentAction:(id)sender
{
speed = [[speeds objectAtIndex:[sender selectedSegmentIndex]] integerValue];
}
- (IBAction)startPause: (UIButton *)sender{
NSString *buttonTitle = sender.currentTitle;
if ([buttonTitle isEqualToString: #"Pause"]) {
[self.myButton setTitle:#"Resume" forState:UIControlStateNormal];
[timer invalidate];
}
else
{
[self.myButton setTitle:#"Pause" forState:UIControlStateNormal];
if ([self.deal length]>cardNum) {
timer = [NSTimer scheduledTimerWithTimeInterval:speed
target:self
selector:#selector(dealCard)
userInfo:nil
repeats:YES];
}
else
{
[timer invalidate];
}
}
}
When the user taps any of the segments in the segmentedController BEFORE tapping the startPause button, the NSTimer works fine. Nut if the user taps the startPause button before tapping a segment, no (desired) delays occur from the NSTimer. Does this help explain the situation any better?
EDIT
I have a UISegmentedControl in Storyboard and want to initialize it. It would be especially nice to initialize it in the Storyboard itself. The image below shows how the Med segment is selected, but when the app starts, Med is NOT selected; rather the user has to trigger a Value changed event. How do I get it selected, initially? One of the other questions here on SO suggested the following code, but I have not figured out how to associate the UISegmentedControl in my Storyboard with the name segmentControl.
segmentControl.selectedSegmentIndex=-1;
I drag 2 IBActions from a UIButton, one with touchDown event and second with drag Inside.
- (IBAction)clickButton:(UIButton *)sender {
NSLog(#"Click Button");
}
- (IBAction)dragInsideButton:(UIButton *)sender {
NSLog(#"Drag Button");
}
But when I drag inside, the touchDown action also gets fired.
How to disable touchDown event when dragInside.
Thanks!
i have solved a problem like this with using drag Events
add events to your button in .xib file or programatically.
programmatically is:
[mybut addTarget:self action:#selector(dragBegan:withEvent: )
forControlEvents: UIControlEventTouchDown];
[mybut addTarget:self action:#selector(dragMoving:withEvent: )
forControlEvents: UIControlEventTouchDragInside];
[mybut addTarget:self action:#selector(dragEnded:withEvent: )
forControlEvents: UIControlEventTouchUpInside |
UIControlEventTouchUpOutside];
then defininitons of events are:
- (void) dragBegan: (UIButton *) c withEvent:ev
{
NSLog(#"dragBegan......");
count=NO;//bool Value to decide the Down Event
c.tag=0;
[self performSelector:#selector(DownSelected:) withObject:mybut afterDelay:0.1];
//user must begin dragging in 0.1 second else touchDownEvent happens
}
- (void) dragMoving: (UIButton *) c withEvent:ev
{
NSLog(#"dragMoving..............");
c.tag++;
}
- (void) dragEnded: (UIButton *) c withEvent:ev
{
NSLog(#"dragEnded..............");
if (c.tag>0 && !count)
{
NSLog(#"make drag events");
}
}
-(void)DownSelected:(UIButton *)c
{
if (c.tag==0) {
NSLog(#"DownEvent");
count=YES;//count made Yes To interrupt drag event
}
}
This method is tested, and should do what I think you're trying to do. You can change the delay in the timer to get the effect you want. I had to connect 3 actions to the button to make this work -- the third action is a touchUp that resets the system to the starting condition.
#interface LastViewController ()
#property (nonatomic) BOOL touchedDown;
#property (strong,nonatomic) NSTimer *downTimer;
#end
#implementation LastViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.touchedDown = NO;
}
-(IBAction)clickDown:(id)sender {
self.downTimer = [NSTimer scheduledTimerWithTimeInterval:.3 target:self selector:#selector(buttonAction:) userInfo:nil repeats:NO];
}
-(IBAction)dragInside:(id)sender {
[self.downTimer invalidate];
[self buttonAction:self];
}
-(void) buttonAction:(id) sender {
if ([sender isKindOfClass:[NSTimer class]]) {
self.touchedDown = YES;
NSLog(#"click down");
}else{
if (! self.touchedDown) {
NSLog(#"Drag");
}
}
}
-(IBAction)touchUpAction:(id)sender {
self.touchedDown = NO;
}
Try this way:
isClicked is property of type BOOL. Set to YES.
- (IBAction)clickButton:(UIButton *)sender { //touch down
if(isClick==YES){
NSLog(#"Click Button");
//do all your stuffs here
}
isClick=YES;
}
- (IBAction)dragInsideButton:(UIButton *)sender {
NSLog(#"Drag Button");
isClick=NO;
}
On top of this you can also implement removeTarget:Action:
Which ever method gets called first set isClick=NO, I expect clickButton is called first in button action.
I got from Two action methods for an UIButton; next track and seek forward:
Change it As per your requirement.
Personally I'd just track the button's state with an integer on your view controller or within a button subclass. If you track what the button is doing you can control what each of the actions do. In your .h file put in some stuff like this:
enum {
MyButtonScanning,
MyButtonStalling,
MyButtonIdle
};
#interface YourClass : UIViewController {
NSInteger buttonModeAt;
}
#property (nonatomic) NSInteger buttonModeAt;
-(IBAction)buttonPushedDown:(id)sender;
-(void)tryScanForward:(id)sender;
-(IBAction)buttonReleasedOutside:(id)sender;
-(IBAction)buttonReleasedInside:(id)sender;
#end
And then in your .m file throw in some of this stuff:
#implementation YourClass
///in your .m file
#synthesize buttonModeAt;
///link this to your button's touch down
-(IBAction)buttonPushedDown:(id)sender {
buttonModeAt = MyButtonStalling;
[self performSelector:#selector(tryScanForward:) withObject:nil afterDelay:1.0];
}
-(void)tryScanForward:(id)sender {
if (buttonModeAt == MyButtonStalling) {
///the button was not released so let's start scanning
buttonModeAt = MyButtonScanning;
////your actual scanning code or a call to it can go here
[self startScanForward];
}
}
////you will link this to the button's touch up outside
-(IBAction)buttonReleasedOutside:(id)sender {
if (buttonModeAt == MyButtonScanning) {
///they released the button and stopped scanning forward
[self stopScanForward];
} else if (buttonModeAt == MyButtonStalling) {
///they released the button before the delay period finished
///but it was outside, so we do nothing
}
self.buttonModeAt = MyButtonIdle;
}
////you will link this to the button's touch up inside
-(IBAction)buttonReleasedInside:(id)sender {
if (buttonModeAt == MyButtonScanning) {
///they released the button and stopped scanning forward
[self stopScanForward];
} else if (buttonModeAt == MyButtonStalling) {
///they released the button before the delay period finished so we skip forward
[self skipForward];
}
self.buttonModeAt = MyButtonIdle;
}
After that just link the button's actions to what I've noted in the comments before the IBactions. I haven't tested this but it should work.
i'm not sure it will work but you can try
- (IBAction)dragInsideButton:(UIButton *)sender {
[sender removeTarget:self forSelector:#selector(clickButton:) forControlEvent:UIControlEventTouchDown];
}
In Keynote (and other apps), I've noticed the "standard" interface of doing Undo/Redo is by providing an Undo button on the tool bar.
Clicking the button (that is always enabled) Undos the recent operation.
(If there is not recent operation to undo, it will show the Undo/Redo menu).
Long-clicking the Undo button opens an Undo/Redo menu.
I searched for methods of implementing this, and the best answer I found so far is at the following link.
I wonder if anyone knows of a simpler way?
Thanks!
After reviewing all methods and discussing with friends, below is the solution I used, for a UIBarButtonItem the responds to both taps and long-press (TapOrLongPressBarButtonItem).
It is based on the following principals:
Subclass UIBarButtonItem
Use a custom view (so it's really trivial to handle the long-press - since our custom view has no problem responding to a long-press gesture handler...)
... So far - this approach was in the other SO thread - and I didn't like this approach since I couldn't find and easy enough way of making the custom view appear like an iPad navigation bar button... Soooo...
Use UIGlossyButton by Water Lou (thanks water!). This use is encapsulated within the subclass...
The resulting code is as follows:
#protocol TapOrPressButtonDelegate;
#interface TapOrPressBarButtonItem : UIBarButtonItem {
UIGlossyButton* _tapOrPressButton;
__weak id<TapOrPressButtonDelegate> _delegate;
}
- (id)initWithTitle:(NSString*)title andDelegate:(id<TapOrPressButtonDelegate>)delegate;
#end
#protocol TapOrPressButtonDelegate<NSObject>
- (void)buttonTapped:(UIButton*)button withBarButtonItem:(UIBarButtonItem*)barButtonItem;
- (void)buttonLongPressed:(UIButton*)button withBarButtonItem:(UIBarButtonItem*)barButtonItem;
#end
#implementation TapOrPressBarButtonItem
- (void)buttonLongPressed:(UILongPressGestureRecognizer*)gesture {
if (gesture.state != UIGestureRecognizerStateBegan)
return;
if([_delegate respondsToSelector:#selector(buttonLongPressed:withBarButtonItem:)]) {
[_delegate buttonLongPressed:_tapOrPressButton withBarButtonItem:self];
}
}
- (void)buttonTapped:(id)sender {
if (sender != _tapOrPressButton) {
return;
}
if([_delegate respondsToSelector:#selector(buttonTapped:withBarButtonItem:)]) {
[_delegate buttonTapped:_tapOrPressButton withBarButtonItem:self];
}
}
- (id)initWithTitle:(NSString*)title andDelegate:(id<TapOrPressButtonDelegate>)delegate {
if (self = [super init]) {
// Store delegate reference
_delegate = delegate;
// Create the customm button that will have the iPad-nav-bar-default appearance
_tapOrPressButton = [UIGlossyButton buttonWithType:UIButtonTypeCustom];
[_tapOrPressButton setTitle:title forState:UIControlStateNormal];
[_tapOrPressButton setNavigationButtonWithColor:[UIColor colorWithRed:123.0/255 green:130.0/255 blue:139.0/255 alpha:1.0]];
// Calculate width...
CGSize labelSize = CGSizeMake(1000, 30);
labelSize = [title sizeWithFont:_tapOrPressButton.titleLabel.font constrainedToSize:labelSize lineBreakMode:UILineBreakModeMiddleTruncation];
_tapOrPressButton.frame = CGRectMake(0, 0, labelSize.width+20, 30);
// Add a handler for a tap
[_tapOrPressButton addTarget:self action:#selector(buttonTapped:) forControlEvents:UIControlEventTouchUpInside];
// Add a handler for a long-press
UILongPressGestureRecognizer* buttonLongPress_ = [[UILongPressGestureRecognizer alloc] initWithTarget:self
action:#selector(buttonLongPressed:)];
[_tapOrPressButton addGestureRecognizer:buttonLongPress_];
// Set this button as the custom view of the bar item...
self.customView = _tapOrPressButton;
}
return self;
}
// Safe guards...
- (id)initWithImage:(UIImage *)image style:(UIBarButtonItemStyle)style target:(id)target action:(SEL)action {
NSLog(#"%s not supported!", __FUNCTION__);
return nil;
}
- (id)initWithImage:(UIImage *)image landscapeImagePhone:(UIImage *)landscapeImagePhone style:(UIBarButtonItemStyle)style target:(id)target action:(SEL)action {
NSLog(#"%s not supported!", __FUNCTION__);
return nil;
}
- (id)initWithTitle:(NSString *)title style:(UIBarButtonItemStyle)style target:(id)target action:(SEL)action {
NSLog(#"%s not supported!", __FUNCTION__);
return nil;
}
- (id)initWithBarButtonSystemItem:(UIBarButtonSystemItem)systemItem target:(id)target action:(SEL)action {
NSLog(#"%s not supported!", __FUNCTION__);
return nil;
}
- (id)initWithCustomView:(UIView *)customView {
NSLog(#"%s not supported!", __FUNCTION__);
return nil;
}
#end
And all you need to do is:
1. Instantiate is as follows:
TapOrPressBarButtonItem* undoMenuButton = [[TapOrPressBarButtonItem alloc] initWithTitle:NSLocalizedString(#"Undo", #"Undo Menu Title") andDelegate:self];
2. Connect the button to the navigation bar:
[self.navigationItem setLeftBarButtonItem:undoMenuButton animated:NO];
3. Implement the TapOrPressButtonDelegate protocol, and you're done...
-(void)buttonTapped:(UIButton*)button withBarButtonItem:(UIBarButtonItem*)barButtonItem {
[self menuItemUndo:barButtonItem];
}
-(void)buttonLongPressed:(UIButton*)button withBarButtonItem:(UIBarButtonItem*)barButtonItem {
[self undoMenuClicked:barButtonItem];
}
Hope this helps anyone else...
If you are using IB (or in Xcode4 the designer...i guess it is called) then you can select "Undo" from the First responder and drag that action to a button. I can give you more specific instructions if that doesn't cover it.
Here's what it looks like
It's on the left underneath the column "Received actions" at the bottom
I believe the key is actually in the UINavigationBar itself. Unlike UIButtons or other normal touch tracking objects, I suspect UIBarItems don't handle their own touches. They don't inherit UIResponder or UIControl methods. However UINavigationBar of course does. And I've personally added gestures straight to a UINavigationBar many times.
I suggest you override touch handling in a UINavigationBar subclass and check the touches against its children. If the child is your special Undo button you can handle it accordingly.
UIButton* undoButton = [UIButton buttonWithType:UIButtonTypeCustom];
[undoButton addTarget:self action:#selector(undoPressStart:) forControlEvents:UIControlEventTouchDown];
[undoButton addTarget:self action:#selector(undoPressFinish:) forControlEvents:UIControlEventTouchUpInside];
UIBarButtonItem* navButton = [[[UIBarButtonItem alloc] initWithCustomView:undoButton] autorelease];
self.navigationItem.rightBarButtonItem = navButton;
You don't necessarily have to add the UIBarButtonItem as the rightBarButtonItem, this is just and easy way to show you how to create your UIBarButtonItem with a custom view that is the UIButton you want to handle events.
You'll need to implement the undoPressStart: and undoPressFinish: by maintaining state. I'd say on start, store the current NSDate or some granular representation of the time. On finish, if check the time elapsed and if it is beyond a certain threshold, show the menu - otherwise (as well as if the start date was never captured) perform the undo.
As an improvement, you'll likely want to observe the UIControlEventTouchDragExit event as well to cancel the long press.