blob: c294b918c425599a164fe39e9383891ab180f061 [file] [log] [blame] [edit]
// Copyright 2017-present the Material Components for iOS authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#import <CoreGraphics/CoreGraphics.h>
#import "MDCBottomNavigationItemView.h"
#import "MaterialInk.h"
#import "MaterialRipple.h"
#import <MDFInternationalization/MDFInternationalization.h>
#import "MDCBottomNavigationBar.h"
#import "MDCBottomNavigationItemBadge.h"
// A number large enough to be larger than any reasonable screen dimension but small enough that
// CGFloat doesn't lose precision.
static const CGFloat kMaxSizeDimension = 1000000;
static const CGFloat MDCBottomNavigationItemViewInkOpacity = (CGFloat)0.150;
static const CGFloat MDCBottomNavigationItemViewTitleFontSize = 12;
/** The default value for @c numberOfLines for the title label. */
static const NSInteger kDefaultTitleNumberOfLines = 1;
// The fonts available on iOS differ from that used on Material.io. When trying to approximate
// the position on iOS, it seems like a horizontal inset of 10 points looks pretty close.
static const CGFloat kBadgeXOffsetFromIconEdgeWithTextLTR = -8;
// However, when the badge has no visible text, its horizontal center should be 1 point inset from
// the edge of the image.
static const CGFloat kBadgeXOffsetFromIconEdgeEmptyLTR = -1;
// The duration of the (de)selection transition animation.
static const NSTimeInterval kMDCBottomNavigationItemViewSelectionAnimationDuration = 0.100f;
// The duration of the title label's fade-out animation on deselection. The fade-in animation of the
// label on selection will be delayed by this value, and the duration of that animation is
// @c kMDCBottomNavigationItemViewSelectionAnimationDuration minus this value.
static const NSTimeInterval kMDCBottomNavigationItemViewLabelFadeOutAnimationDuration = 0.0333f;
// The amount to inset pointerEffectHoverRect.
// These values were chosen to achieve visual parity with UITabBar's highlight effect.
const CGSize MDCButtonNavigationItemViewPointerEffectHighlightRectInset = {-24, -12};
@interface MDCBottomNavigationItemView ()
@property(nonatomic, strong) MDCBottomNavigationItemBadge *badge;
@property(nonatomic, strong) UIImageView *iconImageView;
@property(nonatomic, strong) UILabel *label;
- (CGPoint)badgeCenterFromIconFrame:(CGRect)iconFrame isRTL:(BOOL)isRTL;
@end
@implementation MDCBottomNavigationItemView
- (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
_titleBelowIcon = YES;
[self commonMDCBottomNavigationItemViewInit];
}
return self;
}
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
self = [super initWithCoder:aDecoder];
if (self) {
_titleBelowIcon = YES;
NSUInteger totalViewsProcessed = 0;
for (UIView *view in self.subviews) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
if ([view isKindOfClass:[MDCInkView class]]) {
_inkView = (MDCInkView *)view;
#pragma clang diagnostic pop
++totalViewsProcessed;
} else if ([view isKindOfClass:[UIImageView class]]) {
_iconImageView = (UIImageView *)view;
_image = _iconImageView.image;
++totalViewsProcessed;
} else if ([view isKindOfClass:[UILabel class]]) {
_label = (UILabel *)view;
++totalViewsProcessed;
} else if ([view isKindOfClass:[MDCBottomNavigationItemBadge class]]) {
_badge = (MDCBottomNavigationItemBadge *)view;
++totalViewsProcessed;
} else if ([view isKindOfClass:[UIButton class]]) {
_button = (UIButton *)view;
++totalViewsProcessed;
}
}
NSAssert(totalViewsProcessed == self.subviews.count,
@"Unexpected number of subviews. Expected %lu but restored %lu. Unarchiving may fail.",
(unsigned long)self.subviews.count, (unsigned long)totalViewsProcessed);
[self commonMDCBottomNavigationItemViewInit];
}
return self;
}
- (void)commonMDCBottomNavigationItemViewInit {
_truncatesTitle = YES;
_titleNumberOfLines = kDefaultTitleNumberOfLines;
if (!_selectedItemTintColor) {
_selectedItemTintColor = [UIColor blackColor];
}
if (!_unselectedItemTintColor) {
_unselectedItemTintColor = [UIColor grayColor];
}
_selectedItemTitleColor = _selectedItemTintColor;
if (!_iconImageView) {
_iconImageView = [[UIImageView alloc] initWithFrame:CGRectZero];
_iconImageView.isAccessibilityElement = NO;
[self addSubview:_iconImageView];
}
if (!_label) {
_label = [[UILabel alloc] initWithFrame:CGRectZero];
_label.text = _title;
_label.font = [UIFont systemFontOfSize:MDCBottomNavigationItemViewTitleFontSize];
_label.textAlignment = NSTextAlignmentCenter;
_label.textColor = _selectedItemTitleColor;
_label.isAccessibilityElement = NO;
[self addSubview:_label];
}
_label.numberOfLines = kDefaultTitleNumberOfLines;
if (!_badge) {
_badge = [[MDCBottomNavigationItemBadge alloc] initWithFrame:CGRectZero];
_badge.isAccessibilityElement = NO;
[self addSubview:_badge];
}
if (!_badge.badgeValue) {
_badge.hidden = YES;
}
if (!_inkView) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
_inkView = [[MDCInkView alloc] initWithFrame:self.bounds];
#pragma clang diagnostic pop
_inkView.autoresizingMask =
(UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight);
_inkView.usesLegacyInkRipple = NO;
_inkView.clipsToBounds = NO;
[self addSubview:_inkView];
}
if (!_rippleTouchController) {
_rippleTouchController = [[MDCRippleTouchController alloc] initWithView:self];
_rippleTouchController.rippleView.rippleStyle = MDCRippleStyleUnbounded;
}
if (!_button) {
_button = [[UIButton alloc] initWithFrame:self.bounds];
_button.autoresizingMask = (UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight);
_button.accessibilityLabel = [self accessibilityLabelWithTitle:_title];
_button.accessibilityValue = self.accessibilityValue;
// This needs to be set specifically for VoiceOver to work on iOS 14, see b/175421576
if (@available(iOS 14, *)) {
_button.accessibilityTraits |= UIAccessibilityTraitButton;
}
[self addSubview:_button];
}
}
- (CGSize)sizeThatFits:(__unused CGSize)size {
if (self.titleBelowIcon) {
return [self sizeThatFitsForVerticalLayout];
} else {
return [self sizeThatFitsForHorizontalLayout];
}
}
- (BOOL)isTitleHidden {
return self.titleVisibility == MDCBottomNavigationBarTitleVisibilityNever ||
(self.titleVisibility == MDCBottomNavigationBarTitleVisibilitySelected && !self.selected);
}
- (CGSize)sizeThatFitsForVerticalLayout {
CGSize maxSize = CGSizeMake(kMaxSizeDimension, kMaxSizeDimension);
CGSize iconSize = [self.iconImageView sizeThatFits:maxSize];
CGRect iconFrame = CGRectMake(0, 0, iconSize.width, iconSize.height);
CGSize badgeSize = [self.badge sizeThatFits:maxSize];
CGPoint badgeCenter = [self badgeCenterFromIconFrame:iconFrame isRTL:NO];
CGRect badgeFrame =
CGRectMake(floor(badgeCenter.x - badgeSize.width / 2),
floor(badgeCenter.y - badgeSize.height / 2), badgeSize.width, badgeSize.height);
CGRect labelFrame = CGRectZero;
if (![self isTitleHidden]) {
CGSize labelSize = [self.label sizeThatFits:maxSize];
labelFrame = CGRectMake(floor(CGRectGetMidX(iconFrame) - labelSize.width / 2),
CGRectGetMaxY(iconFrame) + self.contentVerticalMargin, labelSize.width,
labelSize.height);
}
return CGRectStandardize(CGRectUnion(labelFrame, CGRectUnion(iconFrame, badgeFrame))).size;
}
- (CGSize)sizeThatFitsForHorizontalLayout {
CGSize maxSize = CGSizeMake(kMaxSizeDimension, kMaxSizeDimension);
CGSize iconSize = [self.iconImageView sizeThatFits:maxSize];
CGRect iconFrame = CGRectMake(0, 0, iconSize.width, iconSize.height);
CGSize badgeSize = [self.badge sizeThatFits:maxSize];
CGPoint badgeCenter = [self badgeCenterFromIconFrame:iconFrame isRTL:NO];
CGRect badgeFrame =
CGRectMake(floor(badgeCenter.x - badgeSize.width / 2),
floor(badgeCenter.y - badgeSize.height / 2), badgeSize.width, badgeSize.height);
CGSize labelSize = [self.label sizeThatFits:maxSize];
CGRect labelFrame = CGRectMake(CGRectGetMaxX(iconFrame) + self.contentHorizontalMargin,
floor(CGRectGetMidY(iconFrame) - labelSize.height / 2),
labelSize.width, labelSize.height);
return CGRectStandardize(CGRectUnion(labelFrame, CGRectUnion(iconFrame, badgeFrame))).size;
}
- (void)layoutSubviews {
[super layoutSubviews];
[self.label sizeToFit];
[self.badge sizeToFit];
self.inkView.maxRippleRadius =
(CGFloat)(hypot(CGRectGetHeight(self.bounds), CGRectGetWidth(self.bounds)) / 2);
[self centerLayoutAnimated:NO];
[self invalidatePointerInteractions];
}
- (void)calculateVerticalLayoutInBounds:(CGRect)contentBounds
forLabelFrame:(CGRect *)outLabelFrame
iconImageViewFrame:(CGRect *)outIconFrame {
// Determine the intrinsic size of the label, icon, and combined content
CGRect contentBoundingRect = CGRectStandardize(contentBounds);
CGSize iconImageViewSize = [self.iconImageView sizeThatFits:contentBoundingRect.size];
CGSize labelSize = [self.label sizeThatFits:contentBoundingRect.size];
CGFloat iconHeight = iconImageViewSize.height;
CGFloat labelHeight = labelSize.height;
CGFloat totalContentHeight = iconHeight;
if (![self isTitleHidden]) {
totalContentHeight += labelHeight + self.contentVerticalMargin;
}
// Determine the position of the label and icon
CGFloat centerX = CGRectGetMidX(contentBoundingRect);
CGFloat iconImageViewCenterY =
MAX(floor(CGRectGetMidY(contentBoundingRect) - totalContentHeight / 2 +
iconHeight / 2), // Content centered
floor(CGRectGetMinY(contentBoundingRect) +
iconHeight / 2) // Pinned to top of bounding rect.
);
CGPoint iconImageViewCenter = CGPointMake(centerX, iconImageViewCenterY);
// Ignore the horizontal titlePositionAdjustment in a vertical layout to match UITabBar behavior.
CGFloat centerY;
if ([self isTitleHidden]) {
centerY = iconImageViewCenter.y + iconHeight / 2 + self.titlePositionAdjustment.vertical +
self.contentVerticalMargin / 2;
} else {
centerY = iconImageViewCenter.y + iconHeight / 2 + self.contentVerticalMargin +
labelHeight / 2 + self.titlePositionAdjustment.vertical;
}
CGPoint labelCenter = CGPointMake(centerX, centerY);
CGFloat availableContentWidth = CGRectGetWidth(contentBoundingRect);
if (self.truncatesTitle && (labelSize.width > availableContentWidth)) {
labelSize = CGSizeMake(availableContentWidth, labelSize.height);
}
// Assign the frames to the inout arguments
if (outLabelFrame != NULL) {
*outLabelFrame = CGRectMake(floor(labelCenter.x - (labelSize.width / 2)),
floor(labelCenter.y - (labelSize.height / 2)), labelSize.width,
labelSize.height);
}
if (outIconFrame != NULL) {
*outIconFrame = CGRectMake(floor(iconImageViewCenter.x - (iconImageViewSize.width / 2)),
floor(iconImageViewCenter.y - (iconImageViewSize.height / 2)),
iconImageViewSize.width, iconImageViewSize.height);
}
}
- (void)calculateHorizontalLayoutInBounds:(CGRect)contentBounds
forLabelFrame:(CGRect *)outLabelFrame
iconImageViewFrame:(CGRect *)outIconFrame {
// Determine the intrinsic size of the label and icon
CGRect contentBoundingRect = CGRectStandardize(contentBounds);
CGSize iconImageViewSize = [self.iconImageView sizeThatFits:contentBoundingRect.size];
CGSize maxLabelSize = CGSizeMake(
contentBoundingRect.size.width - self.contentHorizontalMargin - iconImageViewSize.width,
contentBoundingRect.size.height);
CGSize labelSize = [self.label sizeThatFits:maxLabelSize];
CGFloat contentsWidth = iconImageViewSize.width + self.contentHorizontalMargin + labelSize.width;
CGFloat remainingContentWidth = CGRectGetWidth(contentBoundingRect);
if (contentsWidth > remainingContentWidth) {
contentsWidth = remainingContentWidth;
}
// If the content width and available width are different, the internal spacing required to center
// the contents.
CGFloat contentPadding = (remainingContentWidth - contentsWidth) / 2;
remainingContentWidth -= iconImageViewSize.width + self.contentHorizontalMargin;
if (self.truncatesTitle) {
labelSize = CGSizeMake(MIN(labelSize.width, remainingContentWidth), labelSize.height);
}
// Account for RTL
BOOL isRTL =
self.mdf_effectiveUserInterfaceLayoutDirection == UIUserInterfaceLayoutDirectionRightToLeft;
NSInteger rtlCoefficient = isRTL ? -1 : 1;
CGFloat layoutStartingPoint =
isRTL ? CGRectGetMaxX(contentBoundingRect) : CGRectGetMinX(contentBoundingRect);
CGFloat centerY = CGRectGetMidY(contentBoundingRect);
// Amount icon center is offset from the leading edge.
CGFloat iconCenterOffset = contentPadding + iconImageViewSize.width / 2;
// Determine the position of the label and icon
CGPoint iconImageViewCenter =
CGPointMake(layoutStartingPoint + rtlCoefficient * iconCenterOffset, centerY);
CGFloat labelOffsetFromIcon =
iconImageViewSize.width / 2 + self.contentHorizontalMargin + labelSize.width / 2;
CGPoint labelCenter = CGPointMake(iconImageViewCenter.x + rtlCoefficient * labelOffsetFromIcon +
self.titlePositionAdjustment.horizontal,
centerY + self.titlePositionAdjustment.vertical);
// Assign the frames to the inout arguments
if (outLabelFrame != NULL) {
*outLabelFrame = CGRectMake(floor(labelCenter.x - (labelSize.width / 2)),
floor(labelCenter.y - (labelSize.height / 2)), labelSize.width,
labelSize.height);
}
if (outIconFrame != NULL) {
*outIconFrame = CGRectMake(floor(iconImageViewCenter.x - (iconImageViewSize.width / 2)),
floor(iconImageViewCenter.y - (iconImageViewSize.height / 2)),
iconImageViewSize.width, iconImageViewSize.height);
}
}
- (void)centerLayoutAnimated:(BOOL)animated {
CGRect labelFrame = CGRectZero;
CGRect iconImageViewFrame = CGRectZero;
if (self.titleBelowIcon) {
[self calculateVerticalLayoutInBounds:self.bounds
forLabelFrame:&labelFrame
iconImageViewFrame:&iconImageViewFrame];
} else {
[self calculateHorizontalLayoutInBounds:self.bounds
forLabelFrame:&labelFrame
iconImageViewFrame:&iconImageViewFrame];
}
CGPoint iconImageViewCenter =
CGPointMake(CGRectGetMidX(iconImageViewFrame), CGRectGetMidY(iconImageViewFrame));
self.label.center = CGPointMake(CGRectGetMidX(labelFrame), CGRectGetMidY(labelFrame));
self.label.bounds = CGRectMake(0, 0, CGRectGetWidth(labelFrame), CGRectGetHeight(labelFrame));
UIUserInterfaceLayoutDirection layoutDirection = self.mdf_effectiveUserInterfaceLayoutDirection;
BOOL isRTL = layoutDirection == UIUserInterfaceLayoutDirectionRightToLeft;
if (self.titleBelowIcon) {
if (animated) {
[UIView animateWithDuration:kMDCBottomNavigationItemViewSelectionAnimationDuration
animations:^(void) {
self.iconImageView.center = iconImageViewCenter;
self.badge.center =
[self badgeCenterFromIconFrame:CGRectStandardize(iconImageViewFrame)
isRTL:isRTL];
}];
} else {
self.iconImageView.center = iconImageViewCenter;
self.badge.center = [self badgeCenterFromIconFrame:CGRectStandardize(iconImageViewFrame)
isRTL:isRTL];
}
self.label.textAlignment = NSTextAlignmentCenter;
} else {
if (!isRTL) {
self.label.textAlignment = NSTextAlignmentLeft;
} else {
self.label.textAlignment = NSTextAlignmentRight;
}
self.iconImageView.center = iconImageViewCenter;
self.badge.center = [self badgeCenterFromIconFrame:CGRectStandardize(iconImageViewFrame)
isRTL:isRTL];
}
}
- (void)updateLabelVisibility:(BOOL)animated {
BOOL shouldHide;
if (self.selected) {
switch (self.titleVisibility) {
case MDCBottomNavigationBarTitleVisibilitySelected:
case MDCBottomNavigationBarTitleVisibilityAlways:
shouldHide = NO;
break;
case MDCBottomNavigationBarTitleVisibilityNever:
shouldHide = YES;
break;
}
} else {
switch (self.titleVisibility) {
case MDCBottomNavigationBarTitleVisibilitySelected:
case MDCBottomNavigationBarTitleVisibilityNever:
shouldHide = YES;
break;
case MDCBottomNavigationBarTitleVisibilityAlways:
shouldHide = NO;
break;
}
}
if (!animated) {
[self setNeedsLayout];
self.label.alpha = shouldHide ? 0.0f : 1.0f;
} else {
[UIView animateWithDuration:kMDCBottomNavigationItemViewSelectionAnimationDuration
animations:^{
[self setNeedsLayout];
self.label.alpha = shouldHide ? 0.0f : 1.0f;
}];
if (shouldHide) {
[UIView animateWithDuration:kMDCBottomNavigationItemViewLabelFadeOutAnimationDuration
animations:^{
self.label.alpha = 0.0f;
}];
} else {
[UIView animateWithDuration:(kMDCBottomNavigationItemViewSelectionAnimationDuration -
kMDCBottomNavigationItemViewLabelFadeOutAnimationDuration)
delay:kMDCBottomNavigationItemViewLabelFadeOutAnimationDuration
options:UIViewAnimationOptionCurveLinear
animations:^{
self.label.alpha = 1.0f;
}
completion:nil];
}
}
}
- (NSString *)accessibilityLabelWithTitle:(NSString *)title {
NSMutableArray *labelComponents = [NSMutableArray array];
// Use untransformed title as accessibility label to ensure accurate reading.
if (title.length > 0) {
[labelComponents addObject:title];
}
// Speak components with a pause in between.
return [labelComponents componentsJoinedByString:@", "];
}
- (CGPoint)badgeCenterFromIconFrame:(CGRect)iconFrame isRTL:(BOOL)isRTL {
CGSize badgeSize = [self.badge sizeThatFits:CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX)];
// There are no specifications for badge layout, so this is based on the Material Guidelines
// article for Bottom Navigation which includes an image showing badge positions.
// https://storage.googleapis.com/spec-host-backup/mio-design%2Fassets%2F0B6xUSjjSulxcaVpEMk5tZ2RGZ3c%2Fbottomnav-badging-1.png
// Attempting to match the "88" badge on the "chrome reader mode" icon results in the badge's top
// edge equalling that of the image bounds.
// https://material.io/tools/icons/?icon=chrome_reader_mode&style=baseline
CGFloat badgeCenterY = CGRectGetMinY(iconFrame) + (badgeSize.height / 2);
CGFloat badgeCenterXOffset = kBadgeXOffsetFromIconEdgeWithTextLTR + badgeSize.width / 2;
if (self.badgeValue.length == 0) {
badgeCenterXOffset = kBadgeXOffsetFromIconEdgeEmptyLTR;
}
CGFloat badgeCenterX = isRTL ? CGRectGetMinX(iconFrame) - badgeCenterXOffset
: CGRectGetMaxX(iconFrame) + badgeCenterXOffset;
return CGPointMake(badgeCenterX, badgeCenterY);
}
- (NSString *)badgeValue {
return self.badge.badgeValue;
}
- (CGRect)pointerEffectHighlightRect {
NSMutableArray<UIView *> *visibleViews = [[NSMutableArray alloc] init];
if (!self.iconImageView.hidden) {
[visibleViews addObject:self.iconImageView];
}
if (!self.label.hidden) {
[visibleViews addObject:self.label];
}
if (!self.badge.hidden) {
[visibleViews addObject:self.badge];
}
// If we don't have any visible views, there is no content to frame
if (visibleViews.count == 0) {
return self.frame;
}
CGRect contentRect = visibleViews.firstObject.frame;
for (UIView *visibleView in visibleViews) {
contentRect = CGRectUnion(contentRect, visibleView.frame);
}
CGRect insetContentRect =
CGRectInset(contentRect, MDCButtonNavigationItemViewPointerEffectHighlightRectInset.width,
MDCButtonNavigationItemViewPointerEffectHighlightRectInset.height);
// Ensure insetContentRect is the same size or smaller than self.bounds
CGSize boundsSize = CGRectStandardize(self.bounds).size;
if (insetContentRect.size.width > boundsSize.width) {
insetContentRect.origin.x = 0;
insetContentRect.size.width = boundsSize.width;
}
if (insetContentRect.size.height > boundsSize.height) {
insetContentRect.origin.y = 0;
insetContentRect.size.height = boundsSize.height;
}
return insetContentRect;
}
- (void)invalidatePointerInteractions {
#ifdef __IPHONE_13_4
if (@available(iOS 13.4, *)) {
for (UIPointerInteraction *interaction in self.interactions) {
[interaction invalidate];
}
}
#endif
}
#pragma mark - Setters
- (void)setSelected:(BOOL)selected {
[self setSelected:selected animated:NO];
}
- (void)setSelected:(BOOL)selected animated:(BOOL)animated {
_selected = selected;
if (selected) {
self.label.textColor = self.selectedItemTitleColor;
self.iconImageView.tintColor = self.selectedItemTintColor;
self.button.accessibilityTraits |= UIAccessibilityTraitSelected;
self.iconImageView.image = (self.selectedImage) ? self.selectedImage : self.image;
[self updateLabelVisibility:animated];
} else {
self.label.textColor = self.unselectedItemTintColor;
self.iconImageView.tintColor = self.unselectedItemTintColor;
self.button.accessibilityTraits &= ~UIAccessibilityTraitSelected;
self.iconImageView.image = self.image;
[self updateLabelVisibility:animated];
}
[self centerLayoutAnimated:animated];
}
- (void)setSelectedItemTintColor:(UIColor *)selectedItemTintColor {
_selectedItemTintColor = selectedItemTintColor;
_selectedItemTitleColor = selectedItemTintColor;
if (self.selected) {
self.iconImageView.tintColor = self.selectedItemTintColor;
self.label.textColor = self.selectedItemTitleColor;
}
UIColor *rippleColor =
[self.selectedItemTintColor colorWithAlphaComponent:MDCBottomNavigationItemViewInkOpacity];
self.inkView.inkColor = rippleColor;
self.rippleTouchController.rippleView.rippleColor = rippleColor;
}
- (void)setUnselectedItemTintColor:(UIColor *)unselectedItemTintColor {
_unselectedItemTintColor = unselectedItemTintColor;
if (!self.selected) {
self.iconImageView.tintColor = self.unselectedItemTintColor;
self.label.textColor = self.unselectedItemTintColor;
}
}
- (void)setSelectedItemTitleColor:(UIColor *)selectedItemTitleColor {
_selectedItemTitleColor = selectedItemTitleColor;
if (self.selected) {
self.label.textColor = self.selectedItemTitleColor;
}
}
- (void)setBadgeColor:(UIColor *)badgeColor {
_badgeColor = badgeColor;
self.badge.badgeColor = badgeColor;
}
- (void)setBadgeTextColor:(UIColor *)badgeTextColor {
_badgeTextColor = badgeTextColor;
self.badge.badgeValueLabel.textColor = badgeTextColor;
}
- (void)setBadgeValue:(NSString *)badgeValue {
// Due to KVO, badgeValue may be of type NSNull.
if ([badgeValue isKindOfClass:[NSNull class]]) {
badgeValue = nil;
}
self.badge.badgeValue = badgeValue;
if ([super accessibilityValue] == nil || [self accessibilityValue].length == 0) {
self.button.accessibilityValue = badgeValue;
}
if (badgeValue == nil) {
self.badge.hidden = YES;
} else {
self.badge.hidden = NO;
}
[self setNeedsLayout];
}
- (void)setImage:(UIImage *)image {
_image = [image imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];
// _image updates unselected state
// _image updates selected state IF there is no selectedImage
if (!self.selected || (self.selected && !self.selectedImage)) {
self.iconImageView.image = _image;
self.iconImageView.tintColor =
(self.selected) ? self.selectedItemTintColor : self.unselectedItemTintColor;
[self.iconImageView sizeToFit];
[self setNeedsLayout];
}
}
- (void)setSelectedImage:(UIImage *)selectedImage {
_selectedImage = [selectedImage imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];
if (self.selected) {
self.iconImageView.image = _selectedImage;
self.iconImageView.tintColor = self.selectedItemTintColor;
[self.iconImageView sizeToFit];
[self setNeedsLayout];
}
}
- (void)setTitle:(NSString *)title {
_title = [title copy];
self.label.text = _title;
self.button.accessibilityLabel = [self accessibilityLabelWithTitle:_title];
[self setNeedsLayout];
}
- (void)setTitleVisibility:(MDCBottomNavigationBarTitleVisibility)titleVisibility {
_titleVisibility = titleVisibility;
[self updateLabelVisibility:NO];
}
- (void)setItemTitleFont:(UIFont *)itemTitleFont {
_itemTitleFont = itemTitleFont;
self.label.font = itemTitleFont;
[self setNeedsLayout];
}
- (void)setAccessibilityValue:(NSString *)accessibilityValue {
[super setAccessibilityValue:accessibilityValue];
self.button.accessibilityValue = accessibilityValue;
}
- (NSString *)accessibilityValue {
return self.button.accessibilityValue;
}
- (void)setAccessibilityHint:(NSString *)accessibilityHint {
[super setAccessibilityHint:accessibilityHint];
self.button.accessibilityHint = accessibilityHint;
}
- (NSString *)accessibilityHint {
return self.button.accessibilityHint;
}
- (void)setAccessibilityElementIdentifier:(NSString *)accessibilityElementIdentifier {
self.button.accessibilityIdentifier = accessibilityElementIdentifier;
}
- (NSString *)accessibilityElementIdentifier {
return self.button.accessibilityIdentifier;
}
- (void)setTitlePositionAdjustment:(UIOffset)titlePositionAdjustment {
if (!UIOffsetEqualToOffset(_titlePositionAdjustment, titlePositionAdjustment)) {
_titlePositionAdjustment = titlePositionAdjustment;
[self setNeedsLayout];
}
}
- (NSInteger)renderedTitleNumberOfLines {
return self.titleBelowIcon ? _titleNumberOfLines : kDefaultTitleNumberOfLines;
}
- (void)setTitleNumberOfLines:(NSInteger)titleNumberOfLines {
_titleNumberOfLines = titleNumberOfLines;
self.label.numberOfLines = [self renderedTitleNumberOfLines];
}
- (void)setTitleBelowIcon:(BOOL)titleBelowIcon {
_titleBelowIcon = titleBelowIcon;
self.label.numberOfLines = [self renderedTitleNumberOfLines];
}
#pragma mark - UILargeContentViewerItem
- (BOOL)showsLargeContentViewer {
return YES;
}
- (NSString *)largeContentTitle {
if (_largeContentTitle) {
return _largeContentTitle;
}
return self.title;
}
- (UIImage *)largeContentImage {
if (_largeContentImage) {
return _largeContentImage;
}
return self.image;
}
- (BOOL)scalesLargeContentImage {
return _largeContentImage == nil;
}
@end