diff --git a/Source/Categories/ESTabBarController+Autolayout.m b/Source/Categories/ESTabBarController+Autolayout.m index a0c337a..dd4541d 100644 --- a/Source/Categories/ESTabBarController+Autolayout.m +++ b/Source/Categories/ESTabBarController+Autolayout.m @@ -30,9 +30,14 @@ @implementation ESTabBarController (Autolayout) - (void)setupButtonsConstraints { + + // In case dumb user doesn't know math + if(self.widthPercentages!=nil && self.widthPercentages.count + != self.tabIcons.count && [[self.widthPercentages valueForKeyPath:@"@sum.self"] floatValue] != 1.0) { + self.widthPercentages = nil; + } for (NSInteger i = 0; i < self.tabIcons.count; i++) { [self.buttons[i] setTranslatesAutoresizingMaskIntoConstraints:NO]; - [self.view addConstraints:[self leftLayoutConstraintsForButtonAtIndex:i]]; [self.view addConstraints:[self verticalLayoutConstraintsForButtonAtIndex:i]]; [self.view addConstraint:[self widthLayoutConstraintForButtonAtIndex:i]]; @@ -43,7 +48,6 @@ - (void)setupButtonsConstraints { - (void)setupSelectionIndicatorConstraints { self.selectionIndicatorLeadingConstraint = [self leadingLayoutConstraintForIndicator]; - [self.buttonsContainer addConstraint:self.selectionIndicatorLeadingConstraint]; [self.buttonsContainer addConstraints:[self widthLayoutConstraintsForIndicator]]; [self.buttonsContainer addConstraints:[self heightLayoutConstraintsForIndicator]]; @@ -53,13 +57,11 @@ - (void)setupSelectionIndicatorConstraints { - (void)setupConstraintsForChildController:(UIViewController *)controller { NSDictionary *views = @{@"view": controller.view}; - NSArray *horizontalConstraints = [NSLayoutConstraint constraintsWithVisualFormat:@"|-0-[view]-0-|" options:0 metrics:nil views:views]; [self.controllersContainer addConstraints:horizontalConstraints]; - NSArray *verticalConstraints = [NSLayoutConstraint constraintsWithVisualFormat:@"V:|-0-[view]-0-|" options:0 metrics:nil @@ -74,7 +76,6 @@ - (void)setupConstraintsForChildController:(UIViewController *)controller { - (NSArray *)leftLayoutConstraintsForButtonAtIndex:(NSInteger)index { UIButton *button = self.buttons[index]; NSArray *leftConstraints; - if (index == 0) { // First button. Stick it to its left margin. leftConstraints = [NSLayoutConstraint constraintsWithVisualFormat:@"|-(0)-[button]" @@ -84,21 +85,18 @@ - (NSArray *)leftLayoutConstraintsForButtonAtIndex:(NSInteger)index { } else { NSDictionary *views = @{@"previousButton": self.buttons[index - 1], @"button": button}; - // Stick the button to the previous one. leftConstraints = [NSLayoutConstraint constraintsWithVisualFormat:@"[previousButton]-(0)-[button]" options:0 metrics:nil views:views]; } - return leftConstraints; } - (NSArray *)verticalLayoutConstraintsForButtonAtIndex:(NSInteger)index { UIButton *button = self.buttons[index]; - // The button is sticked to its top and bottom margins. return [NSLayoutConstraint constraintsWithVisualFormat:@"V:|-(0)-[button]" options:0 @@ -109,20 +107,23 @@ - (NSArray *)verticalLayoutConstraintsForButtonAtIndex:(NSInteger)index { - (NSLayoutConstraint *)widthLayoutConstraintForButtonAtIndex:(NSInteger)index { UIButton *button = self.buttons[index]; - + CGFloat width = 1.0/self.buttons.count; + if (self.widthPercentages) { + width = [[self.widthPercentages objectAtIndex:index] floatValue]; + } + return [NSLayoutConstraint constraintWithItem:button attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:self.buttonsContainer attribute:NSLayoutAttributeWidth - multiplier:1.0 / self.buttons.count + multiplier:width constant:0.0]; } - (NSLayoutConstraint *)heightLayoutConstraintForButtonAtIndex:(NSInteger)index { UIButton *button = self.buttons[index]; - return [NSLayoutConstraint constraintWithItem:button attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual @@ -134,34 +135,45 @@ - (NSLayoutConstraint *)heightLayoutConstraintForButtonAtIndex:(NSInteger)index - (NSLayoutConstraint *)leadingLayoutConstraintForIndicator { - NSArray *constraints = [NSLayoutConstraint constraintsWithVisualFormat:@"|-(0)-[selectionIndicator]" - options:0 - metrics:nil - views:@{@"selectionIndicator": self.selectionIndicator}]; - - return [constraints firstObject]; + if(self.indicatorSizeRelativeToIcon){ + return [NSLayoutConstraint constraintWithItem:self.selectionIndicator attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:self.buttons[0] attribute:NSLayoutAttributeCenterX multiplier:1.0 constant:0.0]; + } else { + NSArray *constraints = [NSLayoutConstraint constraintsWithVisualFormat:@"|-(0)-[selectionIndicator]" + options:0 + metrics:nil + views:@{@"selectionIndicator": self.selectionIndicator}]; + return [constraints firstObject]; + } + } - (NSArray *)widthLayoutConstraintsForIndicator { - NSDictionary *views = @{@"button": self.buttons[0], - @"selectionIndicator": self.selectionIndicator}; - - return [NSLayoutConstraint constraintsWithVisualFormat:@"[selectionIndicator(==button)]" - options:0 - metrics:nil - views:views]; + if(self.indicatorSizeRelativeToIcon){ + return @[[NSLayoutConstraint constraintWithItem:self.selectionIndicator + attribute:NSLayoutAttributeWidth + relatedBy:NSLayoutRelationEqual + toItem:[self.buttons[0] valueForKey:@"imageView"] + attribute:NSLayoutAttributeWidth + multiplier:1.0 + constant:0.0]]; + } else { + NSDictionary *views = @{@"button": self.buttons[0], + @"selectionIndicator": self.selectionIndicator}; + return [NSLayoutConstraint constraintsWithVisualFormat:@"[selectionIndicator(==button)]" + options:0 + metrics:nil + views:views]; + + } } - (NSArray *)heightLayoutConstraintsForIndicator { - NSArray *heightConstraints = [NSLayoutConstraint constraintsWithVisualFormat:@"V:[selectionIndicator(==3)]" + return [NSLayoutConstraint constraintsWithVisualFormat:@"V:[selectionIndicator(==3)]" options:0 metrics:nil views:@{@"selectionIndicator": self.selectionIndicator}]; - self.selectionIndicatorHeightConstraint = [heightConstraints firstObject]; - - return heightConstraints; } @@ -172,5 +184,4 @@ - (NSArray *)bottomLayoutConstraintsForIndicator { views:@{@"selectionIndicator": self.selectionIndicator}]; } - @end diff --git a/Source/Categories/UIButton+ESTabBar.h b/Source/Categories/UIButton+ESTabBar.h index 1f4e3f3..0ba1e2d 100644 --- a/Source/Categories/UIButton+ESTabBar.h +++ b/Source/Categories/UIButton+ESTabBar.h @@ -13,6 +13,7 @@ - (void)customizeForTabBarWithImage:(UIImage *)image selectedColor:(UIColor *)selectedColor - highlighted:(BOOL)highlighted; + highlighted:(BOOL)highlighted + backgroundColor:(UIColor *)backgroundColor; @end diff --git a/Source/Categories/UIButton+ESTabBar.m b/Source/Categories/UIButton+ESTabBar.m index adcb65a..24c971a 100644 --- a/Source/Categories/UIButton+ESTabBar.m +++ b/Source/Categories/UIButton+ESTabBar.m @@ -17,10 +17,11 @@ @implementation UIButton (ESTabBar) - (void)customizeForTabBarWithImage:(UIImage *)image selectedColor:(UIColor *)selectedColor - highlighted:(BOOL)highlighted { + highlighted:(BOOL)highlighted + backgroundColor:(UIColor *)backgroundColor { if (highlighted) { [self customizeAsHighlightedButtonForTabBarWithImage:image - selectedColor:selectedColor]; + backgroundColor:backgroundColor]; } else { [self customizeAsNormalButtonForTabBarWithImage:image selectedColor:selectedColor]; @@ -32,34 +33,34 @@ - (void)customizeForTabBarWithImage:(UIImage *)image - (void)customizeAsHighlightedButtonForTabBarWithImage:(UIImage *)image - selectedColor:(UIColor *)selectedColor { - + backgroundColor:(UIColor *)backgroundColor { + // We want the image to be always white in highlighted state. self.tintColor = [UIColor whiteColor]; - [self setImage:[image imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate] + [self setImage:[image imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal] forState:UIControlStateNormal]; - + // And its background color should always be the selected color. - self.backgroundColor = selectedColor; + self.backgroundColor = backgroundColor; } - (void)customizeAsNormalButtonForTabBarWithImage:(UIImage *)image selectedColor:(UIColor *)selectedColor { - + // The tint color is the one used for selected state. self.tintColor = selectedColor; - + // When the button is not selected, we show the image always with its // original color. [self setImage:[image imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal] forState:UIControlStateNormal]; - + // When the button is selected, we apply the tint color using the // always template mode. [self setImage:[image imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate] forState:UIControlStateSelected]; - + // We don't want a background color to use the one in the tab bar. self.backgroundColor = [UIColor clearColor]; } diff --git a/Source/Controllers/ESTabBarController.h b/Source/Controllers/ESTabBarController.h index d1d98a3..1ab057e 100644 --- a/Source/Controllers/ESTabBarController.h +++ b/Source/Controllers/ESTabBarController.h @@ -7,12 +7,15 @@ // #import +#import "ESTabBarDelegate.h" typedef void (^ESTabBarAction)(void); @interface ESTabBarController : UIViewController +/// Delegate to expose some events from the tab bar +@property (nonatomic, assign) id delegate; /// Color to use for when a tab bar button is selected. @property (nonatomic, strong) UIColor *selectedColor; @@ -20,6 +23,9 @@ typedef void (^ESTabBarAction)(void); /// Background color for the view that contains the buttons. @property (nonatomic, strong) UIColor *buttonsBackgroundColor; +/// The color of the highlighted button background +@property (nonatomic, strong) UIColor *highlightedBackgroundColor; + /// The index (starting from 0) of the view controller being shown. @property (nonatomic, readonly) NSInteger selectedIndex; @@ -38,6 +44,13 @@ typedef void (^ESTabBarAction)(void); @property (nonatomic, assign) CGFloat selectionIndicatorHeight; +/// Array with all widths of the buttons. Enables to determine the width of each button in the tab bar. +@property (nonatomic,strong) NSMutableArray *widthPercentages; + +/// Sets the small indicator size to be rational to the size of the icon. +@property (nonatomic,assign) BOOL indicatorSizeRelativeToIcon; + + /** Initializes the tab bar with an array of UIImage that will be the icons to show in the tab bar. @@ -58,6 +71,11 @@ typedef void (^ESTabBarAction)(void); - (void)setViewController:(UIViewController *)viewController atIndex:(NSInteger)index; +/** + Gets the button container view. Useful for onboarding for tab bar and etc. + */ +- (UIView *)getButtonsContianer; + /** Sets an action to be fired when tapping a button at a specific index. If there is also a view controller set at that index, the action is fired immediately @@ -92,5 +110,10 @@ typedef void (^ESTabBarAction)(void); */ - (void)setSelectedIndex:(NSInteger)selectedIndex animated:(BOOL)animated; +/** + Changes icon image at specific index. + */ +- (void)setIconImageAtIndex:(NSInteger)selectedIndex icon:(UIImage *)icon; + @end diff --git a/Source/Controllers/ESTabBarController.m b/Source/Controllers/ESTabBarController.m index bcd10b1..ce1f39c 100644 --- a/Source/Controllers/ESTabBarController.m +++ b/Source/Controllers/ESTabBarController.m @@ -9,6 +9,7 @@ #import "ESTabBarController.h" #import "UIButton+ESTabBar.h" #import "ESTabBarController+Autolayout.h" +#import "ESTabBarDelegate.h" @interface ESTabBarController () @@ -43,22 +44,22 @@ @implementation ESTabBarController - (instancetype)initWithTabIcons:(NSArray *)tabIcons { NSBundle *bundle = [NSBundle bundleForClass:[ESTabBarController class]]; self = [self initWithNibName:@"ESTabBarController" bundle:bundle]; - + if (self != nil) { [self initializeWithTabIcons:tabIcons]; } - + return self; } - (instancetype)initWithTabIconNames:(NSArray *)tabIconNames { NSMutableArray *icons = [NSMutableArray array]; - + for (NSString *name in tabIconNames) { [icons addObject:[UIImage imageNamed:name]]; } - + return [self initWithTabIcons:icons]; } @@ -70,14 +71,22 @@ - (void)setSelectedColor:(UIColor *)selectedColor { if (_selectedColor != selectedColor) { _selectedColor = selectedColor; } - + [self updateInterfaceIfNeeded]; - + // Select the current button again to reflect the color change. UIButton *selectedButton = self.buttons[self.selectedIndex]; selectedButton.selected = YES; } +- (void)sethighlightedBackgroundColor:(UIColor *)color { + if (_highlightedBackgroundColor != color) { + _highlightedBackgroundColor = color; + } + + [self updateInterfaceIfNeeded]; +} + - (void)setButtonsBackgroundColor:(UIColor *)buttonsBackgroundColor { if (_buttonsBackgroundColor != buttonsBackgroundColor) { _buttonsBackgroundColor = buttonsBackgroundColor; @@ -89,7 +98,7 @@ - (void)setSeparatorLineVisible:(BOOL)visible { if (_separatorLineVisible != visible) { _separatorLineVisible = visible; } - + [self setupSeparatorLine]; } @@ -98,7 +107,7 @@ - (void)setSeparatorLineColor:(UIColor *)color { if (_separatorLineColor != color) { _separatorLineColor = color; } - + self.separatorLine.backgroundColor = color; } @@ -107,7 +116,7 @@ - (void)setSelectionIndicatorHeight:(CGFloat)selectionIndicatorHeight { if (_selectionIndicatorHeight != selectionIndicatorHeight && selectionIndicatorHeight > 0) { _selectionIndicatorHeight = selectionIndicatorHeight; } - + self.selectionIndicatorHeightConstraint.constant = selectionIndicatorHeight; } @@ -123,7 +132,7 @@ - (void)viewDidLoad { - (void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; - + if (self.selectedIndex == -1) { // We only setup everything if there isn't any selected index. [self setupInterface]; @@ -135,9 +144,9 @@ - (void)viewWillAppear:(BOOL)animated { - (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation { // When rotating, have to update the selection indicator leading to match // the selected button x, that might have changed because of the rotation. - + CGFloat selectedButtonX = [self.buttons[self.selectedIndex] frame].origin.x; - + if (self.selectionIndicatorLeadingConstraint.constant != selectedButtonX) { [UIView animateWithDuration:0.1 animations:^{ self.selectionIndicatorLeadingConstraint.constant = selectedButtonX; @@ -153,13 +162,13 @@ - (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceO - (void)setViewController:(UIViewController *)viewController atIndex:(NSInteger)index { UIViewController *currentViewController = self.controllers[@(index)]; - + if (currentViewController != nil) { [currentViewController removeFromParentViewController]; } - + self.controllers[@(index)] = viewController; - + if (index == self.selectedIndex) { // If the index is the selected one, we have to update the view // controller at that index so that the change is reflected. @@ -179,18 +188,21 @@ - (void)highlightButtonAtIndex:(NSInteger)index { [self updateInterfaceIfNeeded]; } +- (UIView *)getButtonsContianer { + return self.buttonsContainer; +} - (void)setButtonTintColor:(UIColor *)color atIndex:(NSInteger)index { if (![self.highlightedButtonIndexes containsObject:@(index)]) { UIButton *button = self.buttons[index]; - + button.tintColor = color; - + UIImage *buttonImage = [button imageForState:UIControlStateNormal]; - + [button setImage:[buttonImage imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate] forState:UIControlStateNormal]; - + [button setImage:[buttonImage imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate] forState:UIControlStateSelected]; } @@ -202,7 +214,7 @@ - (void)setBarHidden:(BOOL)hidden animated:(BOOL)animated { self.buttonsContainerHeightConstraint.constant = hidden ? 0 : self.buttonsContainerHeightConstraintInitialConstant; [self.view layoutIfNeeded]; }; - + if (animated) { [self.view layoutIfNeeded]; [UIView animateWithDuration:0.5 animations:animations]; @@ -213,10 +225,9 @@ - (void)setBarHidden:(BOOL)hidden animated:(BOOL)animated { - (void)setSelectedIndex:(NSInteger)selectedIndex animated:(BOOL)animated { - if (self.selectedIndex != selectedIndex) { - [self moveToControllerAtIndex:selectedIndex animated:animated]; - } - + // Show the selected view controller. + [self moveToControllerAtIndex:selectedIndex animated:animated]; + // Run the action if necessary. void (^action)(void) = self.actions[@(selectedIndex)]; if (action != nil) { @@ -224,13 +235,20 @@ - (void)setSelectedIndex:(NSInteger)selectedIndex animated:(BOOL)animated { } } +- (void)setIconImageAtIndex:(NSInteger)selectedIndex icon:(UIImage *)icon { + NSMutableArray *mutablearr = [self.tabIcons mutableCopy]; + mutablearr[selectedIndex] = icon; + self.tabIcons = [NSArray arrayWithArray:mutablearr]; + [self customizeButtonAtIndex:selectedIndex]; +} + #pragma mark - Actions - (void)tabButtonAction:(UIButton *)button { NSInteger index = [self.buttons indexOfObject:button]; - + if (index != NSNotFound) { [self setSelectedIndex:index animated:YES]; } @@ -243,17 +261,17 @@ - (void)tabButtonAction:(UIButton *)button { - (void)initializeWithTabIcons:(NSArray *)tabIcons { NSAssert(tabIcons.count > 0, @"The array of tab icons shouldn't be empty."); - + _tabIcons = tabIcons; - + self.controllers = [NSMutableDictionary dictionaryWithCapacity:tabIcons.count]; self.actions = [NSMutableDictionary dictionaryWithCapacity:tabIcons.count]; - + self.highlightedButtonIndexes = [NSMutableSet set]; - + // No selected index at first. _selectedIndex = -1; - + self.separatorLineColor = [UIColor lightGrayColor]; } @@ -277,17 +295,17 @@ - (void)setupInterface { - (void)setupButtons { if (self.buttons == nil) { self.buttons = [NSMutableArray arrayWithCapacity:self.tabIcons.count]; - + for (NSInteger i = 0; i < self.tabIcons.count; i++) { UIButton *button = [self createButtonForIndex:i]; - + [self.buttonsContainer addSubview:button]; self.buttons[i] = button; } - + [self setupButtonsConstraints]; } - + [self customizeButtons]; self.buttonsContainer.backgroundColor = self.buttonsBackgroundColor ?: [UIColor lightGrayColor]; } @@ -295,39 +313,46 @@ - (void)setupButtons { - (UIButton *)createButtonForIndex:(NSInteger)index { UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom]; - + [button addTarget:self action:@selector(tabButtonAction:) forControlEvents:UIControlEventTouchUpInside]; - + return button; } - (void)customizeButtons { for (NSInteger i = 0; i < self.tabIcons.count; i++) { - UIButton *button = self.buttons[i]; - - BOOL isHighlighted = [self.highlightedButtonIndexes containsObject:@(i)]; - [button customizeForTabBarWithImage:self.tabIcons[i] - selectedColor:self.selectedColor ?: [UIColor blackColor] - highlighted:isHighlighted]; - + [self customizeButtonAtIndex:i]; } } +- (void) customizeButtonAtIndex:(NSInteger)index { + UIButton *button = self.buttons[index]; + + BOOL isHighlighted = [self.highlightedButtonIndexes containsObject:@(index)]; + [button customizeForTabBarWithImage:self.tabIcons[index] + selectedColor:self.selectedColor ?: [UIColor blackColor] + highlighted:isHighlighted + backgroundColor:self.highlightedBackgroundColor ?: nil]; +} + - (void)moveToControllerAtIndex:(NSInteger)index animated:(BOOL)animated { + if (self.selectedIndex == index) { + [self.delegate tabClickedTwice:index]; + } UIViewController *controller = self.controllers[@(index)]; - + if (controller != nil) { // Deselect all the buttons excepting the selected one. for (NSInteger i = 0; i < self.buttons.count; i++) { UIButton *button = self.buttons[i]; - + BOOL selected = (i == index); button.selected = selected; - + if (self.highlightsSelectedButton && !(self.actions[@(i)] != nil && self.controllers[@(i)] == nil)) { // Only if the selected button highlighting is enabled and // the button either has a controller, or a controller and an @@ -335,19 +360,19 @@ - (void)moveToControllerAtIndex:(NSInteger)index animated:(BOOL)animated { button.alpha = selected ? 1.0 : 0.5; } } - + if (self.selectedIndex >= 0) { // Remove the current controller's view. UIViewController *currentController = self.controllers[@(self.selectedIndex)]; [currentController.view removeFromSuperview]; } - + if (![self.childViewControllers containsObject:controller]) { // If I haven't added the controller to the childs yet... [self addChildViewController:controller]; [controller didMoveToParentViewController:self]; } - + if (NSFoundationVersionNumber <= NSFoundationVersionNumber_iOS_7_1) { // Table views have an issue when disabling autoresizing // constraints in iOS 7. @@ -363,9 +388,9 @@ - (void)moveToControllerAtIndex:(NSInteger)index animated:(BOOL)animated { [self.controllersContainer addSubview:controller.view]; [self setupConstraintsForChildController:controller]; } - + [self moveSelectionIndicatorToIndex:index animated:animated]; - + _selectedIndex = index; } } @@ -376,10 +401,10 @@ - (void)setupSelectionIndicator { self.selectionIndicator = [[UIView alloc] init]; self.selectionIndicator.translatesAutoresizingMaskIntoConstraints = NO; [self.buttonsContainer addSubview:self.selectionIndicator]; - + [self setupSelectionIndicatorConstraints]; } - + self.selectionIndicator.backgroundColor = self.selectedColor ?: [UIColor blackColor]; } @@ -394,13 +419,13 @@ - (void)setupSeparatorLine { - (void)moveSelectionIndicatorToIndex:(NSInteger)index animated:(BOOL)animated { CGFloat constant = [self.buttons[index] frame].origin.x; - + [self.buttonsContainer layoutIfNeeded]; void (^animations)(void) = ^{ self.selectionIndicatorLeadingConstraint.constant = constant; [self.buttonsContainer layoutIfNeeded]; }; - + if (animated) { [UIView animateWithDuration:0.25 delay:0.0 diff --git a/Source/Protocols/ESTabBarDelegate.h b/Source/Protocols/ESTabBarDelegate.h new file mode 100644 index 0000000..da76aa3 --- /dev/null +++ b/Source/Protocols/ESTabBarDelegate.h @@ -0,0 +1,4 @@ +#import +@protocol ESTabBarDelegate +- (void)tabClickedTwice:(NSInteger)index; +@end