blob: 2837e0b57958bf6c5de86519d5cb33e9d85359d5 [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 <MDFInternationalization/MDFInternationalization.h>
#import "MDCBottomNavigationItemBadge.h"
#import "MaterialAvailability.h"
#import "MaterialBottomNavigationStrings.h"
#import "MaterialBottomNavigationStrings_table.h"
#import "MaterialMath.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 selection transition animation.
static const NSTimeInterval kMDCBottomNavigationItemViewTransitionDuration = 0.180;
// The Bundle for string resources.
static NSString *const kMaterialBottomNavigationBundle = @"MaterialBottomNavigation.bundle";
static NSString *const kMDCBottomNavigationItemViewTabString = @"tab";
@interface MDCBottomNavigationItemView ()
@property(nonatomic, strong) MDCBottomNavigationItemBadge *badge;
@property(nonatomic, strong) UIImageView *iconImageView;
@property(nonatomic, strong) UILabel *label;
@property(nonatomic) BOOL shouldPretendToBeATab;
- (CGPoint)badgeCenterFromIconFrame:(CGRect)iconFrame isRTL:(BOOL)isRTL;
@end
@implementation MDCBottomNavigationItemView
- (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
#if MDC_AVAILABLE_SDK_IOS(10_0)
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunguarded-availability"
#pragma clang diagnostic ignored "-Wtautological-pointer-compare"
if (&UIAccessibilityTraitTabBar == NULL) {
_shouldPretendToBeATab = YES;
}
#pragma clang diagnostic pop
#else
_shouldPretendToBeATab = YES;
#endif // MDC_AVAILABLE_SDK_IOS(10_0)
_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) {
if ([view isKindOfClass:[MDCInkView class]]) {
_inkView = (MDCInkView *)view;
++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) {
_inkView = [[MDCInkView alloc] initWithFrame:self.bounds];
_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.accessibilityTraits &= ~UIAccessibilityTraitButton;
_button.accessibilityValue = self.accessibilityValue;
[self addSubview:_button];
}
}
- (CGSize)sizeThatFits:(__unused CGSize)size {
if (self.titleBelowIcon) {
return [self sizeThatFitsForVerticalLayout];
} else {
return [self sizeThatFitsForHorizontalLayout];
}
}
- (CGSize)sizeThatFitsForVerticalLayout {
BOOL titleHidden =
self.titleVisibility == MDCBottomNavigationBarTitleVisibilityNever ||
(self.titleVisibility == MDCBottomNavigationBarTitleVisibilitySelected && !self.selected);
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(badgeCenter.x - badgeSize.width / 2, badgeCenter.y - badgeSize.height / 2,
badgeSize.width, badgeSize.height);
CGRect labelFrame = CGRectZero;
if (!titleHidden) {
CGSize labelSize = [self.label sizeThatFits:maxSize];
labelFrame = CGRectMake(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(badgeCenter.x - badgeSize.width / 2, badgeCenter.y - badgeSize.height / 2,
badgeSize.width, badgeSize.height);
CGSize labelSize = [self.label sizeThatFits:maxSize];
CGRect labelFrame = CGRectMake(CGRectGetMaxX(iconFrame) + self.contentHorizontalMargin,
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)(MDCHypot(CGRectGetHeight(self.bounds), CGRectGetWidth(self.bounds)) / 2);
[self centerLayoutAnimated:NO];
}
- (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];
BOOL titleHidden =
self.titleVisibility == MDCBottomNavigationBarTitleVisibilityNever ||
(self.titleVisibility == MDCBottomNavigationBarTitleVisibilitySelected && !self.selected);
CGFloat iconHeight = iconImageViewSize.height;
CGFloat labelHeight = labelSize.height;
CGFloat totalContentHeight = iconHeight;
if (!titleHidden) {
totalContentHeight += labelHeight + self.contentVerticalMargin;
}
// Determine the position of the label and icon
CGFloat centerX = CGRectGetMidX(contentBoundingRect);
CGFloat iconImageViewCenterY =
MAX(CGRectGetMidY(contentBoundingRect) - totalContentHeight / 2 +
iconHeight / 2, // Content centered
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.
CGPoint labelCenter =
CGPointMake(centerX, iconImageViewCenter.y + iconHeight / 2 + self.contentVerticalMargin +
labelHeight / 2 + self.titlePositionAdjustment.vertical);
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(labelCenter.x - (labelSize.width / 2), labelCenter.y - (labelSize.height / 2),
labelSize.width, labelSize.height);
}
if (outIconFrame != NULL) {
*outIconFrame = CGRectMake(iconImageViewCenter.x - (iconImageViewSize.width / 2),
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(labelCenter.x - (labelSize.width / 2), labelCenter.y - (labelSize.height / 2),
labelSize.width, labelSize.height);
}
if (outIconFrame != NULL) {
*outIconFrame = CGRectMake(iconImageViewCenter.x - (iconImageViewSize.width / 2),
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:kMDCBottomNavigationItemViewTransitionDuration
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 {
if (self.selected) {
switch (self.titleVisibility) {
case MDCBottomNavigationBarTitleVisibilitySelected:
case MDCBottomNavigationBarTitleVisibilityAlways:
self.label.hidden = NO;
break;
case MDCBottomNavigationBarTitleVisibilityNever:
self.label.hidden = YES;
break;
}
} else {
switch (self.titleVisibility) {
case MDCBottomNavigationBarTitleVisibilitySelected:
case MDCBottomNavigationBarTitleVisibilityNever:
self.label.hidden = YES;
break;
case MDCBottomNavigationBarTitleVisibilityAlways:
self.label.hidden = NO;
break;
}
}
[self setNeedsLayout];
}
- (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];
}
if (self.shouldPretendToBeATab) {
NSString *key = kMaterialBottomNavigationStringTable
[kStr_MaterialBottomNavigationTabElementAccessibilityLabel];
NSString *tabString = NSLocalizedStringFromTableInBundle(
key, kMaterialBottomNavigationStringsTableName, [[self class] bundle],
kMDCBottomNavigationItemViewTabString);
[labelComponents addObject:tabString];
}
// 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;
}
#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];
} else {
self.label.textColor = self.unselectedItemTintColor;
self.iconImageView.tintColor = self.unselectedItemTintColor;
self.button.accessibilityTraits &= ~UIAccessibilityTraitSelected;
self.iconImageView.image = self.image;
[self updateLabelVisibility];
}
[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];
}
- (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 - Resource bundle
+ (NSBundle *)bundle {
static NSBundle *bundle = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
bundle = [NSBundle bundleWithPath:[self bundlePathWithName:kMaterialBottomNavigationBundle]];
});
return bundle;
}
+ (NSString *)bundlePathWithName:(NSString *)bundleName {
// In iOS 8+, we could be included by way of a dynamic framework, and our resource bundles may
// not be in the main .app bundle, but rather in a nested framework, so figure out where we live
// and use that as the search location.
NSBundle *bundle = [NSBundle bundleForClass:[MDCBottomNavigationBar class]];
NSString *resourcePath = [(nil == bundle ? [NSBundle mainBundle] : bundle) resourcePath];
return [resourcePath stringByAppendingPathComponent:bundleName];
}
#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