Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Objective C UIButton with divider and changes textcolor when selected

I have created a row of UIButtons and added one week's date and day as UILabel on it. I have also created a UIView selector that when the button is selected, it will highlight the whole button.

Now I am stuck with how to add a separator or vertical lines between the buttons. And instead of highlighting the UIButton I wish to change the text colour to blue when the button is selected. Please help

What I have created

what i have

what i am trying to achieve

want to achieve

Code

CGFloat HEIGHT_BTN = 55.0; //--- 10 pixels from the navigation view

CGFloat HEIGHT_LABEL = 30.0;
CGFloat HEIGHT_LABEL2 = 15.0;


   -(void)setupSegmentButtons
{
    CGFloat Y_POS_BTN = [[UIApplication sharedApplication] statusBarFrame].size.height+5;

//=== The view where the buttons sits
navigationView = [[UIView alloc]initWithFrame:CGRectMake(0,Y_POS_BTN,self.view.frame.size.width,HEIGHT_BTN)];
navigationView.backgroundColor = [UIColor whiteColor];

[self.view addSubview:navigationView]; //=== Create a View called navigationView

//==== Setup the shadows
UIBezierPath *shadowPath = [UIBezierPath bezierPathWithRect:self.navigationView.bounds];
self.navigationView.layer.masksToBounds = NO;
self.navigationView.layer.shadowColor = [UIColor lightGrayColor].CGColor;
self.navigationView.layer.shadowOffset = CGSizeMake(5.0f, 5.0f);
self.navigationView.layer.shadowOpacity = 0.8f;
self.navigationView.layer.shadowPath = shadowPath.CGPath;

//=== Get the dates and formatting of the dates
NSDate *now = [NSDate date];
NSCalendar *calendar = [NSCalendar currentCalendar];
NSDate *beginningOfThisWeek;
NSTimeInterval durationOfWeek;

[calendar rangeOfUnit:NSWeekCalendarUnit
            startDate:&beginningOfThisWeek
             interval:&durationOfWeek
              forDate:now];

NSDateComponents *comps = [calendar components:NSUIntegerMax fromDate:now];

NSDateFormatter *dateFormatter=[[NSDateFormatter alloc] init];
[dateFormatter setDateFormat:@"dd/MM/YYYY"];
NSDateFormatter *datelblFormat = [[NSDateFormatter alloc] init];
[datelblFormat setDateFormat:@"dd"];
NSDateFormatter *daylblFormat= [[NSDateFormatter alloc] init];
[daylblFormat setDateFormat:@"EEE"];

//=== Loop 7 times to create the Buttons and the 2 lines Labels
for (int i = 0; i<numControllers; i++) {

    UIButton *button = [[UIButton alloc]initWithFrame:CGRectMake(i*(self.navigationView.frame.size.width/numControllers), 0, (self.navigationView.frame.size.width/numControllers),HEIGHT_BTN)];

    [navigationView addSubview:button]; //=== Put the buttons into the navigation View

    NSString *dateString = [dateFormatter stringFromDate:[calendar dateFromComponents:comps]];
    [dtDate addObject:dateString];

    NSString *lblDate = [datelblFormat stringFromDate:[calendar dateFromComponents:comps]];

    firstLineButton = [[UILabel alloc] initWithFrame:CGRectMake(0,5,self.view.frame.size.width/numControllers,HEIGHT_LABEL)];

    firstLineButton.text = lblDate;
    firstLineButton.font = [UIFont systemFontOfSize:20];
    firstLineButton.textColor = [UIColor whiteColor];
    firstLineButton.textAlignment=NSTextAlignmentCenter;
    [button addSubview:firstLineButton]; //=== Put the Date in the 1st line of the the button

    NSString *lblDay = [daylblFormat stringFromDate:[calendar dateFromComponents:comps]];

    UILabel *secondLineButton = [[UILabel alloc] initWithFrame:CGRectMake(0,28,self.view.frame.size.width/numControllers,HEIGHT_LABEL2)];
    secondLineButton.text = lblDay;
    secondLineButton.textColor = [UIColor whiteColor];
    secondLineButton.font = [UIFont boldSystemFontOfSize:11];
    secondLineButton.textAlignment=NSTextAlignmentCenter;
    [button addSubview:secondLineButton]; //=== Put the Day in the 2nd line of the Button

    button.tag = i; //--- IMPORTANT: if you make your own custom buttons, you have to tag them appropriately

    button.backgroundColor = [UIColor colorWithRed:236.0f/255.0f green:0/255.0f blue:140.0f/255.0f alpha:0.6];//%%% buttoncolors

    [button addTarget:self action:@selector(tapSegmentButtonAction:) forControlEvents:UIControlEventTouchUpInside];

    ++comps.day;
}

[self setupSelector]; //=== The selection bar or highligthed area
}

//=== sets up the selection bar under the buttons or the highligted buttons on the navigation bar
-(void)setupSelector {

    //CGFloat Y_POS_BTN = [[UIApplication sharedApplication] statusBarFrame].size.height+5;
    selectionBar = [[UIView alloc]initWithFrame:CGRectMake(0, Y_BUFFER, (self.view.frame.size.width/numControllers),HEIGHT_BTN)];
    selectionBar.backgroundColor = [UIColor colorWithRed:236.0f/255.0f green:0/255.0f blue:140.0f/255.0f alpha:0.6]; //%%% sbcolor
    //selectionBar.alpha = 0.8; //%%% sbalpha
    [navigationView addSubview:selectionBar];
}

//=== When the top button is tapped
#pragma mark Setup
 -(void)tapSegmentButtonAction:(UIButton *)button {

    sDtDate = dtDate[button.tag];

    [self LoadClasses];

    __weak typeof(self) weakSelf = self;
    [weakSelf updateCurrentPageIndex:button.tag];

    NSInteger xCoor = selectionBar.frame.size.width*self.currentPageIndex;

    selectionBar.frame = CGRectMake(xCoor, selectionBar.frame.origin.y, selectionBar.frame.size.width, selectionBar.frame.size.height);
}
like image 240
Hanz Cheah Avatar asked Aug 15 '18 02:08

Hanz Cheah


2 Answers

Well, I'll prefer a more elegant way to achieve this.

First, create a UIButton subclass (let's say WeekdayButton) which will generate an attributed title from NSDate and configure so it is able to display a multiline title with different fonts.

WeekdayButton.h

#import <UIKit/UIKit.h>

@interface WeekdayButton : UIButton

@property (strong, nonatomic) NSDate *date;

@end

WeekdayButton.m

#import "WeekdayButton.h"

@implementation WeekdayButton

static NSDateFormatter *dayFormatter;
static NSDateFormatter *weekFormatter;

- (instancetype)initWithFrame:(CGRect)frame {
    self = [super initWithFrame:frame];
    [self setup];
    return self;
}

- (void)awakeFromNib {
    [super awakeFromNib];
    [self setup];
}

- (void)setup {
    self.titleLabel.numberOfLines = 2;
    self.titleLabel.lineBreakMode = NSLineBreakByWordWrapping;
    self.titleLabel.textAlignment = NSTextAlignmentCenter;
    self.backgroundColor = [UIColor colorWithRed:236.0f/255.0f green:0/255.0f blue:140.0f/255.0f alpha:0.6];
}


- (void)setDate:(NSDate *)date {
    _date = date;

    NSAttributedString *normalTitle = [self generateNormalTitle];;
    NSAttributedString *selectedTitle = [self generateSelectedTitle];

    [self setAttributedTitle:normalTitle forState:UIControlStateNormal];
    [self setAttributedTitle:selectedTitle forState:UIControlStateSelected];
}

- (NSAttributedString *)generateNormalTitle {
    NSMutableAttributedString *result = [NSMutableAttributedString new];

    if (!dayFormatter) {
        dayFormatter = [NSDateFormatter new];
        dayFormatter.dateFormat = @"dd";
    }

    if (!weekFormatter) {
        weekFormatter = [NSDateFormatter new];
        weekFormatter.dateFormat = @"EEE";
    }

    NSString *day = [[dayFormatter stringFromDate:self.date] stringByAppendingString:@"\n"];
    NSString *week = [weekFormatter stringFromDate:self.date];

    [result appendAttributedString:[[NSAttributedString alloc] initWithString:day
                                                                   attributes:@{NSFontAttributeName:[UIFont systemFontOfSize:20]}]];

    [result appendAttributedString:[[NSAttributedString alloc] initWithString:week
                                                                   attributes:@{NSFontAttributeName:[UIFont boldSystemFontOfSize:11]}]];

    [result addAttribute:NSForegroundColorAttributeName value:[UIColor whiteColor] range:NSMakeRange(0, result.length)];

    return result;
}

- (NSAttributedString *)generateSelectedTitle {
    NSMutableAttributedString *result = [[self generateNormalTitle] mutableCopy];
    [result addAttribute:NSForegroundColorAttributeName value:[UIColor blueColor] range:NSMakeRange(0, result.length)];
    return result;
}

@end

Then in your ViewController make a horizontal UIStackView, generate 7 WeekdayButton button and pass NSDate object starting from today.

ViewController.m

#import "ViewController.h"
#import "WeekdayButton.h"

@interface ViewController ()

@property (strong, nonatomic) UIStackView *stackView;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    [self setupWeekDayButtons];
}

- (void)setupWeekDayButtons {

    UIView *containerView = [[UIView alloc] initWithFrame:CGRectMake(0, 40, self.view.frame.size.width, 55)];
    containerView.autoresizingMask = UIViewAutoresizingFlexibleWidth;
    [self.view addSubview:containerView];

    containerView.layer.shadowRadius = 5;
    containerView.layer.shadowColor = [UIColor blackColor].CGColor;
    containerView.layer.shadowOpacity = 0.5;
    containerView.layer.shadowOffset = CGSizeMake(0, 5);

    self.stackView = [[UIStackView alloc] initWithFrame:containerView.bounds];
    self.stackView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
    self.stackView.axis = UILayoutConstraintAxisHorizontal;
    self.stackView.distribution = UIStackViewDistributionFill;
    self.stackView.spacing = 0;
    [containerView addSubview:self.stackView]; //Embeding stackview in a UIView cause UIStackView can't draw shadow.

    int numberOfDays = 7;
    for (int i = 0; i < numberOfDays; i++) {
        NSDate *date = [[NSDate date] dateByAddingTimeInterval:i * 24 * 3600];
        WeekdayButton *button = [WeekdayButton buttonWithType:UIButtonTypeCustom];
        [button addTarget:self action:@selector(weekDayTapped:) forControlEvents:UIControlEventTouchUpInside];
        button.date = date;
        button.translatesAutoresizingMaskIntoConstraints = NO;
        [self.stackView addArrangedSubview:button];
        [button.widthAnchor constraintEqualToAnchor:self.stackView.widthAnchor
                                         multiplier:1/(CGFloat)numberOfDays].active = YES;

        if (i != numberOfDays - 1) {
            UIView *separator = [UIView new];
            separator.translatesAutoresizingMaskIntoConstraints = NO;
            separator.backgroundColor = [UIColor whiteColor];
            [button addSubview:separator];

            [separator.widthAnchor constraintEqualToConstant:1].active = true;
            [separator.trailingAnchor constraintEqualToAnchor:button.trailingAnchor constant:0].active = YES;
            [separator.centerYAnchor constraintEqualToAnchor:button.centerYAnchor constant:0].active = YES;
            [separator.heightAnchor constraintEqualToAnchor:button.heightAnchor multiplier:1 constant:-20].active = YES;
        }
    }
}

- (void)weekDayTapped:(WeekdayButton *)sender {
    [self.stackView.arrangedSubviews makeObjectsPerformSelector:@selector(setSelected:) withObject:nil]; //Deselecting all buttons
    sender.selected = YES; 

    NSLog(@"Selected: %@", sender.date);
    //TODO: Do your logic after selection here
}

@end

Here is the result:

enter image description here

like image 74
arturdev Avatar answered Sep 18 '22 23:09

arturdev


I here provide an answer with the power of stack view and auto layout

The code will be limited and you can customized almost everything in IB.
Tint color of buttons may needs care when setting selected state.

-(IBAction)selector:(id)sender{
    UIButton * button = (UIButton *)sender;
    [button setSelected:  !button.isSelected ];
 }

setting in IB

selected State

topstackview second stack view

Use this idea, you can build your example much faster and safe.

like image 38
E.Coms Avatar answered Sep 21 '22 23:09

E.Coms