Skip to content

Commit

Permalink
[TabBarView] Add titleFontForState APIs (#7757)
Browse files Browse the repository at this point in the history
Adds `titleFont:forState:` APIs to customize the font of selected and unselected items generally. A future change may enable support for `-[UIBarItem titleTextAttributesForState:]`.

## Dragons Example View Controller

|Before|After|
|---|---|
|![Simulator Screen Shot - iPhone 7 - 2019-06-30 at 22 38 45](https://user-images.githubusercontent.com/1753199/60407218-e9634c80-9b87-11e9-9bef-10c2449002a7.png)|![Simulator Screen Shot - iPhone 7 - 2019-06-30 at 22 39 36](https://user-images.githubusercontent.com/1753199/60407236-f718d200-9b87-11e9-8380-195235c9494e.png)|


Part of #7657
  • Loading branch information
codeman7 authored and Robert Moore committed Jul 1, 2019
1 parent 494feae commit 029692a
Show file tree
Hide file tree
Showing 10 changed files with 175 additions and 0 deletions.
1 change: 1 addition & 0 deletions MaterialComponentsBeta.podspec
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ Pod::Spec.new do |mdc|
unit_tests.source_files = [
"components/#{extension.base_name.split('+')[0]}/tests/unit/#{extension.base_name.split('+')[1]}/*.{h,m,swift}",
]
unit_tests.dependency "MaterialComponents/Typography"
end
end

Expand Down
1 change: 1 addition & 0 deletions components/Tabs/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,7 @@ mdc_objc_library(
deps = [
":TabBarView",
":privateTabBarView",
"//components/Typography",
],
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,8 @@ - (void)viewDidLoad {
forState:UIControlStateNormal];
[self.tabBar setImageTintColor:self.containerScheme.colorScheme.primaryColor
forState:UIControlStateSelected];
[self.tabBar setTitleFont:self.containerScheme.typographyScheme.button
forState:UIControlStateNormal];
self.tabBar.selectedItem = item4;
self.tabBar.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addSubview:self.tabBar];
Expand Down
16 changes: 16 additions & 0 deletions components/Tabs/src/TabBarView/MDCTabBarView.h
Original file line number Diff line number Diff line change
Expand Up @@ -68,4 +68,20 @@ __attribute__((objc_subclassing_restricted)) @interface MDCTabBarView : UIScroll
*/
- (nullable UIColor *)titleColorForState:(UIControlState)state;

/**
Sets the font of the bar items' title for the given control state. Supports
@c UIControlStateNormal and @c UIControlStateSelected.
If no value for a control state is set, the value for @c UIControlStateNormal is used. If no value
for @c UIControlStateNormal is set, then a default value is used.
*/
- (void)setTitleFont:(nullable UIFont *)titleFont forState:(UIControlState)state;

/**
Returns the font of the bar items' title for the given control state.
If no value for a control state is set, the value for @c UIControlStateNormal is returned.
*/
- (nullable UIFont *)titleFontForState:(UIControlState)state;

@end
39 changes: 39 additions & 0 deletions components/Tabs/src/TabBarView/MDCTabBarView.m
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@ @interface MDCTabBarView ()
/** The image tint colors for bar items. */
@property(nonnull, nonatomic, strong)
NSMutableDictionary<NSNumber *, UIColor *> *stateToImageTintColor;

/** The title font for bar items. */
@property(nonnull, nonatomic, strong) NSMutableDictionary<NSNumber *, UIFont *> *stateToTitleFont;
@end

@implementation MDCTabBarView
Expand All @@ -63,6 +66,7 @@ - (instancetype)init {
_items = @[];
_stateToImageTintColor = [NSMutableDictionary dictionary];
_stateToTitleColor = [NSMutableDictionary dictionary];
_stateToTitleFont = [NSMutableDictionary dictionary];
self.backgroundColor = UIColor.whiteColor;
self.showsHorizontalScrollIndicator = NO;

Expand Down Expand Up @@ -252,6 +256,41 @@ - (UIColor *)titleColorForState:(UIControlState)state {
return titleColor;
}

- (void)updateTitleFontForAllViews {
for (UITabBarItem *item in self.items) {
NSUInteger indexOfItem = [self.items indexOfObject:item];
// This is a significant error, but defensive coding is preferred.
if (indexOfItem == NSNotFound || indexOfItem >= self.containerView.arrangedSubviews.count) {
NSAssert(NO, @"Unable to find associated item view for (%@)", item);
continue;
}
UIView *itemView = self.containerView.arrangedSubviews[indexOfItem];
// Skip custom views
if (![itemView isKindOfClass:[MDCTabBarViewItemView class]]) {
continue;
}
MDCTabBarViewItemView *tabBarViewItemView = (MDCTabBarViewItemView *)itemView;
if (item == self.selectedItem) {
tabBarViewItemView.titleLabel.font = [self titleFontForState:UIControlStateSelected];
} else {
tabBarViewItemView.titleLabel.font = [self titleFontForState:UIControlStateNormal];
}
}
}

- (void)setTitleFont:(UIFont *)titleFont forState:(UIControlState)state {
self.stateToTitleFont[@(state)] = titleFont;
[self updateTitleFontForAllViews];
}

- (UIFont *)titleFontForState:(UIControlState)state {
UIFont *titleFont = self.stateToTitleFont[@(state)];
if (!titleFont) {
titleFont = self.stateToTitleFont[@(UIControlStateNormal)];
}
return titleFont;
}

#pragma mark - Key-Value Observing (KVO)

- (void)addObserversToTabBarItems {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,8 @@ - (void)testChangingSelectedImageOfSelectedItemAfterAddingToBarDoesNothing {
[self generateSnapshotAndVerifyForView:self.tabBarView];
}

#pragma mark - Layout Sizes

// TODO(https://github.com/material-components/material-components-ios/issues/7717): This golden
// is incorrect due to a suspected bug in MDCTabBarViewItemView.
- (void)testSettingBoundsTooNarrowWithItemsLessThanMinimumWidthResultsInScrollableLayout {
Expand Down Expand Up @@ -489,6 +491,58 @@ - (void)testSetImageTintColorExplicitlyToNilUsesTintColor {
[self generateSnapshotAndVerifyForView:self.tabBarView];
}

- (void)testSetTitleFontForExplicitItemStates {
// Given
UITabBarItem *item1 = [[UITabBarItem alloc] initWithTitle:@"One" image:self.typicalIcon1 tag:0];
UITabBarItem *item2 = [[UITabBarItem alloc] initWithTitle:@"Two" image:self.typicalIcon2 tag:2];
UITabBarItem *item3 = [[UITabBarItem alloc] initWithTitle:@"Three" image:self.typicalIcon3 tag:3];
self.tabBarView.items = @[ item1, item2, item3 ];
[self.tabBarView setSelectedItem:item2 animated:NO];

// When
[self.tabBarView setTitleFont:[UIFont systemFontOfSize:8] forState:UIControlStateNormal];
[self.tabBarView setTitleFont:[UIFont systemFontOfSize:24] forState:UIControlStateSelected];
[self.tabBarView sizeToFit];

// Then
[self generateSnapshotAndVerifyForView:self.tabBarView];
}

- (void)testSetTitleFontForNormalStateAppliesToSelectedItem {
// Given
UITabBarItem *item1 = [[UITabBarItem alloc] initWithTitle:@"One" image:self.typicalIcon1 tag:0];
UITabBarItem *item2 = [[UITabBarItem alloc] initWithTitle:@"Two" image:self.typicalIcon2 tag:2];
UITabBarItem *item3 = [[UITabBarItem alloc] initWithTitle:@"Three" image:self.typicalIcon3 tag:3];
self.tabBarView.items = @[ item1, item2, item3 ];
[self.tabBarView setSelectedItem:item2 animated:NO];

// When
[self.tabBarView setTitleFont:[UIFont systemFontOfSize:8] forState:UIControlStateNormal];
[self.tabBarView sizeToFit];

// Then
[self generateSnapshotAndVerifyForView:self.tabBarView];
}

- (void)testSetTitleFontExplicitlyToNilUsesDefaultFont {
// Given
UITabBarItem *item1 = [[UITabBarItem alloc] initWithTitle:@"One" image:self.typicalIcon1 tag:0];
UITabBarItem *item2 = [[UITabBarItem alloc] initWithTitle:@"Two" image:self.typicalIcon2 tag:2];
UITabBarItem *item3 = [[UITabBarItem alloc] initWithTitle:@"Three" image:self.typicalIcon3 tag:3];
self.tabBarView.items = @[ item1, item2, item3 ];
[self.tabBarView setSelectedItem:item2 animated:NO];
[self.tabBarView setTitleFont:[UIFont systemFontOfSize:8] forState:UIControlStateNormal];
[self.tabBarView setTitleFont:[UIFont systemFontOfSize:24] forState:UIControlStateSelected];

// When
[self.tabBarView setTitleFont:nil forState:UIControlStateNormal];
[self.tabBarView setTitleFont:nil forState:UIControlStateSelected];
[self.tabBarView sizeToFit];

// Then
[self generateSnapshotAndVerifyForView:self.tabBarView];
}

- (void)testChangingSelectionUpdatesItemStyle {
// Given
self.tabBarView.bounds = CGRectMake(0, 0, 360, kExpectedHeightTitlesAndIcons);
Expand Down
62 changes: 62 additions & 0 deletions components/Tabs/tests/unit/TabBarView/MDCTabBarViewTests.m
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
#import "../../../src/TabBarView/private/MDCTabBarViewItemView.h"
#import "MDCTabBarView.h"
#import "MDCTabBarViewDelegate.h"
#import "MaterialTypography.h"

// Minimum height of the MDCTabBar view.
static const CGFloat kMinHeight = 48;
Expand Down Expand Up @@ -382,6 +383,67 @@ - (void)testTitleColorForStateWithNoValuesReturnsNil {
XCTAssertNil([self.tabBarView titleColorForState:UIControlStateSelected]);
}

- (void)testTitleFontForStateFallsBackToNormalState {
// Given
UIFont *fakeFont = [UIFont systemFontOfSize:25];
[self.tabBarView setTitleFont:nil forState:UIControlStateNormal];
[self.tabBarView setTitleFont:nil forState:UIControlStateSelected];

// When
[self.tabBarView setTitleFont:fakeFont forState:UIControlStateNormal];

// Then
[self assertTitleFontForState:UIControlStateSelected equalsFont:fakeFont];
}

- (void)testTitleFontForStateReturnsExpectedValue {
// Given
UIFont *fakeNormalFont = [UIFont systemFontOfSize:25];
UIFont *fakeSelectedFont = [UIFont systemFontOfSize:24];
[self.tabBarView setTitleFont:nil forState:UIControlStateNormal];
[self.tabBarView setTitleFont:nil forState:UIControlStateSelected];

// When
[self.tabBarView setTitleFont:fakeNormalFont forState:UIControlStateNormal];
[self.tabBarView setTitleFont:fakeSelectedFont forState:UIControlStateSelected];

// Then
[self assertTitleFontForState:UIControlStateNormal equalsFont:fakeNormalFont];
[self assertTitleFontForState:UIControlStateSelected equalsFont:fakeSelectedFont];
}

- (void)testTitleFontForStateSetToNilFallsBackToNormal {
// Given
UIFont *fakeNormalFont = [UIFont systemFontOfSize:25];
UIFont *fakeSelectedFont = [UIFont systemFontOfSize:24];
[self.tabBarView setTitleFont:nil forState:UIControlStateNormal];
[self.tabBarView setTitleFont:fakeSelectedFont forState:UIControlStateSelected];

// When
[self.tabBarView setTitleFont:fakeNormalFont forState:UIControlStateNormal];
[self.tabBarView setTitleFont:nil forState:UIControlStateSelected];

// Then
[self assertTitleFontForState:UIControlStateNormal equalsFont:fakeNormalFont];
[self assertTitleFontForState:UIControlStateSelected equalsFont:fakeNormalFont];
}

- (void)testTitleFontForStateWithNoValuesReturnsNil {
// When
[self.tabBarView setTitleFont:nil forState:UIControlStateNormal];
[self.tabBarView setTitleFont:nil forState:UIControlStateSelected];

// Then
XCTAssertNil([self.tabBarView titleColorForState:UIControlStateNormal]);
XCTAssertNil([self.tabBarView titleColorForState:UIControlStateSelected]);
}

- (void)assertTitleFontForState:(UIControlState)state equalsFont:(UIFont *)font {
UIFont *statefulTitleFont = [self.tabBarView titleFontForState:state];
XCTAssertTrue([statefulTitleFont mdc_isSimplyEqual:font], @"(%@) is not equal to (%@)",
statefulTitleFont, font);
}

#pragma mark - Delegate

- (void)testReturnNoForShouldSelectItemPreventsSelection {
Expand Down
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit 029692a

Please sign in to comment.