5

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

}
Hanz Cheah
  • 761
  • 5
  • 15
  • 44

5 Answers5

3

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/

whyp
  • 603
  • 5
  • 14
2

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.

daris mathew
  • 429
  • 5
  • 18
  • thanks for your answer I will award you the points once I tested out your answer. In the meantime, please do you have time to take a look at this question https://stackoverflow.com/questions/52093069/objectivec-webview-not-reloading-from-the-top/52094058#52094058 – Hanz Cheah Sep 04 '18 at 06:19
  • Yeah, i had a look at the question and the easy and proper way to do so would be protocol (delegate) methods. Will work something out and let you know soon. – daris mathew Sep 04 '18 at 07:31
  • Hi @daris mathew I can't fire the method `- (IBAction)btnPressed:(UIButton*)sender` when I press on the button – Hanz Cheah Sep 05 '18 at 04:05
  • Do I need to do anything in the IB for `*mainViewOL`? – Hanz Cheah Sep 05 '18 at 04:05
  • 1
    Got it, added the following `All the buttons need to drag to the following method - (IBAction)btnPressed:(UIButton*)sender;` – Hanz Cheah Sep 05 '18 at 04:24
  • Hi Daris, if I want to give the 2 lines of title of the buttons dynamically, can you give some pointers? – Hanz Cheah Sep 05 '18 at 04:27
  • Another thing is how to deselect the buttons. I have a reset method that I wish deselect the buttons. – Hanz Cheah Sep 05 '18 at 04:42
  • Can you elaborate the last two comments so i can help you with that. – daris mathew Sep 05 '18 at 04:51
  • I manage to get the deselect thing working. The other thing is I have `VC` which I need to label the Title of the buttons dynamically, like from today's date till 7 days later, so it is like 05 Wed, 06 Thu onwards. The title need to be in 2 separate lines. The UI is something like this https://stackoverflow.com/questions/48298252/top-custom-tabs-bar-displaying-today-and-1-week-dates but without the selection bar. All I need is the title to change color when selected. – Hanz Cheah Sep 05 '18 at 05:05
  • Sorry but i am somehow not clear on the idea can you start a private chat and we will discuss this there. – daris mathew Sep 05 '18 at 05:12
  • sorry give me 30 mins please – Hanz Cheah Sep 05 '18 at 05:25
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/179436/discussion-between-daris-mathew-and-hanz-cheah). – daris mathew Sep 05 '18 at 05:37
0

Try to use custom type button.

UIButton *customButton = [UIButton buttonWithType:UIButtonTypeCustom];

Or set this property in Interface Builder.

Lumialxk
  • 6,239
  • 6
  • 24
  • 47
  • How to do this, can you please elaborate a bit more? I need the button to change background color and font color and remains selected. – Hanz Cheah Aug 27 '18 at 03:41
0

You can prepare you screen with the help UICollectionView.

Create custom class with UICollectionViewCell and override below property.

override var isSelected: Bool `{
         willSet{
                   super.isSelected = newValue

          // put button background color value as per selected state`
0

You can get any button and control any button through the tag control.

enter image description here

ylgwhyh
  • 1,588
  • 18
  • 21