mirror of
https://github.com/material-components/material-components-ios.git
synced 2026-02-20 08:27:32 +08:00
574 lines
21 KiB
Objective-C
574 lines
21 KiB
Objective-C
// Copyright 2016-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 "MDCFloatingButton.h"
|
|
|
|
#import "private/MDCFloatingButtonModeAnimator.h"
|
|
#import "private/MDCFloatingButtonModeAnimatorDelegate.h"
|
|
#import "MDCButton.h"
|
|
#import "MaterialShadowElevations.h"
|
|
|
|
#import <MDFInternationalization/MDFInternationalization.h>
|
|
|
|
static const CGFloat MDCFloatingButtonDefaultDimension = 56;
|
|
static const CGFloat MDCFloatingButtonMiniDimension = 40;
|
|
static const CGFloat MDCFloatingButtonDefaultImageTitleSpace = 8;
|
|
static const UIEdgeInsets internalLayoutInsets = (UIEdgeInsets){0, 16, 0, 24};
|
|
|
|
@interface MDCFloatingButton () <MDCFloatingButtonModeAnimatorDelegate>
|
|
|
|
@property(nonatomic, readonly)
|
|
NSMutableDictionary<NSNumber *, NSMutableDictionary<NSNumber *, NSValue *> *>
|
|
*shapeToModeToMinimumSize;
|
|
|
|
@property(nonatomic, readonly)
|
|
NSMutableDictionary<NSNumber *, NSMutableDictionary<NSNumber *, NSValue *> *>
|
|
*shapeToModeToMaximumSize;
|
|
|
|
@property(nonatomic, readonly)
|
|
NSMutableDictionary<NSNumber *, NSMutableDictionary<NSNumber *, NSValue *> *>
|
|
*shapeToModeToContentEdgeInsets;
|
|
|
|
@property(nonatomic, readonly)
|
|
NSMutableDictionary<NSNumber *, NSMutableDictionary<NSNumber *, NSValue *> *>
|
|
*shapeToModeToHitAreaInsets;
|
|
|
|
@property(nonatomic, readonly)
|
|
NSMutableDictionary<NSNumber *, NSMutableDictionary<NSNumber *, NSNumber *> *>
|
|
*shapeToModeToCenterVisibleArea;
|
|
|
|
@end
|
|
|
|
@implementation MDCFloatingButton {
|
|
MDCFloatingButtonShape _shape;
|
|
|
|
MDCFloatingButtonModeAnimator *_modeAnimator;
|
|
// Allows us to perform masking effects during mode animations.
|
|
UIView *_titleLabelContainerView;
|
|
}
|
|
|
|
+ (void)initialize {
|
|
[[MDCFloatingButton appearance] setElevation:MDCShadowElevationFABResting
|
|
forState:UIControlStateNormal];
|
|
[[MDCFloatingButton appearance] setElevation:MDCShadowElevationFABPressed
|
|
forState:UIControlStateHighlighted];
|
|
}
|
|
|
|
+ (CGFloat)defaultDimension {
|
|
return MDCFloatingButtonDefaultDimension;
|
|
}
|
|
|
|
+ (CGFloat)miniDimension {
|
|
return MDCFloatingButtonMiniDimension;
|
|
}
|
|
|
|
+ (instancetype)floatingButtonWithShape:(MDCFloatingButtonShape)shape {
|
|
return [[[self class] alloc] initWithFrame:CGRectZero shape:shape];
|
|
}
|
|
|
|
- (instancetype)init {
|
|
return [self initWithFrame:CGRectZero shape:MDCFloatingButtonShapeDefault];
|
|
}
|
|
|
|
- (instancetype)initWithFrame:(CGRect)frame {
|
|
return [self initWithFrame:frame shape:MDCFloatingButtonShapeDefault];
|
|
}
|
|
|
|
- (instancetype)initWithFrame:(CGRect)frame shape:(MDCFloatingButtonShape)shape {
|
|
self = [super initWithFrame:frame];
|
|
if (self) {
|
|
_shape = shape;
|
|
[self commonMDCFloatingButtonInit];
|
|
// The superclass sets contentEdgeInsets from defaultContentEdgeInsets before the _shape is set.
|
|
// Set contentEdgeInsets again to ensure the defaults are for the correct shape.
|
|
[self updateShapeAndAllowResize:NO];
|
|
}
|
|
return self;
|
|
}
|
|
|
|
#pragma clang diagnostic push
|
|
#pragma clang diagnostic ignored "-Wobjc-designated-initializers"
|
|
// https://stackoverflow.com/questions/24458608/convenience-initializer-missing-a-self-call-to-another-initializer
|
|
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
|
|
self = [super initWithCoder:aDecoder];
|
|
#pragma clang diagnostic pop
|
|
if (self) {
|
|
// Required to migrate any previously-archived FloatingButtons from .largeIcon shape value
|
|
if (@(_shape).integerValue >= 2) {
|
|
_shape = MDCFloatingButtonShapeDefault;
|
|
}
|
|
// Shape must be set first before the common initialization
|
|
[self commonMDCFloatingButtonInit];
|
|
|
|
[self updateShapeAndAllowResize:NO];
|
|
}
|
|
return self;
|
|
}
|
|
|
|
- (void)commonMDCFloatingButtonInit {
|
|
_imageTitleSpace = MDCFloatingButtonDefaultImageTitleSpace;
|
|
|
|
// Create a container view for titleLabel and add the titelLabel to it. This will enable us to
|
|
// mask the titleLabel mode animations if desired, while acting effectively as a pass-through for
|
|
// the superview layout logic.
|
|
_titleLabelContainerView = [[UIView alloc] initWithFrame:self.bounds];
|
|
_titleLabelContainerView.autoresizingMask =
|
|
UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
|
|
[self insertSubview:_titleLabelContainerView belowSubview:self.titleLabel];
|
|
_titleLabelContainerView.userInteractionEnabled = NO;
|
|
[_titleLabelContainerView addSubview:self.titleLabel];
|
|
|
|
const CGSize miniNormalSize =
|
|
CGSizeMake(MDCFloatingButtonMiniDimension, MDCFloatingButtonMiniDimension);
|
|
const CGSize defaultNormalSize =
|
|
CGSizeMake(MDCFloatingButtonDefaultDimension, MDCFloatingButtonDefaultDimension);
|
|
const CGSize defaultExpandedMinimumSize = CGSizeMake(0, 48);
|
|
const CGSize defaultExpandedMaximumSize = CGSizeMake(328, 0);
|
|
|
|
// Minimum size values for different shape + mode combinations
|
|
NSMutableDictionary *miniShapeMinimumSizeDictionary =
|
|
[@{@(MDCFloatingButtonModeNormal) : [NSValue valueWithCGSize:miniNormalSize]} mutableCopy];
|
|
NSMutableDictionary *defaultShapeMinimumSizeDictionary = [@{
|
|
@(MDCFloatingButtonModeNormal) : [NSValue valueWithCGSize:defaultNormalSize],
|
|
@(MDCFloatingButtonModeExpanded) : [NSValue valueWithCGSize:defaultExpandedMinimumSize],
|
|
} mutableCopy];
|
|
_shapeToModeToMinimumSize = [@{
|
|
@(MDCFloatingButtonShapeMini) : miniShapeMinimumSizeDictionary,
|
|
@(MDCFloatingButtonShapeDefault) : defaultShapeMinimumSizeDictionary,
|
|
} mutableCopy];
|
|
|
|
// Maximum size values for different shape + mode combinations
|
|
NSMutableDictionary *miniShapeMaximumSizeDictionary =
|
|
[@{@(MDCFloatingButtonModeNormal) : [NSValue valueWithCGSize:miniNormalSize]} mutableCopy];
|
|
NSMutableDictionary *defaultShapeMaximumSizeDictionary = [@{
|
|
@(MDCFloatingButtonModeNormal) : [NSValue valueWithCGSize:defaultNormalSize],
|
|
@(MDCFloatingButtonModeExpanded) : [NSValue valueWithCGSize:defaultExpandedMaximumSize],
|
|
} mutableCopy];
|
|
_shapeToModeToMaximumSize = [@{
|
|
@(MDCFloatingButtonShapeMini) : miniShapeMaximumSizeDictionary,
|
|
@(MDCFloatingButtonShapeDefault) : defaultShapeMaximumSizeDictionary,
|
|
} mutableCopy];
|
|
|
|
// Content edge insets values for different shape + mode combinations
|
|
// .mini shape, .normal mode
|
|
const UIEdgeInsets miniNormalContentInsets = UIEdgeInsetsMake(8, 8, 8, 8);
|
|
NSMutableDictionary *miniShapeContentEdgeInsetsDictionary =
|
|
[@{@(MDCFloatingButtonModeNormal) : [NSValue valueWithUIEdgeInsets:miniNormalContentInsets]}
|
|
mutableCopy];
|
|
_shapeToModeToContentEdgeInsets =
|
|
[@{@(MDCFloatingButtonShapeMini) : miniShapeContentEdgeInsetsDictionary} mutableCopy];
|
|
|
|
// Hit area insets values for different shape + mode combinations
|
|
// .mini shape, .normal mode
|
|
const UIEdgeInsets miniNormalHitAreaInset = UIEdgeInsetsMake(-4, -4, -4, -4);
|
|
NSMutableDictionary *miniShapeHitAreaInsetsDictionary = [@{
|
|
@(MDCFloatingButtonModeNormal) : [NSValue valueWithUIEdgeInsets:miniNormalHitAreaInset],
|
|
} mutableCopy];
|
|
_shapeToModeToHitAreaInsets = [@{
|
|
@(MDCFloatingButtonShapeMini) : miniShapeHitAreaInsetsDictionary,
|
|
} mutableCopy];
|
|
|
|
_shapeToModeToCenterVisibleArea = [[NSMutableDictionary alloc] init];
|
|
}
|
|
|
|
#pragma mark - UIView
|
|
|
|
- (CGSize)sizeThatFits:(__unused CGSize)size {
|
|
return [self intrinsicContentSize];
|
|
}
|
|
|
|
- (CGSize)intrinsicContentSizeForModeNormal {
|
|
switch (_shape) {
|
|
case MDCFloatingButtonShapeDefault:
|
|
return CGSizeMake(MDCFloatingButtonDefaultDimension, MDCFloatingButtonDefaultDimension);
|
|
case MDCFloatingButtonShapeMini:
|
|
return CGSizeMake(MDCFloatingButtonMiniDimension, MDCFloatingButtonMiniDimension);
|
|
}
|
|
}
|
|
|
|
- (CGSize)intrinsicContentSizeForModeExpanded {
|
|
const CGSize intrinsicTitleSize =
|
|
[self.titleLabel sizeThatFits:CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX)];
|
|
const CGSize intrinsicImageSize =
|
|
[self.imageView sizeThatFits:CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX)];
|
|
CGFloat intrinsicWidth = intrinsicTitleSize.width + intrinsicImageSize.width +
|
|
self.imageTitleSpace + internalLayoutInsets.left +
|
|
internalLayoutInsets.right + self.contentEdgeInsets.left +
|
|
self.contentEdgeInsets.right;
|
|
CGFloat intrinsicHeight = MAX(intrinsicTitleSize.height, intrinsicImageSize.height) +
|
|
self.contentEdgeInsets.top + self.contentEdgeInsets.bottom +
|
|
internalLayoutInsets.top + internalLayoutInsets.bottom;
|
|
return CGSizeMake(intrinsicWidth, intrinsicHeight);
|
|
}
|
|
|
|
- (CGSize)intrinsicContentSize {
|
|
CGSize contentSize = CGSizeZero;
|
|
if (self.mode == MDCFloatingButtonModeNormal) {
|
|
contentSize = [self intrinsicContentSizeForModeNormal];
|
|
} else if (self.mode == MDCFloatingButtonModeExpanded) {
|
|
contentSize = [self intrinsicContentSizeForModeExpanded];
|
|
}
|
|
|
|
if (self.minimumSize.height > 0) {
|
|
contentSize.height = MAX(self.minimumSize.height, contentSize.height);
|
|
}
|
|
if (self.maximumSize.height > 0) {
|
|
contentSize.height = MIN(self.maximumSize.height, contentSize.height);
|
|
}
|
|
if (self.minimumSize.width > 0) {
|
|
contentSize.width = MAX(self.minimumSize.width, contentSize.width);
|
|
}
|
|
if (self.maximumSize.width > 0) {
|
|
contentSize.width = MIN(self.maximumSize.width, contentSize.width);
|
|
}
|
|
|
|
return contentSize;
|
|
}
|
|
|
|
/*
|
|
Performs custom layout when the FAB is in .expanded mode. Specifically, the layout algorithm is
|
|
as follows:
|
|
|
|
1. Inset the bounds by the value of `contentEdgeInsets` and use this as the layout bounds.
|
|
2. Determine the intrinsic sizes of the imageView and titleLabel.
|
|
3. Compute the space remaining for the titleLabel after accounting for the imageView and built-in
|
|
alignment guidelines (internalLayoutInsets).
|
|
4. Position the imageView along the leading (or trailing) edge of the button, inset by
|
|
internalLayoutInsets.left (flipped for RTL).
|
|
5. Position the titleLabel along the leading edge of its available space.
|
|
6. Apply the imageEdgeInsets and titleEdgeInsets to their respective views.
|
|
*/
|
|
- (void)layoutSubviews {
|
|
// We have to set cornerRadius before laying out subviews so that the boundingPath is correct.
|
|
CGRect visibleBounds = UIEdgeInsetsInsetRect(self.bounds, self.visibleAreaInsets);
|
|
self.layer.cornerRadius = CGRectGetHeight(visibleBounds) / 2;
|
|
[super layoutSubviews];
|
|
|
|
if (self.mode == MDCFloatingButtonModeNormal) {
|
|
return;
|
|
}
|
|
|
|
// Position the imageView and titleView
|
|
//
|
|
// +------------------------------------+
|
|
// | | | | CEI TOP | |
|
|
// |CEI +--+ |+-----+ |CEI|
|
|
// | LT ||SP||Title| |RGT|
|
|
// | +--+ |+-----+ | |
|
|
// | | | | CEI BOT | |
|
|
// +------------------------------------+
|
|
//
|
|
// (A) The same spacing on either side of the label.
|
|
// (SP) The spacing between the image and title
|
|
// (CEI) Content Edge Insets
|
|
//
|
|
// The diagram above assumes an LTR user interface orientation
|
|
// and a .leadingIcon imageLocation for this button.
|
|
|
|
const CGRect insetBounds = [self insetBoundsForBounds:self.bounds];
|
|
|
|
const CGFloat imageViewWidth = CGRectGetWidth(self.imageView.bounds);
|
|
const CGFloat boundsCenterY = CGRectGetMidY(insetBounds);
|
|
CGFloat titleWidthAvailable = CGRectGetWidth(insetBounds);
|
|
titleWidthAvailable -= imageViewWidth;
|
|
titleWidthAvailable -= self.imageTitleSpace;
|
|
|
|
const CGFloat availableHeight = CGRectGetHeight(insetBounds);
|
|
CGSize titleIntrinsicSize =
|
|
[self.titleLabel sizeThatFits:CGSizeMake(titleWidthAvailable, availableHeight)];
|
|
|
|
const CGSize titleSize = CGSizeMake(MAX(0, MIN(titleIntrinsicSize.width, titleWidthAvailable)),
|
|
MAX(0, MIN(titleIntrinsicSize.height, availableHeight)));
|
|
|
|
CGPoint titleCenter;
|
|
CGPoint imageCenter;
|
|
BOOL isLTR =
|
|
self.effectiveUserInterfaceLayoutDirection == UIUserInterfaceLayoutDirectionLeftToRight;
|
|
BOOL isLeadingIcon = self.imageLocation == MDCFloatingButtonImageLocationLeading;
|
|
|
|
// If we are LTR with a leading image, the image goes on the left.
|
|
// If we are RTL with a trailing image, the image goes on the left.
|
|
if ((isLTR && isLeadingIcon) || (!isLTR && !isLeadingIcon)) {
|
|
const CGFloat imageCenterX = CGRectGetMinX(insetBounds) + (imageViewWidth / 2);
|
|
const CGFloat titleCenterX =
|
|
CGRectGetMaxX(insetBounds) - titleWidthAvailable + (titleSize.width / 2);
|
|
titleCenter = CGPointMake(titleCenterX, boundsCenterY);
|
|
imageCenter = CGPointMake(imageCenterX, boundsCenterY);
|
|
}
|
|
// If we are LTR with a trailing image, the image goes on the right.
|
|
// If we are RTL with a leading image, the image goes on the right.
|
|
else {
|
|
const CGFloat imageCenterX = CGRectGetMaxX(insetBounds) - (imageViewWidth / 2);
|
|
const CGFloat titleCenterX =
|
|
CGRectGetMinX(insetBounds) + titleWidthAvailable - (titleSize.width / 2);
|
|
imageCenter = CGPointMake(imageCenterX, boundsCenterY);
|
|
titleCenter = CGPointMake(titleCenterX, boundsCenterY);
|
|
}
|
|
|
|
self.imageView.center = imageCenter;
|
|
self.imageView.frame = UIEdgeInsetsInsetRect(self.imageView.frame, self.imageEdgeInsets);
|
|
self.titleLabel.center = titleCenter;
|
|
CGRect newBounds = CGRectStandardize(self.titleLabel.bounds);
|
|
self.titleLabel.bounds = (CGRect){newBounds.origin, titleSize};
|
|
self.titleLabel.frame = UIEdgeInsetsInsetRect(self.titleLabel.frame, self.titleEdgeInsets);
|
|
}
|
|
|
|
- (CGRect)insetBoundsForBounds:(CGRect)bounds {
|
|
BOOL isLeadingIcon = self.imageLocation == MDCFloatingButtonImageLocationLeading;
|
|
UIEdgeInsets adjustedLayoutInsets =
|
|
(isLeadingIcon ? internalLayoutInsets : MDFInsetsFlippedHorizontally(internalLayoutInsets));
|
|
return UIEdgeInsetsInsetRect(UIEdgeInsetsInsetRect(bounds, adjustedLayoutInsets),
|
|
self.contentEdgeInsets);
|
|
}
|
|
|
|
#pragma mark - Mode animator
|
|
|
|
- (MDCFloatingButtonModeAnimator *)modeAnimator {
|
|
if (!_modeAnimator) {
|
|
_modeAnimator =
|
|
[[MDCFloatingButtonModeAnimator alloc] initWithTitleLabel:self.titleLabel
|
|
titleLabelContainerView:_titleLabelContainerView];
|
|
_modeAnimator.delegate = self;
|
|
}
|
|
return _modeAnimator;
|
|
}
|
|
|
|
#pragma mark MDCFloatingButtonModeAnimatorDelegate
|
|
|
|
- (void)floatingButtonModeAnimatorCommitLayoutChanges:(MDCFloatingButtonModeAnimator *)modeAnimator
|
|
mode:(MDCFloatingButtonMode)mode {
|
|
[self sizeToFit];
|
|
}
|
|
|
|
#pragma mark - Property Setters/Getters
|
|
|
|
- (void)setShape:(MDCFloatingButtonShape)shape {
|
|
if (_shape == shape) {
|
|
return;
|
|
}
|
|
_shape = shape;
|
|
[self updateShapeAndAllowResize:YES];
|
|
}
|
|
|
|
- (void)setMode:(MDCFloatingButtonMode)mode {
|
|
[self setMode:mode animated:NO animateAlongside:nil completion:nil];
|
|
}
|
|
|
|
- (void)setMode:(MDCFloatingButtonMode)mode animated:(BOOL)animated {
|
|
[self setMode:mode animated:animated animateAlongside:nil completion:nil];
|
|
}
|
|
|
|
- (void)setMode:(MDCFloatingButtonMode)mode
|
|
animated:(BOOL)animated
|
|
animateAlongside:(void (^)(void))animateAlongside
|
|
completion:(void (^)(BOOL finished))completion {
|
|
if (_mode == mode) {
|
|
if (animateAlongside) {
|
|
animateAlongside();
|
|
}
|
|
if (completion) {
|
|
completion(YES);
|
|
}
|
|
return;
|
|
}
|
|
_mode = mode;
|
|
|
|
[self updateShapeAndAllowResize:YES];
|
|
|
|
[[self modeAnimator] modeDidChange:mode
|
|
animated:animated
|
|
animateAlongside:animateAlongside
|
|
completion:completion];
|
|
}
|
|
|
|
- (void)setMinimumSize:(CGSize)size
|
|
forShape:(MDCFloatingButtonShape)shape
|
|
inMode:(MDCFloatingButtonMode)mode {
|
|
NSMutableDictionary *modeToMinimumSize = self.shapeToModeToMinimumSize[@(shape)];
|
|
if (!modeToMinimumSize) {
|
|
modeToMinimumSize = [@{} mutableCopy];
|
|
self.shapeToModeToMinimumSize[@(shape)] = modeToMinimumSize;
|
|
}
|
|
modeToMinimumSize[@(mode)] = [NSValue valueWithCGSize:size];
|
|
if (shape == _shape && mode == self.mode) {
|
|
[self updateShapeAndAllowResize:YES];
|
|
}
|
|
}
|
|
|
|
- (CGSize)minimumSizeForMode:(MDCFloatingButtonMode)mode {
|
|
NSMutableDictionary *modeToMinimumSize = self.shapeToModeToMinimumSize[@(_shape)];
|
|
if (!modeToMinimumSize) {
|
|
return CGSizeZero;
|
|
}
|
|
|
|
NSValue *sizeValue = modeToMinimumSize[@(mode)];
|
|
if (sizeValue) {
|
|
return [sizeValue CGSizeValue];
|
|
} else {
|
|
return CGSizeZero;
|
|
}
|
|
}
|
|
|
|
- (BOOL)updateMinimumSize {
|
|
CGSize newSize = [self minimumSizeForMode:self.mode];
|
|
if (CGSizeEqualToSize(newSize, self.minimumSize)) {
|
|
return NO;
|
|
}
|
|
super.minimumSize = newSize;
|
|
return YES;
|
|
}
|
|
|
|
- (void)setMaximumSize:(CGSize)size
|
|
forShape:(MDCFloatingButtonShape)shape
|
|
inMode:(MDCFloatingButtonMode)mode {
|
|
NSMutableDictionary *modeToMaximumSize = self.shapeToModeToMaximumSize[@(shape)];
|
|
if (!modeToMaximumSize) {
|
|
modeToMaximumSize = [@{} mutableCopy];
|
|
self.shapeToModeToMaximumSize[@(shape)] = modeToMaximumSize;
|
|
}
|
|
modeToMaximumSize[@(mode)] = [NSValue valueWithCGSize:size];
|
|
if (shape == _shape && mode == self.mode) {
|
|
[self updateShapeAndAllowResize:YES];
|
|
}
|
|
}
|
|
|
|
- (CGSize)maximumSizeForMode:(MDCFloatingButtonMode)mode {
|
|
NSMutableDictionary *modeToMaximumSize = self.shapeToModeToMaximumSize[@(_shape)];
|
|
if (!modeToMaximumSize) {
|
|
return CGSizeZero;
|
|
}
|
|
|
|
NSValue *sizeValue = modeToMaximumSize[@(mode)];
|
|
if (sizeValue) {
|
|
return [sizeValue CGSizeValue];
|
|
} else {
|
|
return CGSizeZero;
|
|
}
|
|
}
|
|
|
|
- (BOOL)updateMaximumSize {
|
|
CGSize newSize = [self maximumSizeForMode:self.mode];
|
|
if (CGSizeEqualToSize(newSize, self.maximumSize)) {
|
|
return NO;
|
|
}
|
|
super.maximumSize = newSize;
|
|
return YES;
|
|
}
|
|
|
|
- (void)setContentEdgeInsets:(UIEdgeInsets)contentEdgeInsets
|
|
forShape:(MDCFloatingButtonShape)shape
|
|
inMode:(MDCFloatingButtonMode)mode {
|
|
NSMutableDictionary *modeToContentEdgeInsets = self.shapeToModeToContentEdgeInsets[@(shape)];
|
|
if (!modeToContentEdgeInsets) {
|
|
modeToContentEdgeInsets = [@{} mutableCopy];
|
|
self.shapeToModeToContentEdgeInsets[@(shape)] = modeToContentEdgeInsets;
|
|
}
|
|
modeToContentEdgeInsets[@(mode)] = [NSValue valueWithUIEdgeInsets:contentEdgeInsets];
|
|
if (shape == _shape && mode == self.mode) {
|
|
[self updateShapeAndAllowResize:YES];
|
|
}
|
|
}
|
|
|
|
- (UIEdgeInsets)contentEdgeInsetsForMode:(MDCFloatingButtonMode)mode {
|
|
NSMutableDictionary *modeToContentEdgeInsets = self.shapeToModeToContentEdgeInsets[@(_shape)];
|
|
if (!modeToContentEdgeInsets) {
|
|
return UIEdgeInsetsZero;
|
|
}
|
|
|
|
NSValue *insetsValue = modeToContentEdgeInsets[@(mode)];
|
|
if (insetsValue) {
|
|
return [insetsValue UIEdgeInsetsValue];
|
|
} else {
|
|
return UIEdgeInsetsZero;
|
|
}
|
|
}
|
|
|
|
- (void)updateContentEdgeInsets {
|
|
super.contentEdgeInsets = [self contentEdgeInsetsForMode:self.mode];
|
|
}
|
|
|
|
- (void)setHitAreaInsets:(UIEdgeInsets)insets
|
|
forShape:(MDCFloatingButtonShape)shape
|
|
inMode:(MDCFloatingButtonMode)mode {
|
|
NSMutableDictionary *modeToHitAreaInsets = self.shapeToModeToHitAreaInsets[@(shape)];
|
|
if (!modeToHitAreaInsets) {
|
|
modeToHitAreaInsets = [@{} mutableCopy];
|
|
self.shapeToModeToHitAreaInsets[@(shape)] = modeToHitAreaInsets;
|
|
}
|
|
modeToHitAreaInsets[@(mode)] = [NSValue valueWithUIEdgeInsets:insets];
|
|
if (shape == _shape && mode == self.mode) {
|
|
[self updateShapeAndAllowResize:NO];
|
|
}
|
|
}
|
|
|
|
- (UIEdgeInsets)hitAreaInsetsForMode:(MDCFloatingButtonMode)mode {
|
|
NSMutableDictionary *modeToHitAreaInsets = self.shapeToModeToHitAreaInsets[@(_shape)];
|
|
if (!modeToHitAreaInsets) {
|
|
return UIEdgeInsetsZero;
|
|
}
|
|
|
|
NSValue *insetsValue = modeToHitAreaInsets[@(mode)];
|
|
if (insetsValue) {
|
|
return [insetsValue UIEdgeInsetsValue];
|
|
} else {
|
|
return UIEdgeInsetsZero;
|
|
}
|
|
}
|
|
|
|
- (void)updateHitAreaInsets {
|
|
super.hitAreaInsets = [self hitAreaInsetsForMode:self.mode];
|
|
}
|
|
|
|
- (void)setCenterVisibleArea:(BOOL)centerVisibleArea
|
|
forShape:(MDCFloatingButtonShape)shape
|
|
inMode:(MDCFloatingButtonMode)mode {
|
|
NSMutableDictionary *modeToCenterVisibleArea = self.shapeToModeToCenterVisibleArea[@(shape)];
|
|
if (!modeToCenterVisibleArea) {
|
|
modeToCenterVisibleArea = [@{} mutableCopy];
|
|
self.shapeToModeToCenterVisibleArea[@(shape)] = modeToCenterVisibleArea;
|
|
}
|
|
modeToCenterVisibleArea[@(mode)] = @(centerVisibleArea);
|
|
if (shape == _shape && mode == self.mode) {
|
|
[self updateShapeAndAllowResize:NO];
|
|
}
|
|
}
|
|
|
|
- (BOOL)centerVisibleAreaForMode:(MDCFloatingButtonMode)mode {
|
|
NSMutableDictionary *modeToCenterVisibleArea = self.shapeToModeToCenterVisibleArea[@(_shape)];
|
|
if (!modeToCenterVisibleArea) {
|
|
return NO;
|
|
}
|
|
|
|
return [modeToCenterVisibleArea[@(mode)] boolValue];
|
|
}
|
|
|
|
- (void)updateCenterVisibleArea {
|
|
super.centerVisibleArea = [self centerVisibleAreaForMode:self.mode];
|
|
}
|
|
|
|
- (void)updateShapeAndAllowResize:(BOOL)allowsResize {
|
|
BOOL minimumSizeChanged = [self updateMinimumSize];
|
|
BOOL maximumSizeChanged = [self updateMaximumSize];
|
|
[self updateContentEdgeInsets];
|
|
[self updateHitAreaInsets];
|
|
[self updateCenterVisibleArea];
|
|
|
|
if (allowsResize && (minimumSizeChanged || maximumSizeChanged)) {
|
|
[self invalidateIntrinsicContentSize];
|
|
[self setNeedsLayout];
|
|
}
|
|
}
|
|
|
|
@end
|