diff --git a/components/BottomNavigation/src/MDCBottomNavigationBar.m b/components/BottomNavigation/src/MDCBottomNavigationBar.m index 92b25865012..5f12ee5a1c6 100644 --- a/components/BottomNavigation/src/MDCBottomNavigationBar.m +++ b/components/BottomNavigation/src/MDCBottomNavigationBar.m @@ -49,6 +49,7 @@ static NSString *const kMDCBottomNavigationBarAccessibilityLabel = @"accessibilityLabel"; static NSString *const kMDCBottomNavigationBarAccessibilityHint = @"accessibilityHint"; static NSString *const kMDCBottomNavigationBarIsAccessibilityElement = @"isAccessibilityElement"; +static NSString *const kTitlePositionAdjustment = @"titlePositionAdjustment"; static NSString *const kMDCBottomNavigationBarOfAnnouncement = @"of"; @@ -322,6 +323,10 @@ - (void)addObserversToTabBarItems { forKeyPath:kMDCBottomNavigationBarIsAccessibilityElement options:NSKeyValueObservingOptionNew context:nil]; + [item addObserver:self + forKeyPath:kTitlePositionAdjustment + options:NSKeyValueObservingOptionNew + context:nil]; } } @@ -338,6 +343,7 @@ - (void)removeObserversFromTabBarItems { [item removeObserver:self forKeyPath:kMDCBottomNavigationBarAccessibilityLabel]; [item removeObserver:self forKeyPath:kMDCBottomNavigationBarAccessibilityHint]; [item removeObserver:self forKeyPath:kMDCBottomNavigationBarIsAccessibilityElement]; + [item removeObserver:self forKeyPath:kTitlePositionAdjustment]; } @catch (NSException *exception) { if (exception) { // No need to do anything if there are no observers. @@ -380,6 +386,8 @@ - (void)observeValueForKeyPath:(NSString *)keyPath itemView.accessibilityHint = change[kMDCBottomNavigationBarNewString]; } else if ([keyPath isEqualToString:kMDCBottomNavigationBarIsAccessibilityElement]) { itemView.isAccessibilityElement = [change[kMDCBottomNavigationBarNewString] boolValue]; + } else if ([keyPath isEqualToString:kTitlePositionAdjustment]) { + itemView.titlePositionAdjustment = [change[kMDCBottomNavigationBarNewString] UIOffsetValue]; } } } @@ -482,6 +490,7 @@ - (void)setItems:(NSArray *)items { itemView.contentVerticalMargin = self.itemsContentVerticalMargin; itemView.contentHorizontalMargin = self.itemsContentHorizontalMargin; itemView.truncatesTitle = self.truncatesLongTitles; + itemView.titlePositionAdjustment = item.titlePositionAdjustment; MDCInkTouchController *controller = [[MDCInkTouchController alloc] initWithView:itemView]; controller.delegate = self; [self.inkControllers addObject:controller]; diff --git a/components/BottomNavigation/src/private/MDCBottomNavigationItemView.h b/components/BottomNavigation/src/private/MDCBottomNavigationItemView.h index 61bf14de230..716af20b21f 100644 --- a/components/BottomNavigation/src/private/MDCBottomNavigationItemView.h +++ b/components/BottomNavigation/src/private/MDCBottomNavigationItemView.h @@ -23,6 +23,7 @@ @property(nonatomic, assign) BOOL selected; @property(nonatomic, assign) MDCBottomNavigationBarTitleVisibility titleVisibility; @property(nonatomic, strong) MDCInkView *inkView; +@property(nonatomic, assign) UIOffset titlePositionAdjustment; @property(nonatomic, copy) NSString *badgeValue; @property(nonatomic, copy) NSString *title; diff --git a/components/BottomNavigation/src/private/MDCBottomNavigationItemView.m b/components/BottomNavigation/src/private/MDCBottomNavigationItemView.m index 1873f614d01..2f1170fdf73 100644 --- a/components/BottomNavigation/src/private/MDCBottomNavigationItemView.m +++ b/components/BottomNavigation/src/private/MDCBottomNavigationItemView.m @@ -242,7 +242,9 @@ - (void)calculateVerticalLayoutInBounds:(CGRect)contentBounds CGFloat centerX = CGRectGetMidX(contentBoundingRect); CGPoint iconImageViewCenter = CGPointMake(centerX, centerY - totalContentHeight / 2 + iconHeight / 2); - CGPoint labelCenter = CGPointMake(centerX, centerY + totalContentHeight / 2 - labelHeight / 2); + // Ignore the horizontal titlePositionAdjustment in a vertical layout to match UITabBar behavior. + CGPoint labelCenter = CGPointMake(centerX, centerY + totalContentHeight / 2 - labelHeight / 2 + + self.titlePositionAdjustment.vertical); CGFloat availableContentWidth = CGRectGetWidth(contentBoundingRect); if (self.truncatesTitle && (labelSize.width > availableContentWidth)) { labelSize = CGSizeMake(availableContentWidth, labelSize.height); @@ -301,8 +303,9 @@ - (void)calculateHorizontalLayoutInBounds:(CGRect)contentBounds CGPointMake(layoutStartingPoint + rtlCoefficient * iconCenterOffset, centerY); CGFloat labelOffsetFromIcon = iconImageViewSize.width / 2 + self.contentHorizontalMargin + labelSize.width / 2; - CGPoint labelCenter = - CGPointMake(iconImageViewCenter.x + rtlCoefficient * labelOffsetFromIcon, centerY); + CGPoint labelCenter = CGPointMake(iconImageViewCenter.x + rtlCoefficient * labelOffsetFromIcon + + self.titlePositionAdjustment.horizontal, + centerY + self.titlePositionAdjustment.vertical); // Assign the frames to the inout arguments if (outLabelFrame != NULL) { @@ -558,6 +561,13 @@ - (NSString *)accessibilityIdentifier { return self.button.accessibilityIdentifier; } +- (void)setTitlePositionAdjustment:(UIOffset)titlePositionAdjustment { + if (!UIOffsetEqualToOffset(_titlePositionAdjustment, titlePositionAdjustment)) { + _titlePositionAdjustment = titlePositionAdjustment; + [self setNeedsLayout]; + } +} + #pragma mark - Resource bundle + (NSBundle *)bundle { diff --git a/components/BottomNavigation/tests/snapshot/MDCBottomNavigationBarSnapshotTests.m b/components/BottomNavigation/tests/snapshot/MDCBottomNavigationBarSnapshotTests.m index 6f4b994f26b..72804eba7e2 100644 --- a/components/BottomNavigation/tests/snapshot/MDCBottomNavigationBarSnapshotTests.m +++ b/components/BottomNavigation/tests/snapshot/MDCBottomNavigationBarSnapshotTests.m @@ -830,6 +830,90 @@ - (void)testCenteredRegularAlwaysFiveItemsFitWidthFitHeightRTL { [self generateAndVerifySnapshot]; } +#pragma mark - Layout Adjustments + +- (void)testTitlePositionAdjustmentJustifiedAdjacentCompactLTR { + // Given + MDCMutableUITraitCollection *traitCollection = [[MDCMutableUITraitCollection alloc] init]; + traitCollection.horizontalSizeClassOverride = UIUserInterfaceSizeClassCompact; + self.navigationBar.titleVisibility = MDCBottomNavigationBarTitleVisibilityAlways; + self.navigationBar.alignment = MDCBottomNavigationBarAlignmentJustifiedAdjacentTitles; + self.navigationBar.selectedItem = self.tabItem2; + self.navigationBar.traitCollectionOverride = traitCollection; + CGSize fitSize = [self.navigationBar sizeThatFits:CGSizeMake(kWidthWide, kHeightTall)]; + self.navigationBar.frame = CGRectMake(0, 0, fitSize.width, fitSize.height); + [self performInkTouchOnBar:self.navigationBar item:self.tabItem1]; + + // When + self.tabItem1.titlePositionAdjustment = UIOffsetMake(20, -20); + self.tabItem3.titlePositionAdjustment = UIOffsetMake(-20, 20); + + // Then + [self generateAndVerifySnapshot]; +} + +- (void)testTitlePositionAdjustmentJustifiedAdjacentCompactRTL { + // Given + MDCMutableUITraitCollection *traitCollection = [[MDCMutableUITraitCollection alloc] init]; + traitCollection.horizontalSizeClassOverride = UIUserInterfaceSizeClassCompact; + self.navigationBar.titleVisibility = MDCBottomNavigationBarTitleVisibilityAlways; + self.navigationBar.alignment = MDCBottomNavigationBarAlignmentJustifiedAdjacentTitles; + self.navigationBar.selectedItem = self.tabItem2; + self.navigationBar.traitCollectionOverride = traitCollection; + CGSize fitSize = [self.navigationBar sizeThatFits:CGSizeMake(kWidthWide, kHeightTall)]; + self.navigationBar.frame = CGRectMake(0, 0, fitSize.width, fitSize.height); + [self performInkTouchOnBar:self.navigationBar item:self.tabItem1]; + [self changeToRTLAndArabicWithTitle:kShortTitleArabic]; + + // When + self.tabItem1.titlePositionAdjustment = UIOffsetMake(20, -20); + self.tabItem3.titlePositionAdjustment = UIOffsetMake(-20, 20); + + // Then + [self generateAndVerifySnapshot]; +} + +- (void)testTitlePositionAdjustmentJustifiedAdjacentRegularLTR { + // Given + MDCMutableUITraitCollection *traitCollection = [[MDCMutableUITraitCollection alloc] init]; + traitCollection.horizontalSizeClassOverride = UIUserInterfaceSizeClassRegular; + self.navigationBar.titleVisibility = MDCBottomNavigationBarTitleVisibilityAlways; + self.navigationBar.alignment = MDCBottomNavigationBarAlignmentJustifiedAdjacentTitles; + self.navigationBar.selectedItem = self.tabItem2; + self.navigationBar.traitCollectionOverride = traitCollection; + CGSize fitSize = [self.navigationBar sizeThatFits:CGSizeMake(kWidthWide, kHeightTall)]; + self.navigationBar.frame = CGRectMake(0, 0, fitSize.width, fitSize.height); + [self performInkTouchOnBar:self.navigationBar item:self.tabItem1]; + + // When + self.tabItem1.titlePositionAdjustment = UIOffsetMake(20, -20); + self.tabItem3.titlePositionAdjustment = UIOffsetMake(-20, 20); + + // Then + [self generateAndVerifySnapshot]; +} + +- (void)testTitlePositionAdjustmentJustifiedAdjacentRegularRTL { + // Given + MDCMutableUITraitCollection *traitCollection = [[MDCMutableUITraitCollection alloc] init]; + traitCollection.horizontalSizeClassOverride = UIUserInterfaceSizeClassRegular; + self.navigationBar.titleVisibility = MDCBottomNavigationBarTitleVisibilityAlways; + self.navigationBar.alignment = MDCBottomNavigationBarAlignmentJustifiedAdjacentTitles; + self.navigationBar.selectedItem = self.tabItem2; + self.navigationBar.traitCollectionOverride = traitCollection; + CGSize fitSize = [self.navigationBar sizeThatFits:CGSizeMake(kWidthWide, kHeightTall)]; + self.navigationBar.frame = CGRectMake(0, 0, fitSize.width, fitSize.height); + [self performInkTouchOnBar:self.navigationBar item:self.tabItem1]; + [self changeToRTLAndArabicWithTitle:kShortTitleArabic]; + + // When + self.tabItem1.titlePositionAdjustment = UIOffsetMake(20, -20); + self.tabItem3.titlePositionAdjustment = UIOffsetMake(-20, 20); + + // Then + [self generateAndVerifySnapshot]; +} + #pragma mark - Theming Material baseline - (void)testMaterialBaselineTheme { diff --git a/snapshot_test_goldens/goldens_64/MDCBottomNavigationBarSnapshotTests/testTitlePositionAdjustmentJustifiedAdjacentCompactLTR_11_2@2x.png b/snapshot_test_goldens/goldens_64/MDCBottomNavigationBarSnapshotTests/testTitlePositionAdjustmentJustifiedAdjacentCompactLTR_11_2@2x.png new file mode 100644 index 00000000000..9d9aca77c72 Binary files /dev/null and b/snapshot_test_goldens/goldens_64/MDCBottomNavigationBarSnapshotTests/testTitlePositionAdjustmentJustifiedAdjacentCompactLTR_11_2@2x.png differ diff --git a/snapshot_test_goldens/goldens_64/MDCBottomNavigationBarSnapshotTests/testTitlePositionAdjustmentJustifiedAdjacentCompactRTL_11_2@2x.png b/snapshot_test_goldens/goldens_64/MDCBottomNavigationBarSnapshotTests/testTitlePositionAdjustmentJustifiedAdjacentCompactRTL_11_2@2x.png new file mode 100644 index 00000000000..83a7bfc1878 Binary files /dev/null and b/snapshot_test_goldens/goldens_64/MDCBottomNavigationBarSnapshotTests/testTitlePositionAdjustmentJustifiedAdjacentCompactRTL_11_2@2x.png differ diff --git a/snapshot_test_goldens/goldens_64/MDCBottomNavigationBarSnapshotTests/testTitlePositionAdjustmentJustifiedAdjacentRegularLTR_11_2@2x.png b/snapshot_test_goldens/goldens_64/MDCBottomNavigationBarSnapshotTests/testTitlePositionAdjustmentJustifiedAdjacentRegularLTR_11_2@2x.png new file mode 100644 index 00000000000..30f29610308 Binary files /dev/null and b/snapshot_test_goldens/goldens_64/MDCBottomNavigationBarSnapshotTests/testTitlePositionAdjustmentJustifiedAdjacentRegularLTR_11_2@2x.png differ diff --git a/snapshot_test_goldens/goldens_64/MDCBottomNavigationBarSnapshotTests/testTitlePositionAdjustmentJustifiedAdjacentRegularRTL_11_2@2x.png b/snapshot_test_goldens/goldens_64/MDCBottomNavigationBarSnapshotTests/testTitlePositionAdjustmentJustifiedAdjacentRegularRTL_11_2@2x.png new file mode 100644 index 00000000000..f603f0223c2 Binary files /dev/null and b/snapshot_test_goldens/goldens_64/MDCBottomNavigationBarSnapshotTests/testTitlePositionAdjustmentJustifiedAdjacentRegularRTL_11_2@2x.png differ