Keeping a UIButton selected after a touch - ios

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

Related

How to hide multiple views at a time in effective manner in ios

In my current project I need to maintain multiple container controllers in a single view controller. There are four buttons on view controller. if first button is selected first container will be visible and remaining will be in hidden state. Similarly to second, third and fourth button. At any time only one will be visible to the user. I can achieve it by showing the respective container and hiding rest by hardcoding.
#property UIView *view1;
#property UIView *view2;
#property UIView *view3;
#property UIView *view4;
- (iBAction *)firstButtonClicked:(UIButton *)button {
self.view1.hidden = NO;
self.view2.hidden = YES;
self.view3.hidden = YES;
self.view4.hidden = YES;
}
- (iBAction *)secondButtonClicked:(UIButton *)button {
self.view1.hidden = YES;
self.view2.hidden = NO;
self.view3.hidden = YES;
self.view4.hidden = YES;
}
- (iBAction *)thirdButtonClicked:(UIButton *)button {
self.view1.hidden = YES;
self.view2.hidden = YES;
self.view3.hidden = NO;
self.view4.hidden = YES;
}
- (iBAction *)fourthButtonClicked:(UIButton *)button {
self.view1.hidden = YES;
self.view2.hidden = YES;
self.view3.hidden = YES;
self.view4.hidden = NO;
}
But I am not satisfied with the approach. I tried searching answer in stack overflow but not successful.
Please tell me know if any body knows any effective approach to achieve it.
Thanks.
There are a few possible solutions to this. Here's one option.
First, give each button a specific tag. Give button 1 a tag of 1. Give button 2 a tag of 2, etc.
Then use a single action for all four buttons instead of the four separate actions you have now.
Then implement the one action method like this:
- (IBAction *)buttonClicked:(UIButton *)button {
self.view1.hidden = button.tag != 1;
self.view2.hidden = button.tag != 2;
self.view3.hidden = button.tag != 3;
self.view4.hidden = button.tag != 4;
}
If the button with a tag of 1 is tapped, then button.tag != 1 will be false so self.view1.hidden will be set to NO. The other 3 conditions will be true so the other buttons will have hidden set to YES.
The same logic applies to the other three buttons each with their own tag values.
Give the views tags like 201 to 204 or anything you like, set the for loop accordingly.
Point the actions of all the buttons to the below selector,
- (void)anyButtonClicked:(UIButton *)button
{
for (int iterator = 201; iterator < 204; iterator ++)
{
UIView *currentView = [self.view viewWithTag:iterator];
if (currentView.tag == button.tag)
{
[currentView setHidden:NO];
} else {
[currentView setHidden:YES];
}
}
}
you can also try this it will also consume less memory-
[yourview removeFromSuperview];
yourview = nil;
At any time only one view is hidden. Why do you need to hide/unhide all of them? Just do the following (just after the #property declarations):
UIView *lastVisible = view1;
And then you can write the actions as:
- (IBAction *)firstButtonClicked:(UIButton *)button {
lastVisible.hidden = YES;
self.view1.hidden = NO;
lastVisible = self.view1;
}
and so on. This assumes that view1 is the visible view at first.

iOS: Handling long press and drag to select another button. (Like the keyboard)

I'm having a hard time finding the right documentation for how to handle touch events in order to support similar behavior to the keyboard.
What I want is a button that when I long press it, it shows a custom view controller above the button, but I want the user to be able to drag their finger to one of the other buttons (without taking their finger off the screen).
I have the button with a long press and it's custom view controller all setup and working. What I can't figure is how to support dragging from the first button over to the other button in the view controller to be able to select it.
I've tried using a subclassed UIButton where I tried this:
[self addTarget:self action:#selector(onDragOver:) forControlEvents:UIControlEventTouchDragEnter];
But that doesn't work.
I also found this question How to track button selection after long press? which is precisely the functionality I'm trying to duplicate. But there are no answers.
Here's my solution. The trick is you have to use hitTest:.
First you add a gesture recognizer to the button that is a normal button - the button that you want to open a context menu / custom view controller.
Then in your gesture recognizer callback, you use hitTest: to figure out if the user is over a custom button of yours and update it's state manually.
- (id) init {
//add a long press gesture recognizer
UILongPressureGestureRecognizer * gesture = [[UILongPressureGestureRecognizer alloc] initWithTarget:self action:#selector(onLongTap:)];
[self.myButton addGestureRecognizer:gesture];
}
- (void) onLongTap:(UIGestureRecognizer *) gesture {
if(gesture.state == UIGestureRecognizerStateBegan) {
//display your view controller / context menu over the button
}
if(gesture.state == UIGestureRecognizerStateEnded) {
//gesture stopped, use hitTest to find if their finger was over a context button
CGPoint location = [gesture locationInView:self.view];
CGPoint superviewLocation = [self.view.superview convertPoint:location fromView:self.view];
UIView * view = [self.view.superview hitTest:superviewLocation withEvent:nil];
if([view isKindOfClass:[MMContextMenuButton class]]) {
//their finger was over my custom button, tell the button to send actions
MMContextMenuButton * button = (MMContextMenuButton *) view;
[self hideAndSendControlEvents:UIControlEventTouchUpInside];
if(self.draggedContextMenuButton == button) {
self.draggedContextMenuButton = nil;
}
}
if(self.draggedContextMenuButton) {
[self sendActionsForControlEvents:UIControlEventTouchUpInside];
}
self.draggedContextMenuButton = nil;
}
if(gesture.state == UIGestureRecognizerStateChanged) {
//gesture changed, use hitTest to see if their finger
//is over a button. Manually have to tell the button
//that it should update it's state.
CGPoint location = [gesture locationInView:self.view];
CGPoint superviewLocation = [self.view.superview convertPoint:location fromView:self.view];
UIView * view = [self.view.superview hitTest:superviewLocation withEvent:nil];
if([view isKindOfClass[MMContextMenuButton class]]) {
MMContextMenuButton * button = (MMContextMenuButton *) view;
if(self.draggedContextMenuButton != button) {
[self.draggedContextMenuButton dragOut];
}
self.draggedContextMenuButton = button;
[button dragOver];
}
}
}
//////////////
#import "MMContextMenuButton.h"
#import "MMContextMenus.h"
#implementation MMContextMenuButton
- (id) initWithCoder:(NSCoder *)aDecoder {
self = [super initWithCoder:aDecoder];
self.layer.cornerRadius = 4;
self.adjustsImageWhenHighlighted = FALSE;
self.adjustsImageWhenDisabled = FALSE;
self.backgroundColor = [UIColor clearColor];
[self setTitleColor:[UIColor whiteColor] forState:UIControlStateHighlighted];
[self setTitleColor:[UIColor colorWithRed:0.435 green:0.745 blue:0.867 alpha:1] forState:UIControlStateNormal];
[self addTarget:self action:#selector(onHighlight:) forControlEvents:UIControlEventTouchDown];
[self addTarget:self action:#selector(onRelease:) forControlEvents:UIControlEventTouchUpOutside&UIControlEventTouchUpOutside];
return self;
}
- (void) onHighlight:(id) sender {
self.backgroundColor = [UIColor colorWithRed:0.435 green:0.745 blue:0.867 alpha:1];
}
- (void) onRelease:(id) sender {
self.backgroundColor = [UIColor clearColor];
}
- (void) hideAndSendControlEvents:(UIControlEvents) events {
[self dragOut];
[self sendActionsForControlEvents:events];
[[MMContextMenus instance] hideContextMenus];
}
- (void) dragOver {
self.highlighted = TRUE;
self.backgroundColor = [UIColor colorWithRed:0.435 green:0.745 blue:0.867 alpha:1];
}
- (void) dragOut {
self.highlighted = FALSE;
self.backgroundColor = [UIColor clearColor];
}
#end

iOS disable programatically created buttons until function completion

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

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:

Checkbox in iOS application

I need to add checkbox controls to my form. I know that there is no such control in iOS SDK. How could I do this?
this has been driving me mad too and I found a different solution that works well for me and avoids having to use images.
Add a new label object to Interface Builder.
Create an IBOutlet property in Xcode and connect it up to it. In the code below I've called it 'fullyPaid' as I want to know if someone has fully paid a sum of money.
Add the 2 functions below.
The 'touchesBegan' function checks if you touched somewhere inside the 'fullyPaid' label object and if so, it calls the 'togglePaidStatus' function.
The 'togglePaidStatus' function sets up two strings which have the unicode characters representing an empty box (\u2610) and a checked box (\u2611) respectively. Then it compares what's currently in the 'fullyPaid' object and toggles it with the other string.
You might want to call the togglePaidStatus function in the viewDidLoad function to set it to an empty string initially.
Obviously you can add extra checks to prevent users toggling the checkbox if the label is not enabled, but that's not shown below.
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
if (CGRectContainsPoint([fullyPaid frame], [touch locationInView:self.view]))
{
[self togglePaidStatus];
}
}
-(void) togglePaidStatus
{
NSString *untickedBoxStr = [[NSString alloc] initWithString:#"\u2610"];
NSString *tickedBoxStr = [[NSString alloc] initWithString:#"\u2611"];
if ([fullyPaid.text isEqualToString:tickedBoxStr])
{
fullyPaid.text = untickedBoxStr;
}
else
{
fullyPaid.text = tickedBoxStr;
}
[tickedBoxStr release];
[untickedBoxStr release];
}
Generally, you would use the UISwitch for checkbox-like functionality.
You could roll your own though by using an image control with two images (checked/unchecked) and switching the images when they touch the control/
If you're showing a group of options and the user can select one of them, use a tableview with a checkmark accessory and a different text color on the selected row.
If you have a single option, your best bet is to use a switch. If you can't or don't want to, use a button, setting the normal image to an empty box and the selected image to a checked box. You'll have to make those two images yourself or find stock graphics to use for them.
Extending to Adrean's idea, i've achieved this using a very simple approach.
My idea is to change button (lets say checkBtn) text depending upon its state, and then change button's state in its IBAction.
Below is the code how i did this:
- (void)viewDidLoad
{
[super viewDidLoad];
[checkBtn setTitle:#"\u2610" forState:UIControlStateNormal]; // uncheck the button in normal state
[checkBtn setTitle:#"\u2611" forState:UIControlStateSelected]; // check the button in selected state
}
- (IBAction)checkButtonTapped:(UIButton*)sender {
sender.selected = !sender.selected; // toggle button's selected state
if (sender.state == UIControlStateSelected) {
// do something when button is checked
} else {
// do something when button is unchecked
}
}
I wanted to do this programmatically, and also solve the problem that the hit area was really too small. This is adapted from various sources, including Mike and Mike's commenter Agha.
In your header
#interface YourViewController : UIViewController {
BOOL checkboxSelected;
UIButton *checkboxButton;
}
#property BOOL checkboxSelected;;
#property (nonatomic, retain) UIButton *checkboxButton;
-(void)toggleButton:(id)sender;
And in your implementation
// put this in your viewDidLoad method. if you put it somewhere else, you'll probably have to change the self.view to something else
// create the checkbox. the width and height are larger than actual image, because we are creating the hit area which also covers the label
UIButton* checkBox = [[UIButton alloc] initWithFrame:CGRectMake(100, 60,120, 44)];
[checkBox setImage:[UIImage imageNamed:#"checkbox.png"] forState:UIControlStateNormal];
// uncomment below to see the hit area
// [checkBox setBackgroundColor:[UIColor redColor]];
[checkBox addTarget:self action:#selector(toggleButton:) forControlEvents: UIControlEventTouchUpInside];
// make the button's image flush left, and then push the image 20px left
[checkBox setContentHorizontalAlignment:UIControlContentHorizontalAlignmentLeft];
[checkBox setImageEdgeInsets:UIEdgeInsetsMake(0.0, 20.0, 0.0, 0.0)];
[self.view addSubview:checkBox];
// add checkbox text text
UILabel *checkBoxLabel = [[UILabel alloc] initWithFrame:CGRectMake(140, 74,200, 16)];
[checkBoxLabel setFont:[UIFont boldSystemFontOfSize:14]];
[checkBoxLabel setTextColor:[UIColor whiteColor]];
[checkBoxLabel setBackgroundColor:[UIColor clearColor]];
[checkBoxLabel setText:#"Checkbox"];
[self.view addSubview:checkBox];
// release the buttons
[checkBox release];
[checkBoxLabel release];
And put this method in too:
- (void)toggleButton: (id) sender
{
checkboxSelected = !checkboxSelected;
UIButton* check = (UIButton*) sender;
if (checkboxSelected == NO)
[check setImage:[UIImage imageNamed:#"checkbox.png"] forState:UIControlStateNormal];
else
[check setImage:[UIImage imageNamed:#"checkbox-checked.png"] forState:UIControlStateNormal];
}
Here is my version of checkbox for iphone.
It is single class which extends UIButton.
It is simple so i will paste it here.
CheckBoxButton.h file content
#import <UIKit/UIKit.h>
#interface CheckBoxButton : UIButton
#property(nonatomic,assign)IBInspectable BOOL isChecked;
#end
CheckBoxButton.m file content
#import "CheckBoxButton.h"
#interface CheckBoxButton()
#property(nonatomic,strong)IBInspectable UIImage* checkedStateImage;
#property(nonatomic,strong)IBInspectable UIImage* uncheckedStateImage;
#end
#implementation CheckBoxButton
-(id)init
{
self = [super init];
if(self)
{
[self addTarget:self action:#selector(switchState) forControlEvents:UIControlEventTouchUpInside];
}
return self;
}
-(id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if(self)
{
[self addTarget:self action:#selector(switchState) forControlEvents:UIControlEventTouchUpInside];
}
return self;
}
-(id)initWithCoder:(NSCoder *)aDecoder
{
self = [super initWithCoder:aDecoder];
if(self)
{
[self addTarget:self action:#selector(switchState) forControlEvents:UIControlEventTouchUpInside];
}
return self;
}
-(void)setIsChecked:(BOOL)isChecked
{
_isChecked = isChecked;
if(isChecked)
{
[self setImage:self.checkedStateImage forState:UIControlStateNormal];
}
else
{
[self setImage:self.uncheckedStateImage forState:UIControlStateNormal];
}
}
-(void)switchState
{
self.isChecked = !self.isChecked;
[self sendActionsForControlEvents:UIControlEventValueChanged];
}
#end
You can set images for checked/unchecked as well as isChecked property in the attribute inspector of visual studio.
To add CheckBoxButton in storyboard or xib, simple add UIButton and set custom class like on next image.
Button will send UIControlEventValueChanged event, every time when isChecked state is changed.
Everyones code here is very long, slightly messy, and could be done a lot simpler. I have a project on GitHub that subclass UIControl that you can download and check out and gives you an almost native checkbox UI element:
https://github.com/Brayden/UICheckbox
I like the idea of Adrian to use the characters rather than images. But I don't like the box, it need only the checkmark itself (#"\u2713"). I draw a box (a rounded box) programmatically and place an UILabel contains the checkmark inside it. This way of implementation makes it easy to use the custom view in any application without care about any dependent resource. You can also customize the color of the checkmark, the rounded box and the background with ease.
Here's the complete code:
#import <UIKit/UIKit.h>
#class CheckBoxView;
#protocol CheckBoxViewDelegate
- (void) checkBoxValueChanged:(CheckBoxView *) cview;
#end
#interface CheckBoxView : UIView {
UILabel *checkMark;
bool isOn;
UIColor *color;
NSObject<CheckBoxViewDelegate> *delegate;
}
#property(readonly) bool isOn;
#property(assign) NSObject<CheckBoxViewDelegate> *delegate;
- (void) drawRoundedRect:(CGRect) rect inContext:(CGContextRef) context;
#end
#import "CheckBoxView.h"
#define SIZE 30.0
#define STROKE_WIDTH 2.0
#define ALPHA .6
#define RADIUS 5.0
#implementation CheckBoxView
#synthesize isOn, delegate;
- (id)initWithFrame:(CGRect)frame {
if ((self = [super initWithFrame:CGRectMake(frame.origin.x, frame.origin.y, SIZE, SIZE)])) {
// Initialization code
}
//UIColor *color = [UIColor blackColor];
color = [[UIColor alloc] initWithWhite:.0 alpha:ALPHA];
self.backgroundColor = [UIColor clearColor];
checkMark = [[UILabel alloc] initWithFrame:CGRectMake(STROKE_WIDTH, STROKE_WIDTH, SIZE - 2 * STROKE_WIDTH, SIZE - 2*STROKE_WIDTH)];
checkMark.font = [UIFont systemFontOfSize:25.];
checkMark.text = #"\u2713";
checkMark.backgroundColor = [UIColor clearColor];
checkMark.textAlignment = UITextAlignmentCenter;
//checkMark.textColor = [UIColor redColor];
[self addSubview:checkMark];
[checkMark setHidden:TRUE];
isOn = FALSE;
return self;
}
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
- (void)drawRect:(CGRect)rect {
// Drawing code
CGRect _rect = CGRectMake(STROKE_WIDTH, STROKE_WIDTH, SIZE - 2 * STROKE_WIDTH, SIZE - 2*STROKE_WIDTH);
[self drawRoundedRect:_rect inContext:UIGraphicsGetCurrentContext()];
[checkMark setHidden:!isOn];
}
- (void)dealloc {
[checkMark release];
[color release];
[super dealloc];
}
- (void) drawRoundedRect:(CGRect) rect inContext:(CGContextRef) context{
CGContextBeginPath(context);
CGContextSetLineWidth(context, STROKE_WIDTH);
CGContextSetStrokeColorWithColor(context, [color CGColor]);
CGContextMoveToPoint(context, CGRectGetMinX(rect) + RADIUS, CGRectGetMinY(rect));
CGContextAddArc(context, CGRectGetMaxX(rect) - RADIUS, CGRectGetMinY(rect) + RADIUS, RADIUS, 3 * M_PI / 2, 0, 0);
CGContextAddArc(context, CGRectGetMaxX(rect) - RADIUS, CGRectGetMaxY(rect) - RADIUS, RADIUS, 0, M_PI / 2, 0);
CGContextAddArc(context, CGRectGetMinX(rect) + RADIUS, CGRectGetMaxY(rect) - RADIUS, RADIUS, M_PI / 2, M_PI, 0);
CGContextAddArc(context, CGRectGetMinX(rect) + RADIUS, CGRectGetMinY(rect) + RADIUS, RADIUS, M_PI, 3 * M_PI / 2, 0);
CGContextClosePath(context);
CGContextStrokePath(context);
}
#pragma mark Touch
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event{
UITouch *touch = [touches anyObject];
CGPoint loc = [touch locationInView:self];
if(CGRectContainsPoint(self.bounds, loc)){
isOn = !isOn;
//[self setNeedsDisplay];
[checkMark setHidden:!isOn];
if([delegate respondsToSelector:#selector(checkBoxValueChanged:)]){
[delegate checkBoxValueChanged:self];
}
}
}
in .h file
#import <UIKit/UIKit.h>
#interface ViewController : UIViewController
{
BOOL isChecked;
UIImageView * checkBoxIV;
}
#end
And .m file
- (void)viewDidLoad
{
[super viewDidLoad];
isChecked = NO;
//change this property according to your need
checkBoxIV = [[UIImageView alloc] initWithFrame:CGRectMake(10, 10, 15, 15)];
checkBoxIV.image =[UIImage imageNamed:#"checkbox_unchecked.png"];
checkBoxIV.userInteractionEnabled = YES;
UITapGestureRecognizer *checkBoxIVTapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(handlecheckBoxIVTapGestureTap:)];
checkBoxIVTapGesture.numberOfTapsRequired = 1;
[checkBoxIV addGestureRecognizer:checkBoxIVTapGesture];
}
- (void)handlecheckBoxIVTapGestureTap:(UITapGestureRecognizer *)recognizer {
if (isChecked) {
isChecked = NO;
checkBoxIV.image =[UIImage imageNamed:#"checkbox_unchecked.png"];
}else{
isChecked = YES;
checkBoxIV.image =[UIImage imageNamed:#"checkbox_checked.png"];
}
}
This will do the trick...
I made it with a UITextField to avoid drawing anything strange but I liked putting inside as text the tick unicode (Unicode Character 'CHECK MARK' (U+2713)) for the NSString: #"\u2713".
This way, in my .h file (implementing the protocol for the UITextField 'UITextFieldDelegate'):
UITextField * myCheckBox;
In my viewDidLoad or the function to prepare the UI:
...
myCheckBox = [[UITextField alloc] initWithFrame:aFrame];
myCheckBox.borderStyle = UITextBorderStyleRoundedRect; // System look like
myCheckBox.contentVerticalAlignment = UIControlContentVerticalAlignmentCenter;
myCheckBox.textAlignment = NSTextAlignmentLeft;
myCheckBox.delegate = self;
myCheckBox.text = #" -"; // Initial text of the checkbox... editable!
...
Then, add an event selector for reating in the touch event and calling 'responseSelected' event:
...
UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(checkboxSelected)];
[myCheckBox addGestureRecognizer:tapGesture];
...
Finally respond to that selector
-(void) checkboxSelected
{
if ([self isChecked])
{
// Uncheck the selection
myCheckBox.text = #" -";
}else{
//Check the selection
myCheckBox.text = #"\u2713";
}
}
The function 'isChecked' only checks if the text is the #"\u2713" check mark. To prevent showing the keyboard when the text field is selected use the event of the UITextField 'textFieldShouldBeginEditing' and add the event selector to manage the selection:
-(BOOL)textFieldShouldBeginEditing:(UITextField *)textField
{
// Question selected form the checkbox
[self checkboxSelected];
// Hide both keyboard and blinking cursor.
return NO;
}
Subclass UIButton, drop a button to view controller, select it and change class name to CheckBox in the identity inspector.
#import "CheckBox.h"
#implementation CheckBox
#define checked_icon #"checked_box_icon.png"
#define empty_icon #"empty_box_icon.png"
- (id)initWithCoder:(NSCoder *)aDecoder
{
self = [super initWithCoder:aDecoder];
if (self)
{
[self setImage:[UIImage imageNamed:empty_icon] forState:UIControlStateNormal];
[self addTarget:self action:#selector(didTouchButton) forControlEvents:UIControlEventTouchUpInside];
}
return self;
}
- (void)didTouchButton {
selected = !selected;
if (selected)
[self setImage:[UIImage imageNamed:checked_icon] forState:UIControlStateNormal];
else
[self setImage:[UIImage imageNamed:empty_icon] forState:UIControlStateNormal];
}
#end
I made one recently. Free to acquire from GitHub. See if this will help. The effect is like
user Aruna Lakmal; FYI, when you add this code to IB as you describe initWithFrame is not called, initWithCoder is. Implement initWithCoder and it will work as you describe.
A simple UIButton subclass will able to behave like checkbox found in Android.
import UIKit
class CheckedUIButton: UIButton {
var checked: Bool = false {
didSet {
if checked {
setImage(UIImage(systemName: "checkmark.circle"), for: .normal)
} else {
setImage(UIImage(systemName: "circle"), for: .normal)
}
}
}
//initWithFrame to init view from code
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
//initWithCode to init view from xib or storyboard
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}
private func commonInit() {
addTarget(self, action: #selector(self.checkedTapped), for: .touchUpInside)
}
#objc
private func checkedTapped() {
self.checked.toggle()
}
}
Output
(Unchecked state)
(Checked state)
A simple UIButton subclass using SF symbols does the trick
class CheckBoxButton: UIButton {
override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
setup()
}
func setup() {
setImage(UIImage(systemName: "square.fill"), for: .normal)
setImage(UIImage(systemName: "checkmark.square.fill"), for: .selected)
}
}
Storyboard
If you are using storyboard, ensure button type is set to custom as seen in the image below
Usage
All you have to do is toggle the isSelected variable in action/selector method
#objc func toggle(_ sender: UIButton) {
sender.isSelected.toggle()
}
Demo

Resources