Skip to content
This repository was archived by the owner on Dec 11, 2025. It is now read-only.

Commit e7a6b60

Browse files
Nobodymaterial-automation
authored andcommitted
Add new item layout to align image and text to top with padding in between, and a new property to manage which image-and-text item layout to use.
PiperOrigin-RevId: 735890394
1 parent 719af90 commit e7a6b60

File tree

5 files changed

+212
-239
lines changed

5 files changed

+212
-239
lines changed

components/Tabs/src/TabBarView/MDCTabBarView.h

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -331,6 +331,19 @@ __attribute__((objc_subclassing_restricted)) @interface MDCTabBarView : UIScroll
331331
*/
332332
@property(nonatomic) CGPoint badgeOffset;
333333

334+
/**
335+
* Whether or not to enforce padding between the tab bar item's image and title.
336+
*
337+
* If set to @c YES, the tab bar item's image and title will be top-aligned with the
338+
* @c itemImageTitlePadding in between.
339+
*
340+
* If set to @c NO, the tab bar item's image will be aligned to the top while the title will be
341+
* aligned to the bottom inside the edge insets.
342+
*
343+
* Defaults to @c NO.
344+
*/
345+
@property(nonatomic) BOOL enforceTextAndImagePadding;
346+
334347
@end
335348

336349
#if MDC_AVAILABLE_SDK_IOS(13_0) && !TARGET_OS_TV

components/Tabs/src/TabBarView/MDCTabBarView.m

Lines changed: 29 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919
#import "private/MDCTabBarViewItemView.h"
2020
#import "private/MDCTabBarViewItemViewDelegate.h"
2121
#import "private/MDCTabBarViewPrivateIndicatorContext.h"
22-
#import "MDCAvailability.h"
2322
#import "MDCBadgeAppearance.h"
2423
#import "MDCRippleTouchController.h"
2524
#import "MDCRippleView.h"
@@ -89,11 +88,9 @@
8988
static NSString *const kBadgeValueKeyPath = @"badgeValue";
9089
static NSString *const kBadgeColorKeyPath = @"badgeColor";
9190

92-
#ifdef __IPHONE_13_4
9391
@interface MDCTabBarView (PointerInteractions) <UIPointerInteractionDelegate,
9492
MDCTabBarViewItemViewDelegate>
9593
@end
96-
#endif
9794

9895
@interface MDCTabBarView ()
9996

@@ -131,14 +128,11 @@ The content padding (as UIEdgeInsets) for each layout style. The layout style is
131128

132129
@property(nonatomic) BOOL useIndividualItemViewContentInsets;
133130

134-
#if defined(__IPHONE_13_0) && (__IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_13_0)
135131
/**
136132
The last large content viewer item displayed by the content viewer while the interaction is
137133
running. When the interaction ends this property is nil.
138134
*/
139-
@property(nonatomic, nullable) id<UILargeContentViewerItem> lastLargeContentViewerItem
140-
NS_AVAILABLE_IOS(13_0);
141-
#endif // defined(__IPHONE_13_0) && (__IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_13_0)
135+
@property(nonatomic, nullable) id<UILargeContentViewerItem> lastLargeContentViewerItem;
142136

143137
@end
144138

@@ -182,6 +176,7 @@ - (void)commonMDCTabBarViewInit {
182176
self.backgroundColor = UIColor.whiteColor;
183177
self.showsHorizontalScrollIndicator = NO;
184178

179+
_enforceTextAndImagePadding = NO;
185180
_itemBadgeAppearance = [[MDCBadgeAppearance alloc] init];
186181
_itemBadgeAppearance.textColor = UIColor.whiteColor;
187182
_itemBadgeAppearance.font = [UIFont systemFontOfSize:kBadgeFontSize];
@@ -218,13 +213,9 @@ - (void)commonMDCTabBarViewInit {
218213
// gesture.
219214
self.scrollsToTop = NO;
220215

221-
#if defined(__IPHONE_13_0) && (__IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_13_0)
222-
if (@available(iOS 13, *)) {
223-
// If clients report conflicting gesture recognizers please see proposed solution in the
224-
// internal document: go/mdc-ios-bottomnavigation-largecontentvieweritem
225-
[self addInteraction:[[UILargeContentViewerInteraction alloc] initWithDelegate:self]];
226-
}
227-
#endif // defined(__IPHONE_13_0) && (__IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_13_0)
216+
// If clients report conflicting gesture recognizers please see proposed solution in the
217+
// internal document: go/mdc-ios-bottomnavigation-largecontentvieweritem
218+
[self addInteraction:[[UILargeContentViewerInteraction alloc] initWithDelegate:self]];
228219
}
229220

230221
- (void)dealloc {
@@ -280,18 +271,6 @@ - (void)setItemViewContentInsets:(UIEdgeInsets)itemViewContentInsets {
280271
_useIndividualItemViewContentInsets = NO;
281272
}
282273

283-
- (void)setItemViewContentInsetsTextOnly:(UIEdgeInsets)itemViewContentInsetsTextOnly {
284-
_itemViewContentInsetsTextOnly = itemViewContentInsetsTextOnly;
285-
}
286-
287-
- (void)setItemViewContentInsetsImageOnly:(UIEdgeInsets)itemViewContentInsetsImageOnly {
288-
_itemViewContentInsetsImageOnly = itemViewContentInsetsImageOnly;
289-
}
290-
291-
- (void)setItemViewContentInsetsTextAndImage:(UIEdgeInsets)itemViewContentInsetsTextAndImage {
292-
_itemViewContentInsetsTextAndImage = itemViewContentInsetsTextAndImage;
293-
}
294-
295274
- (void)setMinItemHeightTitleAndImage:(CGFloat)minItemHeightTitleAndImage {
296275
_minItemHeightTitleAndImage = minItemHeightTitleAndImage;
297276
for (UIView *itemView in self.itemViews) {
@@ -310,6 +289,15 @@ - (void)setItemImageTitlePadding:(CGFloat)itemImageTitlePadding {
310289
}
311290
}
312291

292+
- (void)setEnforceTextAndImagePadding:(BOOL)enforceTextAndImagePadding {
293+
_enforceTextAndImagePadding = enforceTextAndImagePadding;
294+
for (UIView *itemView in self.itemViews) {
295+
if ([itemView isKindOfClass:[MDCTabBarViewItemView class]]) {
296+
((MDCTabBarViewItemView *)itemView).enforceTextAndImagePadding = _enforceTextAndImagePadding;
297+
}
298+
}
299+
}
300+
313301
- (void)setItems:(NSArray<UITabBarItem *> *)items {
314302
NSParameterAssert(items);
315303

@@ -358,31 +346,17 @@ - (void)setItems:(NSArray<UITabBarItem *> *)items {
358346
mdcItemView.minHeightTitleAndImage = self.minItemHeightTitleAndImage;
359347
mdcItemView.imageTitlePadding = self.itemImageTitlePadding;
360348
mdcItemView.badgeOffset = self.badgeOffset;
361-
362-
#if defined(__IPHONE_13_0) && (__IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_13_0)
363-
if (@available(iOS 13, *)) {
364-
mdcItemView.largeContentImageInsets = item.largeContentSizeImageInsets;
365-
mdcItemView.largeContentImage = item.largeContentSizeImage;
366-
}
367-
#endif // defined(__IPHONE_13_0) && (__IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_13_0)
368-
349+
mdcItemView.enforceTextAndImagePadding = self.enforceTextAndImagePadding;
350+
mdcItemView.largeContentImageInsets = item.largeContentSizeImageInsets;
351+
mdcItemView.largeContentImage = item.largeContentSizeImage;
369352
itemView = mdcItemView;
370353
}
371354
UITapGestureRecognizer *tapGesture =
372355
[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(didTapItemView:)];
373356
[itemView addGestureRecognizer:tapGesture];
374357

375-
#ifdef __IPHONE_13_4
376-
if (@available(iOS 13.4, *)) {
377-
// Because some iOS 13 betas did not have the UIPointerInteraction class, we need to verify
378-
// that it exists before attempting to use it.
379-
if (NSClassFromString(@"UIPointerInteraction")) {
380-
UIPointerInteraction *pointerInteraction =
381-
[[UIPointerInteraction alloc] initWithDelegate:self];
382-
[itemView addInteraction:pointerInteraction];
383-
}
384-
}
385-
#endif
358+
UIPointerInteraction *pointerInteraction = [[UIPointerInteraction alloc] initWithDelegate:self];
359+
[itemView addInteraction:pointerInteraction];
386360

387361
[self addSubview:itemView];
388362
[itemViews addObject:itemView];
@@ -835,18 +809,12 @@ - (void)observeValueForKeyPath:(nullable NSString *)keyPath
835809
tabBarItemView.accessibilityTraits =
836810
(tabBarItemView.accessibilityTraits | UIAccessibilityTraitSelected);
837811
}
838-
#if defined(__IPHONE_13_0) && (__IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_13_0)
839812
} else if ([keyPath isEqualToString:NSStringFromSelector(@selector(largeContentSizeImage))]) {
840-
if (@available(iOS 13.0, *)) {
841-
tabBarItemView.largeContentImage = newValue;
842-
}
813+
tabBarItemView.largeContentImage = newValue;
843814
} else if ([keyPath
844815
isEqualToString:NSStringFromSelector(@selector(largeContentSizeImageInsets))]) {
845-
if (@available(iOS 13.0, *)) {
846-
tabBarItemView.largeContentImageInsets = [newValue UIEdgeInsetsValue];
847-
}
816+
tabBarItemView.largeContentImageInsets = [newValue UIEdgeInsetsValue];
848817
}
849-
#endif // defined(__IPHONE_13_0) && (__IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_13_0)
850818
} else {
851819
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
852820
}
@@ -1271,18 +1239,14 @@ - (void)scrollToItem:(UITabBarItem *)item animated:(BOOL)animated {
12711239
}
12721240

12731241
- (void)invalidateInteractionsForItemViews {
1274-
#ifdef __IPHONE_13_4
1275-
if (@available(iOS 13.4, *)) {
1276-
for (MDCTabBarView *view in self.itemViews) {
1277-
for (id<UIInteraction> interaction in view.interactions) {
1278-
if ([interaction isKindOfClass:[UIPointerInteraction class]]) {
1279-
UIPointerInteraction *pointerInteraction = (UIPointerInteraction *)interaction;
1280-
[pointerInteraction invalidate];
1281-
}
1242+
for (MDCTabBarView *view in self.itemViews) {
1243+
for (id<UIInteraction> interaction in view.interactions) {
1244+
if ([interaction isKindOfClass:[UIPointerInteraction class]]) {
1245+
UIPointerInteraction *pointerInteraction = (UIPointerInteraction *)interaction;
1246+
[pointerInteraction invalidate];
12821247
}
12831248
}
12841249
}
1285-
#endif
12861250
}
12871251

12881252
- (CGRect)estimatedFrameForItemAtIndex:(NSUInteger)index {
@@ -1520,9 +1484,8 @@ - (UIView *)selectedItemView {
15201484

15211485
#pragma mark - UIPointerInteractionDelegate
15221486

1523-
#ifdef __IPHONE_13_4
15241487
- (nullable UIPointerStyle *)pointerInteraction:(UIPointerInteraction *)interaction
1525-
styleForRegion:(UIPointerRegion *)region API_AVAILABLE(ios(13.4)) {
1488+
styleForRegion:(UIPointerRegion *)region {
15261489
UIPointerStyle *pointerStyle = nil;
15271490
if (interaction.view) {
15281491
UITargetedPreview *targetedPreview = [[UITargetedPreview alloc] initWithView:interaction.view];
@@ -1532,7 +1495,6 @@ - (nullable UIPointerStyle *)pointerInteraction:(UIPointerInteraction *)interact
15321495
}
15331496
return pointerStyle;
15341497
}
1535-
#endif
15361498

15371499
#pragma mark - UILargeContentViewerInteractionDelegate
15381500

@@ -1548,11 +1510,9 @@ - (UIView *)itemViewForPoint:(CGPoint)point {
15481510
return nil;
15491511
}
15501512

1551-
#if MDC_AVAILABLE_SDK_IOS(13_0)
15521513
- (nullable id<UILargeContentViewerItem>)largeContentViewerInteraction:
15531514
(UILargeContentViewerInteraction *)interaction
1554-
itemAtPoint:(CGPoint)point
1555-
NS_AVAILABLE_IOS(13_0) {
1515+
itemAtPoint:(CGPoint)point {
15561516
if (!CGRectContainsPoint(self.bounds, point)) {
15571517
// The touch has wandered outside of the view. Do not display the content viewer.
15581518
if ([self.lastLargeContentViewerItem isKindOfClass:[MDCTabBarViewItemView class]]) {
@@ -1590,7 +1550,7 @@ - (UIView *)itemViewForPoint:(CGPoint)point {
15901550

15911551
- (void)largeContentViewerInteraction:(UILargeContentViewerInteraction *)interaction
15921552
didEndOnItem:(nullable id<UILargeContentViewerItem>)item
1593-
atPoint:(CGPoint)point NS_AVAILABLE_IOS(13_0) {
1553+
atPoint:(CGPoint)point {
15941554
if (item) {
15951555
for (NSUInteger i = 0; i < self.items.count; i++) {
15961556
UIView *itemView = self.itemViews[i];
@@ -1607,7 +1567,6 @@ - (void)largeContentViewerInteraction:(UILargeContentViewerInteraction *)interac
16071567

16081568
self.lastLargeContentViewerItem = nil;
16091569
}
1610-
#endif // MDC_AVAILABLE_SDK_IOS(13_0)
16111570

16121571
@end
16131572

components/Tabs/src/TabBarView/private/MDCTabBarViewItemView.h

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,19 @@ NS_ASSUME_NONNULL_BEGIN
121121
*/
122122
@property(nonatomic) CGFloat imageTitlePadding;
123123

124+
/**
125+
* Whether or not to enforce padding between the tab bar item's image and title.
126+
*
127+
* If set to @c YES, the tab bar item's image and title will be top-aligned with the
128+
* @c itemImageTitlePadding in between.
129+
*
130+
* If set to @c NO, the tab bar item's image will be aligned to the top while the title will be
131+
* aligned to the bottom inside the edge insets.
132+
*
133+
* Defaults to @c NO.
134+
*/
135+
@property(nonatomic) BOOL enforceTextAndImagePadding;
136+
124137
@end
125138

126139
NS_ASSUME_NONNULL_END

components/Tabs/src/TabBarView/private/MDCTabBarViewItemView.m

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,7 @@ - (void)commonMDCTabBarViewItemViewInit {
115115
_iconSize = CGSizeZero;
116116
_minHeightTitleAndImage = kMinHeightTitleAndImage;
117117
_imageTitlePadding = kImageTitlePadding;
118+
_enforceTextAndImagePadding = NO;
118119
}
119120

120121
- (CGPoint)badgeCenterFromFrame:(CGRect)frame isRTL:(BOOL)isRTL {
@@ -245,14 +246,23 @@ - (void)layoutTitleLabelFrame:(CGRect *)titleLabelFrame
245246
return;
246247
}
247248

248-
// Now position the label from the bottom.
249+
// Now position the label in the remaining space.
249250
CGSize availableLabelSize =
250251
CGSizeMake(contentSize.width,
251252
contentSize.height - (CGRectGetHeight(imageViewFrame) + self.imageTitlePadding));
252253
CGSize finalLabelSize = [self.titleLabel sizeThatFits:availableLabelSize];
253-
CGRect titleFrame = CGRectMake(CGRectGetMidX(contentFrame) - (finalLabelSize.width / 2),
254-
CGRectGetMaxY(contentFrame) - finalLabelSize.height,
255-
finalLabelSize.width, finalLabelSize.height);
254+
CGFloat titleFrameX = CGRectGetMidX(contentFrame) - (finalLabelSize.width / 2);
255+
CGRect titleFrame;
256+
if (self.enforceTextAndImagePadding) {
257+
// Position the label below the image and padding.
258+
titleFrame = CGRectMake(
259+
titleFrameX, CGRectGetMinY(contentFrame) + imageFinalSize.height + self.imageTitlePadding,
260+
finalLabelSize.width, finalLabelSize.height);
261+
} else {
262+
// Position the label from bottom.
263+
titleFrame = CGRectMake(titleFrameX, CGRectGetMaxY(contentFrame) - finalLabelSize.height,
264+
finalLabelSize.width, finalLabelSize.height);
265+
}
256266
titleFrame = MDCRectAlignToScale(titleFrame, self.window.screen.scale);
257267
*titleLabelFrame = titleFrame;
258268
}

0 commit comments

Comments
 (0)