Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ObjectiveC - UIButton remains highlighted/selected and background color and font color changes when highlighted/selected

I have used the interface builder to create the following UIButton for different time slot and a UIButton for Search. I want the UIButton for different time slot to remain selected/highlighted when user tap on it. And the background color and font color will change as well (See pic for illustration). Moreover, user can only select one of the time slot at one time.

UIButton with different time slotenter image description here

What I am trying to achieve button

enter image description here

Code

#import "Search.h"
#import <QuartzCore/QuartzCore.h>

@interface Search(){

}

@end

@implementation Search

@synthesize btn1;
@synthesize btn2;
@synthesize btn3;
@synthesize btn4;
@synthesize btn5;
@synthesize btn6;
@synthesize btn7;
@synthesize btn8;
@synthesize btn9;
@synthesize btnSearch;

- (void)viewDidLoad
{
    [super viewDidLoad];

    _borderBox.layer.shadowRadius  = 5;
    _borderBox.layer.shadowColor   = [UIColor colorWithRed:211.f/255.f green:211.f/255.f blue:211.f/255.f alpha:1.f].CGColor;
    _borderBox.layer.shadowOffset  = CGSizeMake(0.0f, 0.0f);
    _borderBox.layer.shadowOpacity = 0.9f;
    _borderBox.layer.masksToBounds = NO;

    btn1.layer.borderColor = [UIColor lightGrayColor].CGColor;
    btn1.layer.borderWidth =1.0f;
    btn2.layer.borderColor = [UIColor lightGrayColor].CGColor;
    btn2.layer.borderWidth =1.0f;
    btn3.layer.borderColor = [UIColor lightGrayColor].CGColor;
    btn3.layer.borderWidth =1.0f;
    btn4.layer.borderColor = [UIColor lightGrayColor].CGColor;
    btn4.layer.borderWidth =1.0f;
    btn5.layer.borderColor = [UIColor lightGrayColor].CGColor;
    btn5.layer.borderWidth =1.0f;
    btn6.layer.borderColor = [UIColor lightGrayColor].CGColor;
    btn6.layer.borderWidth =1.0f;
    btn7.layer.borderColor = [UIColor lightGrayColor].CGColor;
    btn7.layer.borderWidth =1.0f;
    btn8.layer.borderColor = [UIColor lightGrayColor].CGColor;
    btn8.layer.borderWidth =1.0f;
    btn9.layer.borderColor = [UIColor lightGrayColor].CGColor;
    btn9.layer.borderWidth =1.0f;
}

-(void)viewWillAppear:(BOOL)animated{


}

- (void)viewDidAppear:(BOOL)animated
{
    [super viewDidAppear:animated];

}

- (void)viewDidDisappear:(BOOL)animated
{
    [super viewDidDisappear:animated];

}

+(void)makeButtonColored:(UIButton*)button color1:(UIColor*) color
{

    CALayer *layer = button.layer;
    layer.cornerRadius = 8.0f;
    layer.masksToBounds = YES;
    layer.borderWidth = 4.0f;
    layer.opacity = .3;//
    layer.borderColor = [UIColor colorWithWhite:0.4f alpha:0.2f].CGColor;

    CAGradientLayer *colorLayer = [CAGradientLayer layer];
    colorLayer.cornerRadius = 8.0f;
    colorLayer.frame = button.layer.bounds;
    //set gradient colors
    colorLayer.colors = [NSArray arrayWithObjects:
                     (id) color.CGColor,
                     (id) color.CGColor,
                     nil];

    //set gradient locations
    colorLayer.locations = [NSArray arrayWithObjects:
                        [NSNumber numberWithFloat:0.0f],
                        [NSNumber numberWithFloat:1.0f],
                        nil];


    [button.layer addSublayer:colorLayer];

}
like image 800
Hanz Cheah Avatar asked Aug 27 '18 03:08

Hanz Cheah


Video Answer


2 Answers

Theoretically, you could do the following:

  1. Store all the buttons in an array (an instance variable)
  2. Add a target to each button which sets one button to be selected and deselects all other buttons.

The constructor function of the button would like something like this:

-(UIButton *)newButtonWithTitle:(NSString *)title fontSize:(NSInteger)fontSize {
    UIColor *selectedButtonColor = [UIColor colorWithRed:1.0 green:0.2 blue:0.2 
    alpha:0.5];

    UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
    [button setTitle:title forState:UIControlStateNormal];
    [button setTitleColor:selectedButtonColor forState:UIControlStateHighlighted];
    [button setTitleColor:selectedButtonColor forState:UIControlStateSelected];
    button.titleLabel.font = [UIFont systemFontOfSize:16 weight:UIFontWeightRegular];
    button.layer.borderColor = [UIColor lightGrayColor].CGColor;
    button.layer.borderWidth = 1.0;

    [button addTarget:self action:@selector(scheduleButtonAction:) forControlEvents:UIControlEventTouchUpInside];
    return button;
}

The button action function could be:

-(void)scheduleButtonAction:(UIButton *)button {
    button.selected = YES;
    [self.buttons enumerateObjectsUsingBlock:^(UIButton *aButton, NSUInteger idx, BOOL * _Nonnull stop) {
        if (![aButton isEqual:button]) {
            aButton.selected = NO;
        }
    }];
}

BUT I wouldn't do it this way. The problem with this solution is while it is possible, it's not the Apple way and it's definitely not an elegant solution.

There are multiple problems here:

  1. How are you binding the data between each button and the value that it represents? You could do that by either using associative objects OR by subclassing UIButton and adding a property OR by using tags and a lookup table. All of which are not great solutions.

  2. This design is hardcoded and not flexible. There is a lot of boilerplate code for the creation of the buttons and you have to keep track of all these properties.

  3. What are you going to do if the requirement will change and you'll need a button for each hour of the day?

A better way to do this layout, which was hinted by user10277996 is to use a collection view. It will allow you to separate the concerns:

  1. a data source where you decide how many buttons (cells) should be created (and what data they should contain)
  2. a constructor class for the cell, where you define the design once.
  3. a layout class where you define how to lay out your buttons.

You should take a day or two and get really familiar with UICollectionView as it is one of the most powerful and useful classes in iOS.

Here is a tutorial to get you started: https://www.raywenderlich.com/975-uicollectionview-tutorial-getting-started

Apple's official documentation: https://developer.apple.com/library/archive/documentation/WindowsViews/Conceptual/CollectionViewPGforIOS/Introduction/Introduction.html#//apple_ref/doc/uid/TP40012334-CH1-SW1

If you want to dig deeper, check out the following resources (although not necessary for solving your specific issue): https://www.objc.io/issues/3-views/collection-view-layouts/ https://ashfurrow.com/uicollectionview-the-complete-guide/

like image 166
whyp Avatar answered Oct 11 '22 01:10

whyp


I was able to achieve the function you are working on and below is how i did it.

I created the design via storyboard and connected all the 9 button's actions methods to a single Selector method, inside the action method with the help sender parameter we can get the selected buttons reference and use it.

- (IBAction)btnPressed:(UIButton*)sender {

/* Below for loop works as a reset for setting the default colour of button and to not select the same one twice*/
for (UIButton* button in buttons) {
    [button setSelected:NO];
    [button setBackgroundColor:[UIColor whiteColor]];
    [button setUserInteractionEnabled:true];
// [button setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
    [button setTitleColor:[UIColor blackColor] forState:UIControlStateSelected];
}

NSInteger tag = sender.tag;        // Here we get the sender tag, which we can use for our needs. Also we can directly use the sender and get the title or whatsoever needed.

/*Now below line works as a toggle for the button where multiple buttons can't be selected at the same time.*/
sender.selected = ! sender.selected;      

if(sender.selected)
{
/* Here we set the color for the button and handle the selected function*/
    [sender setSelected:YES];
    [sender setUserInteractionEnabled:false];
    [sender setBackgroundColor:[UIColor magentaColor]];
}
}

You can also add custom layer for the button by using the "sender.Layer" property.

The Whole code is added below,

All the button's action needs to be connected to a single selector method, - (IBAction)btnPressed:(UIButton*)sender;

#import "ViewController.h"

@interface ViewController ()
@property (weak, nonatomic) IBOutlet UIView *mainViewOL;
@property (weak, nonatomic) IBOutlet UIButton *btn1;
@property (weak, nonatomic) IBOutlet UIButton *btn2;
@property (weak, nonatomic) IBOutlet UIButton *btn3;
@property (weak, nonatomic) IBOutlet UIButton *btn4;
@property (weak, nonatomic) IBOutlet UIButton *btn5;
@property (weak, nonatomic) IBOutlet UIButton *btn6;
@property (weak, nonatomic) IBOutlet UIButton *btn7;
@property (weak, nonatomic) IBOutlet UIButton *btn8;
@property (weak, nonatomic) IBOutlet UIButton *btn9;

@end

@implementation ViewController

NSArray* buttons;

- (void)viewDidLoad {
    [super viewDidLoad];

    buttons = [NSArray arrayWithObjects:_btn1, _btn2, _btn3,_btn4,_btn5,_btn6,_btn7,_btn8,_btn9,nil];

    self.mainViewOL.layer.shadowRadius  = 5;
    self.mainViewOL.layer.shadowColor   = [UIColor colorWithRed:211.f/255.f green:211.f/255.f blue:211.f/255.f alpha:1.f].CGColor;
    self.mainViewOL.layer.shadowOffset  = CGSizeMake(0.0f, 0.0f);
    self.mainViewOL.layer.shadowOpacity = 0.9f;
    self.mainViewOL.layer.masksToBounds = NO;

    /* I Have added the 9 button's in an array and used it to reduce the lines of code and for easy understanding as well*/
    for (UIButton* button in buttons) {
        button.layer.borderColor = [UIColor lightGrayColor].CGColor;
        button.layer.borderWidth =1.0f;
    }
}

- (IBAction)btnPressed:(UIButton*)sender {
    for (UIButton* button in buttons) {
        [button setSelected:NO];
        [button setBackgroundColor:[UIColor whiteColor]];
        [button setUserInteractionEnabled:true];
     // [button setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];        //Based on your needs and colour variant you cant add properties to the button for different control states.
        [button setTitleColor:[UIColor blackColor] forState:UIControlStateSelected];
    }

    NSInteger tag = sender.tag;

    sender.selected = ! sender.selected;


    if(sender.selected)
    {
        [sender setSelected:YES];
        [sender setUserInteractionEnabled:false];
        [sender setBackgroundColor:[UIColor purpleColor]];
        sender.backgroundColor = [UIColor magentaColor];
    }
}

@end

And the Final Result

Ignore the delay in button selection, it is caused by the video to gif conversion.

Hope This helps.

like image 24
daris mathew Avatar answered Oct 11 '22 02:10

daris mathew