iOS disable programatically created buttons until function completion - ios

Hi and thanks in advance for you patience and help :)
Here is what I am doing:
- I create programmatically multiple buttons that call the same function
And what I want to accomplish:
- after a button is pressed, I want the common function to run and all the buttons to be disabled so that the function won't be called again during execution of the firs call. after the first call is ended I want to reenable the buttons.
Here is how I create the buttons:
#interface ViewController ()
{
UIButton *button;
}
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
int tagForTheButton = 0;
int yPossition = 0;
int xPossition = 0;
for (int numberOfTheColomn = 0; numberOfTheColomn < 6; numberOfTheColomn++) {
button = [UIButton buttonWithType:UIButtonTypeCustom];
button.adjustsImageWhenHighlighted = NO;
button.frame = CGRectMake(xPossition, yPossition, 64, 64);
button.tag = tagForTheButton;
[button setTitle:title forState:UIControlStateNormal];
[button addTarget:self action:#selector(buttonPressed:) forControlEvents:UIControlEventTouchUpInside];
[self.scroll addSubview:button];
yPossition=yPossition+100;
tagForTheButton++;
}
}
-(void)buttonPressed:(id)sender{
//This is my attempt to disable the buttons
//previous mistake used 2578
for (int numberForIncrease; numberForIncrease<6; numberForIncrease++) {
if (button.tag == numberForIncrease) {
UIButton *btn = (UIButton*)[self.scroll viewWithTag:numberForIncrease];
btn.enabled=NO;
[btn setUserInteractionEnabled:NO];
}
}
}

You must be disabling it incorrectly. The docs for UIButton.enabled state:
If the enabled state is NO, the control ignores touch events and
subclasses may draw differently.
Check that DisableButton is being called by adding an NSLog() call, & do away with the DisableButton method altogether:
dispatch_sync(dispatch_get_main_queue(), ^{
self.view.userInteractionEnabled = NO;
_btn_sync_outlet.enabled = NO;
_btn_sync_outlet.userInteractionEnabled = NO;
});
This might helps you :)

I don't understand why you need to loop from 0 to 2577 for disabling a button which you already have an iVar pointing to..?
why don't you just switch off userInteraction to your entire view (or if that is not possible to a containerView which holds all the buttons)?
[self.view setUserInteractionEnabled: NO ];
or, failing that why not just haver an iVar there?
{
BOOL _ignoreButtons;
}
-(void)buttonPressed:(id)sender{
if (!_ignoreButtons){
_ignoreButtons = YES;
//do your time consuming job, possibly on bg thread
//on completion..
_ignoreButtons = NO;
}
}

Related

override the fullscreen tap or hide the fullscreen button -MPMoviePlayerController

When using the MPMoviePlayerController I want to override fullscreen button tap or to hide it so I can add a custom button.
The mission is to really change the MPMoviePlayerController frame and other subviews frame.
Any ideas? Or I must use different video player to accomplish that?
First of all remove gestures of your MPMoviePlayerController. Then add your custom gestures or Button or other control.
self.player.view.gestureRecognizers = nil;
//Add your custom gestures or Button or other control
I recently was solving similar issue. My client was insisting to disable Previous and Next buttons although I was strongly recommend to do that because it's quite tricky and not guaranteed to work in next iOS version. So here I publish my solution as an very unwanted option)
-(void) disableNextPrevButtons:(UIView *) view :(int) level {
Class cl = NSClassFromString(#"MPKnockoutButton");
for (UIView * v in view.subviews) {
NSLog(#"[%d] scanning view of class %# tag: %ld", level, NSStringFromClass(v.class), (long)v.tag);
if ([v isKindOfClass:cl]) {
for (UIView * v2 in v.subviews)
if ([v2 isKindOfClass:[UIImageView class]]) {
v.alpha = 0.0;
v.userInteractionEnabled = NO;
if (!buttonsDiscovered) {
buttonsDiscovered = YES;
UIButton * b = [[UIButton alloc] initWithFrame:CGRectMake(0, 0, v.superview.frame.size.height, v.superview.frame.size.height)];
if (MLGSystemVersion() > 7.9)
b.alpha = 0.5;
b.autoresizingMask = (UIViewAutoresizingFlexibleLeftMargin |
UIViewAutoresizingFlexibleRightMargin |
UIViewAutoresizingFlexibleTopMargin |
UIViewAutoresizingFlexibleBottomMargin);
[b setImage:[UIImage imageNamed:#"btnMPPause.png"] forState:UIControlStateNormal];
[b setImage:[UIImage imageNamed:#"btnMPPlay.png"] forState:UIControlStateSelected];
b.selected = YES;
[b addTarget:self action:#selector(btnPlayPauseDidPress:) forControlEvents:UIControlEventTouchUpInside];
b.center = CGPointMake(v.superview.frame.size.width / 2, v.center.y);
[v.superview addSubview:b];
self.btnPlayPause = b;
}
break;
}
} else
[self disableNextPrevButtons:v :level +1];
}
}
The method is recursevely scans and finds the desired controls and do some bad things to them

How to preform an action while the button is pressed?

I have a button and i need to change the label while the button is pressed to #"PRESSED" and if it's let go I need to change it to #"LET GO".
I've found many answers, but i have no idea how to implement them in Xcode 5.
That are the ones i tried:
You can start your action on the touchDownInside and stop it on the touchUpInside actions - you can hook them up in IB.
or
Add targets to your button for both control states, UIControlEventTouchDown and UIControlEventTouchUpInside or UIControlEventTouchUpOutside
or even
Add a dispatch source iVar to your controller...
dispatch_source_t _timer;
Then, in your touchDown action, create the timer that fires every so many seconds. You will do your repeating work in there.
If all your work happens in UI, then set queue to be
dispatch_queue_t queue = dispatch_get_main_queue();
and then the timer will run on the main thread.
- (IBAction)touchDown:(id)sender {
if (!_timer) {
dispatch_queue_t queue = dispatch_get_global_queue(
DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
_timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
// This is the number of seconds between each firing of the timer
float timeoutInSeconds = 0.25;
dispatch_source_set_timer(
_timer,
dispatch_time(DISPATCH_TIME_NOW, timeoutInSeconds * NSEC_PER_SEC),
timeoutInSeconds * NSEC_PER_SEC,
0.10 * NSEC_PER_SEC);
dispatch_source_set_event_handler(_timer, ^{
// ***** LOOK HERE *****
// This block will execute every time the timer fires.
// Do any non-UI related work here so as not to block the main thread
dispatch_async(dispatch_get_main_queue(), ^{
// Do UI work on main thread
NSLog(#"Look, Mom, I'm doing some work");
});
});
}
dispatch_resume(_timer);
}
Now, make sure to register for both touch-up-inside and touch-up-outside
- (IBAction)touchUp:(id)sender {
if (_timer) {
dispatch_suspend(_timer);
}
}
Make sure you destroy the timer
- (void)dealloc {
if (_timer) {
dispatch_source_cancel(_timer);
dispatch_release(_timer);
_timer = NULL;
}
}
I am new to iOS and have no idea whatsoever how to do any of that! Thanks a lot for any help in advance!
I would do this with a GestureRecognzier. Here is some code:
- (void)viewDidLoad
{
[super viewDidLoad];
UILongPressGestureRecognizer *gr = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:#selector(buttonIsPressed:)];
gr.minimumPressDuration = 0.1;
[self.button addGestureRecognizer:gr];
}
-(IBAction)buttonIsPressed:(UILongPressGestureRecognizer*)gesture {
if (gesture.state == UIGestureRecognizerStateBegan) {
self.label.text = #"Button is pressed!";
}
else if (gesture.state == UIGestureRecognizerStateEnded) {
self.label.text = #"Button is not pressed";
}
}
You need an IBOutlet for the label and the button.
The first method does what you want, here's an example implementation:
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
UIButton *myButton = [[UIButton alloc] initWithFrame:CGRectMake(self.view.bounds.size.width / 2.0 - 50.0, self.view.bounds.size.height / 2.0 - 25.0, 100.0, 50.0)];
myButton.backgroundColor = [UIColor colorWithRed:0.2 green:0.3 blue:0.8 alpha:1.0];
[myButton setTitle:#"PRESS ME" forState:UIControlStateNormal];
[myButton addTarget:self action:#selector(setPressed:) forControlEvents:UIControlEventTouchDown];
[myButton addTarget:self action:#selector(setLetGo:) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:myButton];
}
-(void)setPressed:(id)sender
{
UIButton *myButton = (UIButton *)sender;
[myButton setTitle:#"PRESSED" forState:UIControlStateNormal];
}
-(void)setLetGo:(id)sender
{
UIButton *myButton = (UIButton *)sender;
[myButton setTitle:#"LET GO" forState:UIControlStateNormal];
}
If you are new to xCode this can be difficult to understand. Follow this and you'll be allright.
Go to Storyboard and put a button in the view now go to the Inspector and change State config from Default to Highlighted
In placeholder 'highlighted title' put 'Pressed'
Now click on the Assistant Editor to open the .m file next to Storyboard
CNTRL + Click and drag from the Button to the .m file code to create a TouchUpInside event, mine called buttonPressed
Put this code in the method body and you are good to go!
This is what it will look like, pressed and released:

How can I create an array of buttons in xcode?

I have a view containing 80 UIButtons, and 80 UIImages. Rather than refer to these by individual outlet references, I would like to refer to them as indexes in an array, and so be able to change the Image, and work out which UIButton is sending a message without specific references.
I am sure this must be possible, as there is no way having 80 different versions of the same code is correct way to do this!
Is this possible?
You may be better served by looking into UICollectionView, but to answer the question as asked:
- (void)viewDidLoad {
[super viewDidLoad];
self.buttonArray = [NSMutableArray array];
for (int i = 0; i < 80; i = i + 1) {
// However you wish to get your button
UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
button.frame = CGRectMake(0, i * 20, 20, 10);
[self.view addSubview:button];
// Other button-specific stuff (like setting the image, etc.)
[button addTarget:self action:#selector(buttonPressed:) forControlEvents:UIControlEventTouchUpInside];
[self.buttonArray addObject:button];
}
}
- (void)buttonPressed:(UIButton *)sender {
int index = [self.buttonArray indexOfObject:sender];
// Now handle the button press based
}
It is possible, it’s called outlet collection.
#property(nonatomic,retain) IBOutletCollection NSArray *buttonsArray;

How to remove duplicate buttons

I created five buttons using for loop, it works well. Whenever i click restart button duplicate buttons created
-(void)buttonCreate {
for( int i = 0; i < 5; i++ )
{
oneBtn1 = [UIButton buttonWithType:UIButtonTypeCustom];
oneBtn1.frame = CGRectMake(316,i*-5+474,51,50);
[oneBtn1 setTag:i];
[oneBtn1 addTarget:self action:#selector(oneButton:) forControlEvents:UIControlEventTouchUpInside];
[oneBtn1 setImage:[UIImage imageNamed:#"1c.png"] forState:UIControlStateNormal];
[self.view addSubview:oneBtn1];
}
}
Restart button function:
-(void)restart {
[self buttonCreate];
}
I tried this one but it will remove only one button out of 5.
if(oneBtn1 != NULL) {
[oneBtn1 removeFromSuperview];
oneBtn1 = nil;
}
Problem is: How to remove duplicate buttons?
Create an array(NSMutableArray *buttonHolderArray) to hold outlets for all five buttons.
[buttonHolderArray addObject:oneBtn1];
Then you can remove/update the buttons as and when you need.
And once you dont want any button, empty the array itself. On top of this, if you wish to clear the view then simply call removeFromSuperView for all the buttons
I tried this one but it will remove only one button out of 5.
if(oneBtn1 != NULL) {
[oneBtn1 removeFromSuperview];
oneBtn1 = nil;
}
You are removing only one button, if you want to remove all then use
for ( UIButton *button in buttonHolderArray){
button removeFromSuperView];
}
You can remove by checking if the button is already created.
-(void)buttonCreate {
int tag_start = 500;
for( int i = 0; i < 5; i++ )
{
UIView * prevbtn = [self.view viewWithTag:tag_start + i];
if(prevbtn) {
[prevbtn removeFromSuperview];
}
oneBtn1 = [UIButton buttonWithType:UIButtonTypeCustom];
oneBtn1.frame = CGRectMake(316,i*-5+474,51,50);
[oneBtn1 setTag:tag_start + i];
[oneBtn1 addTarget:self action:#selector(oneButton:) forControlEvents:UIControlEventTouchUpInside];
[oneBtn1 setImage:[UIImage imageNamed:#"1c.png"] forState:UIControlStateNormal];
[self.view addSubview:oneBtn1];
}
}
use this before calling again restart method
for (UIButton *btn in self.view.subviews ) {
[btn removeFromSuperview];
}
Before the restart button clicked you can remove all the buttons from you view calling the method removeChildViewsWithKindOfClass
-(void)restart {
[self.view removeChildViewsWithKindOfClass:[UIButton class]];
[self buttonCreate];
}
-(void)removeChildViewsWithKindOfClass:(Class)classToRemove{
if (classToRemove==nil) return;
for (UIView *v in [self subviews]) {
if ([v isKindOfClass:classToRemove]) {
[v removeFromSuperview];
}
}
}
If you want to remove a specific button you can use the property tag that you have to set beforehand when you create your button and it allows you removing the button with a specific tag.

Keeping a UIButton selected after a touch

After my user clicks a button, I'd like that button to stay pushed during the time that I perform a network operation. When the network operation is complete, I want the button to return to its default state.
I've tried calling -[UIButton setSelected:YES] right after the button push (with a corresponding call to -[UIButton setSelected:NO] after my network op finishes) but it doesn't seem to do anything. Same thing if I call setHighlighted:.
I suppose I could try swapping out the background image to denote a selected state for the duration of the network op, but that seems like a hack. Any better suggestions?
Here's what my code looks like:
- (IBAction)checkInButtonPushed
{
self.checkInButton.enabled = NO;
self.checkInButton.selected = YES;
self.checkInButton.highlighted = YES;
[self.checkInActivityIndicatorView startAnimating];
[CheckInOperation startWithPlace:self.place delegate:self];
}
- (void)checkInCompletedWithNewFeedItem:(FeedItem*)newFeedItem wasNewPlace:(BOOL)newPlace possibleError:(NSError*)error;
{
[self.checkInActivityIndicatorView stopAnimating];
self.checkInButton.enabled = YES;
self.checkInButton.selected = NO;
self.checkInButton.highlighted = NO;
}
How are you setting the images for the different UIControlStates on the button? Are you setting a background image for UIControlStateHighlighted as well as UIControlStateSelected?
UIImage *someImage = [UIImage imageNamed:#"SomeResource.png"];
[button setBackgroundImage:someImage forState:UIControlStateHighlighted];
[button setBackgroundImage:someImage forState:UIControlStateSelected];
If you're setting the selected state on the button touch down event rather than touch up inside, your button will actually be in a highlighted+selected state, so you'll want to set that too.
[button setBackgroundImage:someImage forState:(UIControlStateHighlighted|UIControlStateSelected)];
Edit:
To sum up my remarks in the comments and to address the code you posted...you need to set your background images for the full UIControl state that you're in. According to your code snippet, this control state would be disabled + selected + highlighted for the duration of the network operation. This means that you would need to do this:
[button setBackgroundImage:someImage forState:(UIControlStateDisabled|UIControlStateHighlighted|UIControlStateSelected)];
If you remove the highlighted = YES, then you would need this:
[button setBackgroundImage:someImage forState:(UIControlStateDisabled|UIControlStateSelected)];
Get the picture?
I have an easier way. Just use "performSelector" with 0 delay to perform [button setHighlighted:YES] . This will perform re-highlighting after the current runloop ends.
- (IBAction)buttonSelected:(UIButton*)sender {
NSLog(#"selected %#",sender.titleLabel.text);
[self performSelector:#selector(doHighlight:) withObject:sender afterDelay:0];
}
- (void)doHighlight:(UIButton*)b {
[b setHighlighted:YES];
}
"Everything gets better when you turn power on"
button.selected = !button.selected;
works perfectly... after I connected the outlet to the button in the Interface Builder.
You do not need to setBackgroundImage:forState:, the builder allows you to specify the background (gets resized if necessary) or/and foreground (not resizing) images.
Try using NSOperationQueue to achieve this. Try out code as follows:
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
theButton.highlighted = YES;
}];
Hope this helps.
Use a block so you don't have to build a whole separate method:
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0), dispatch_get_main_queue(), ^{
theButton.highlighted = YES;
});
Update
To be clear you still need to set the background (or normal image) for the combination states as well as the regular ones like sbrocket says in the accepted answer. At some point your button will be both selected and highlighted, and you won't have an image for that unless you do something like this:
[button setBackgroundImage:someImage forState (UIControlStateHighlighted|UIControlStateSelected)];
Otherwise your button can fall back to the UIControlStateNormal image for the brief selected+highlighted state and you'll see a flash.
In swift I'm doing it like the following.
I create a Subclass of UIButton and implemented a custom property state
class ActiveButton: UIButton {
private var _active = false
var active:Bool {
set{
_active = newValue
updateState()
}
get{
return _active
}
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.addTarget(self, action: #selector(ActiveButton.touchUpInside(_:)), forControlEvents: .TouchUpInside)
}
func touchUpInside(sender:UIButton) {
active = !active
}
private func updateState() {
NSOperationQueue.mainQueue().addOperationWithBlock {
self.highlighted = self.active
}
}
}
Works perfectly for me.
I had a similar problem where I wanted a button to keep it's highlight after click.
The problem is if you try to use setHighlighted:YES inside of you click action it will reset right after you click action, - (IBAction)checkInButtonPushed
I solved this by using a NSTimer like this
NSTimer *timer;
timer = [NSTimer scheduledTimerWithTimeInterval: 0.01
target: self
selector: #selector(selectCurrentIndex)
userInfo: nil
repeats: NO];
and then call setHighlighted:YES from my selectCurrentIndex method. I use regular UIButtonTypeRoundedRect buttons.
I have another way ...if you don't want to use images, and you want the effect of a pressed button, You can subclass the Button and here's my code:
in the .h File:
#interface reservasButton : UIButton {
BOOL isPressed;
}
#end
In the .m File:
#import <QuartzCore/QuartzCore.h>
#implementation reservasButton
-(void)setupView { //This is for Shadow
self.layer.shadowColor = [UIColor blackColor].CGColor;
self.layer.shadowOpacity = 0.5;
self.layer.shadowRadius = 1;
self.layer.shadowOffset = CGSizeMake(2.0f, 2.0f); //comment
// self.layer.borderWidth = 1;
self.contentVerticalAlignment = UIControlContentVerticalAlignmentCenter;
self.contentHorizontalAlignment = UIControlContentHorizontalAlignmentCenter;
// [self setBackgroundColor:[UIColor whiteColor]];
// self.opaque = YES;
}
-(id)initWithFrame:(CGRect)frame{
if((self = [super initWithFrame:frame])){
[self setupView];
}
return self;
}
-(id)initWithCoder:(NSCoder *)aDecoder{
if((self = [super initWithCoder:aDecoder])){
[self setupView];
}
return self;
}
//Here is the important code
-(void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
if (isPressed == FALSE) {
self.contentEdgeInsets = UIEdgeInsetsMake(1.0,1.0,-1.0,-1.0);
self.layer.shadowOffset = CGSizeMake(1.0f, 1.0f);
self.layer.shadowOpacity = 0.8;
[super touchesBegan:touches withEvent:event];
isPressed = TRUE;
}
else {
self.contentEdgeInsets = UIEdgeInsetsMake(0.0,0.0,0.0,0.0);
self.layer.shadowOffset = CGSizeMake(2.0f, 2.0f);
self.layer.shadowOpacity = 0.5;
[super touchesEnded:touches withEvent:event];
isPressed = FALSE;
}
} `
Here is a C# / MonoTouch (Xamarin.iOS) implementation using approaches presented above. It assumes you have set the Highlighted image state already, and configures the selected and selected|highlighted states to use the same image.
var selected = button.BackgroundImageForState(UIControlState.Highlighted);
button.SetBackgroundImage(selected, UIControlState.Selected);
button.SetBackgroundImage(selected, UIControlState.Selected | UIControlState.Highlighted);
button.TouchUpInside += delegate
{
NSTimer.CreateScheduledTimer(TimeSpan.FromMilliseconds(0), delegate
{
button.Highlighted = true;
NSTimer.CreateScheduledTimer(TimeSpan.FromMilliseconds(200), delegate
{
button.Highlighted = false;
});
});
};

Resources