From ad2b16134b8dc04667604a7b8609686a1c401494 Mon Sep 17 00:00:00 2001 From: Jeff Verkoeyen Date: Wed, 12 Aug 2020 09:03:08 -0700 Subject: [PATCH 01/20] Internal change PiperOrigin-RevId: 326243049 --- .../Snackbar/examples/SnackbarSimpleExample.m | 6 +-- .../unit/MDCSnackbarManagerInstanceTests.m | 48 ++++++++++--------- 2 files changed, 29 insertions(+), 25 deletions(-) diff --git a/components/Snackbar/examples/SnackbarSimpleExample.m b/components/Snackbar/examples/SnackbarSimpleExample.m index 45f854d1ce5..96f5849be2d 100644 --- a/components/Snackbar/examples/SnackbarSimpleExample.m +++ b/components/Snackbar/examples/SnackbarSimpleExample.m @@ -53,7 +53,7 @@ - (void)viewDidLoad { target:self action:@selector(toggleDynamicType)] ]; - MDCSnackbarManager.delegate = self; + MDCSnackbarManager.defaultManager.delegate = self; } - (void)toggleModes { @@ -134,7 +134,7 @@ - (void)showColorThemedSnackbar:(id)sender { forState:UIControlStateNormal]; [MDCSnackbarManager.defaultManager setButtonTitleColor:MDCPalette.purplePalette.tint700 forState:UIControlStateHighlighted]; - MDCSnackbarManager.messageTextColor = MDCPalette.greenPalette.tint500; + MDCSnackbarManager.defaultManager.messageTextColor = MDCPalette.greenPalette.tint500; [MDCSnackbarManager.defaultManager showMessage:message]; } @@ -161,7 +161,7 @@ - (void)showDecustomizedSnackbar:(id)sender { MDCSnackbarManager.buttonFont = nil; [MDCSnackbarManager.defaultManager setButtonTitleColor:nil forState:UIControlStateNormal]; [MDCSnackbarManager.defaultManager setButtonTitleColor:nil forState:UIControlStateHighlighted]; - MDCSnackbarManager.messageTextColor = nil; + MDCSnackbarManager.defaultManager.messageTextColor = nil; MDCSnackbarMessage *message = [[MDCSnackbarMessage alloc] init]; message.text = @"Back to the standard snackbar"; diff --git a/components/Snackbar/tests/unit/MDCSnackbarManagerInstanceTests.m b/components/Snackbar/tests/unit/MDCSnackbarManagerInstanceTests.m index 5a92b866112..90b678b4ad3 100644 --- a/components/Snackbar/tests/unit/MDCSnackbarManagerInstanceTests.m +++ b/components/Snackbar/tests/unit/MDCSnackbarManagerInstanceTests.m @@ -38,8 +38,10 @@ - (void)setUp { [super setUp]; self.messageTextColor = MDCSnackbarManager.messageTextColor; - self.snackbarMessageViewShadowColor = MDCSnackbarManager.snackbarMessageViewShadowColor; - self.snackbarMessageViewBackgroundColor = MDCSnackbarManager.snackbarMessageViewBackgroundColor; + self.snackbarMessageViewShadowColor = + MDCSnackbarManager.defaultManager.snackbarMessageViewShadowColor; + self.snackbarMessageViewBackgroundColor = + MDCSnackbarManager.defaultManager.snackbarMessageViewBackgroundColor; self.titleColorForState = [@{} mutableCopy]; NSUInteger maxState = UIControlStateNormal | UIControlStateDisabled | UIControlStateSelected | UIControlStateHighlighted; @@ -51,9 +53,11 @@ - (void)setUp { - (void)tearDown { // Restore the Snackbar Manager's state - MDCSnackbarManager.messageTextColor = self.messageTextColor; - MDCSnackbarManager.snackbarMessageViewShadowColor = self.snackbarMessageViewShadowColor; - MDCSnackbarManager.snackbarMessageViewBackgroundColor = self.snackbarMessageViewBackgroundColor; + MDCSnackbarManager.defaultManager.messageTextColor = self.messageTextColor; + MDCSnackbarManager.defaultManager.snackbarMessageViewShadowColor = + self.snackbarMessageViewShadowColor; + MDCSnackbarManager.defaultManager.snackbarMessageViewBackgroundColor = + self.snackbarMessageViewBackgroundColor; for (NSNumber *state in self.titleColorForState.allKeys) { if (self.titleColorForState[state] != nil) { [MDCSnackbarManager.defaultManager setButtonTitleColor:self.titleColorForState[state] @@ -93,32 +97,32 @@ - (void)testClassPropertiesAssignToDefaultInstance { FakeMDCSnackbarManagerDelegate *delegate = [[FakeMDCSnackbarManagerDelegate alloc] init]; // When - MDCSnackbarManager.alignment = MDCSnackbarAlignmentLeading; - MDCSnackbarManager.buttonFont = [UIFont systemFontOfSize:72]; - MDCSnackbarManager.delegate = delegate; + MDCSnackbarManager.defaultManager.alignment = MDCSnackbarAlignmentLeading; + MDCSnackbarManager.defaultManager.buttonFont = [UIFont systemFontOfSize:72]; + MDCSnackbarManager.defaultManager.delegate = delegate; [MDCSnackbarManager.defaultManager mdc_setAdjustsFontForContentSizeCategory:YES]; - MDCSnackbarManager.messageFont = [UIFont systemFontOfSize:66]; - MDCSnackbarManager.messageTextColor = UIColor.orangeColor; - MDCSnackbarManager.shouldApplyStyleChangesToVisibleSnackbars = YES; - MDCSnackbarManager.snackbarMessageViewBackgroundColor = UIColor.brownColor; - MDCSnackbarManager.snackbarMessageViewShadowColor = UIColor.purpleColor; + MDCSnackbarManager.defaultManager.messageFont = [UIFont systemFontOfSize:66]; + MDCSnackbarManager.defaultManager.messageTextColor = UIColor.orangeColor; + MDCSnackbarManager.defaultManager.shouldApplyStyleChangesToVisibleSnackbars = YES; + MDCSnackbarManager.defaultManager.snackbarMessageViewBackgroundColor = UIColor.brownColor; + MDCSnackbarManager.defaultManager.snackbarMessageViewShadowColor = UIColor.purpleColor; [MDCSnackbarManager.defaultManager setButtonTitleColor:UIColor.greenColor forState:UIControlStateDisabled]; // Then - XCTAssertEqual(manager.alignment, MDCSnackbarManager.alignment); - XCTAssertEqualObjects(manager.buttonFont, MDCSnackbarManager.buttonFont); - XCTAssertEqual(manager.delegate, MDCSnackbarManager.delegate); + XCTAssertEqual(manager.alignment, MDCSnackbarManager.defaultManager.alignment); + XCTAssertEqualObjects(manager.buttonFont, MDCSnackbarManager.defaultManager.buttonFont); + XCTAssertEqual(manager.delegate, MDCSnackbarManager.defaultManager.delegate); XCTAssertEqual(manager.mdc_adjustsFontForContentSizeCategory, - MDCSnackbarManager.mdc_adjustsFontForContentSizeCategory); - XCTAssertEqualObjects(manager.messageFont, MDCSnackbarManager.messageFont); - XCTAssertEqual(manager.messageTextColor, MDCSnackbarManager.messageTextColor); + MDCSnackbarManager.defaultManager.mdc_adjustsFontForContentSizeCategory); + XCTAssertEqualObjects(manager.messageFont, MDCSnackbarManager.defaultManager.messageFont); + XCTAssertEqual(manager.messageTextColor, MDCSnackbarManager.defaultManager.messageTextColor); XCTAssertEqual(manager.shouldApplyStyleChangesToVisibleSnackbars, - MDCSnackbarManager.shouldApplyStyleChangesToVisibleSnackbars); + MDCSnackbarManager.defaultManager.shouldApplyStyleChangesToVisibleSnackbars); XCTAssertEqual(manager.snackbarMessageViewBackgroundColor, - MDCSnackbarManager.snackbarMessageViewBackgroundColor); + MDCSnackbarManager.defaultManager.snackbarMessageViewBackgroundColor); XCTAssertEqual(manager.snackbarMessageViewShadowColor, - MDCSnackbarManager.snackbarMessageViewShadowColor); + MDCSnackbarManager.defaultManager.snackbarMessageViewShadowColor); XCTAssertEqual( [manager buttonTitleColorForState:UIControlStateDisabled], [MDCSnackbarManager.defaultManager buttonTitleColorForState:UIControlStateDisabled]); From 91a2953b0d052bd6411c178be22923445ed03b35 Mon Sep 17 00:00:00 2001 From: Bryan Oltman Date: Wed, 12 Aug 2020 09:34:51 -0700 Subject: [PATCH 02/20] Add isPointerInteractionEnabled availability check. There are still some iOS 13.4 betas floating around that had not yet introduced this API. This change should prevent those users from crashing when attempting to get/set this value. PiperOrigin-RevId: 326248791 --- .../Buttons/src/MDCFloatingButton+Animation.m | 21 +++++++++++++------ .../src/private/MDCAlertActionManager.m | 5 ++++- 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/components/Buttons/src/MDCFloatingButton+Animation.m b/components/Buttons/src/MDCFloatingButton+Animation.m index f09a18d87c9..a680315473e 100644 --- a/components/Buttons/src/MDCFloatingButton+Animation.m +++ b/components/Buttons/src/MDCFloatingButton+Animation.m @@ -13,6 +13,7 @@ // limitations under the License. #import "MDCFloatingButton+Animation.h" +#import "MDCFloatingButton.h" #if TARGET_IPHONE_SIMULATOR float UIAnimationDragCoefficient(void); // Private API for simulator animation speed @@ -96,8 +97,10 @@ - (void)expand:(BOOL)animated completion:(void (^_Nullable)(void))completion { #ifdef __IPHONE_13_4 BOOL wasPointerInteractionEnabled = NO; if (@available(iOS 13.4, *)) { - wasPointerInteractionEnabled = self.pointerInteractionEnabled; - self.pointerInteractionEnabled = NO; + if ([self respondsToSelector:@selector(isPointerInteractionEnabled)]) { + wasPointerInteractionEnabled = self.pointerInteractionEnabled; + self.pointerInteractionEnabled = NO; + } } #endif void (^expandActions)(void) = ^{ @@ -111,7 +114,9 @@ - (void)expand:(BOOL)animated completion:(void (^_Nullable)(void))completion { [self.imageView.layer removeAnimationForKey:kMDCFloatingButtonTransformKey]; #ifdef __IPHONE_13_4 if (@available(iOS 13.4, *)) { - self.pointerInteractionEnabled = wasPointerInteractionEnabled; + if ([self respondsToSelector:@selector(isPointerInteractionEnabled)]) { + self.pointerInteractionEnabled = wasPointerInteractionEnabled; + } } #endif if (completion) { @@ -188,8 +193,10 @@ - (void)collapse:(BOOL)animated completion:(void (^_Nullable)(void))completion { #ifdef __IPHONE_13_4 BOOL wasPointerInteractionEnabled = NO; if (@available(iOS 13.4, *)) { - wasPointerInteractionEnabled = self.pointerInteractionEnabled; - self.pointerInteractionEnabled = NO; + if ([self respondsToSelector:@selector(isPointerInteractionEnabled)]) { + wasPointerInteractionEnabled = self.pointerInteractionEnabled; + self.pointerInteractionEnabled = NO; + } } #endif @@ -204,7 +211,9 @@ - (void)collapse:(BOOL)animated completion:(void (^_Nullable)(void))completion { [self.imageView.layer removeAnimationForKey:kMDCFloatingButtonTransformKey]; #ifdef __IPHONE_13_4 if (@available(iOS 13.4, *)) { - self.pointerInteractionEnabled = wasPointerInteractionEnabled; + if ([self respondsToSelector:@selector(isPointerInteractionEnabled)]) { + self.pointerInteractionEnabled = wasPointerInteractionEnabled; + } } #endif if (completion) { diff --git a/components/Dialogs/src/private/MDCAlertActionManager.m b/components/Dialogs/src/private/MDCAlertActionManager.m index 41805aaf1b3..2620957779e 100644 --- a/components/Dialogs/src/private/MDCAlertActionManager.m +++ b/components/Dialogs/src/private/MDCAlertActionManager.m @@ -13,6 +13,7 @@ // limitations under the License. #import "MDCAlertActionManager.h" +#import "MaterialButtons.h" @interface MDCAlertActionManager () @@ -93,7 +94,9 @@ - (MDCButton *)makeButtonForAction:(MDCAlertAction *)action button.accessibilityIdentifier = action.accessibilityIdentifier; #ifdef __IPHONE_13_4 if (@available(iOS 13.4, *)) { - button.pointerInteractionEnabled = YES; + if ([self respondsToSelector:@selector(isPointerInteractionEnabled)]) { + button.pointerInteractionEnabled = YES; + } } #endif [button addTarget:target action:selector forControlEvents:UIControlEventTouchUpInside]; From 8dc2768f7fabd5451cf8b8956098020465ac9088 Mon Sep 17 00:00:00 2001 From: Jeff Verkoeyen Date: Wed, 12 Aug 2020 09:54:16 -0700 Subject: [PATCH 03/20] [Snackbar] Deprecate more class methods on MDCSnackbarManager. Also undeprecated the accidental deprecation of the instance property, messageTextColor. PiperOrigin-RevId: 326252484 --- components/Snackbar/src/MDCSnackbarManager.h | 106 ++++++++++--------- 1 file changed, 58 insertions(+), 48 deletions(-) diff --git a/components/Snackbar/src/MDCSnackbarManager.h b/components/Snackbar/src/MDCSnackbarManager.h index 44d7f932e00..4bdfd8bf051 100644 --- a/components/Snackbar/src/MDCSnackbarManager.h +++ b/components/Snackbar/src/MDCSnackbarManager.h @@ -165,6 +165,11 @@ */ @property(nonatomic, strong, nullable) UIFont *messageFont; +/** + The color for the message text in the Snackbar message view. + */ +@property(nonatomic, strong, nullable) UIColor *messageTextColor; + /** The font for the button text in the Snackbar message view. */ @@ -295,58 +300,11 @@ */ + (void)showMessage:(nullable MDCSnackbarMessage *)message; -/** - Calls @c -setPresentationHostView: on the @c defaultManager instance. - */ -+ (void)setPresentationHostView:(nullable UIView *)hostView; - -/** - Calls @c -hasMessagesShowingORQueued on the @c defaultManager instance. - */ -+ (BOOL)hasMessagesShowingOrQueued; - /** Calls @c -dismissAndCallCompletionBlocksWithCategory: on the @c defaultManager instance. */ + (void)dismissAndCallCompletionBlocksWithCategory:(nullable NSString *)category; -/** - Calls -setBottomOffset: on the @c defaultManager instance. - */ -+ (void)setBottomOffset:(CGFloat)offset; - -/** - Calls @c -suspendAllMessages on the @c defaultManager instance. - */ -+ (nullable id)suspendAllMessages; - -/** - Calls @c -suspendMessagesWithCategory: on the @c defaultManager instance. - */ -+ (nullable id)suspendMessagesWithCategory: - (nullable NSString *)category; - -/** - Calls @c -resumeMessagesWithToken: on the @c defaultManager instance. - */ -+ (void)resumeMessagesWithToken:(nullable id)token; - -/** - Calls @c -setButtonTitleColor:forState: on the @c defaultManager instance. - */ -+ (void)setButtonTitleColor:(nullable UIColor *)titleColor forState:(UIControlState)state; - -/** - Bound to @c messageTextColor on the @c defaultManager instance. - */ -@property(class, nonatomic, strong, nullable) UIColor *messageTextColor; - -/** - Bound to @c mdc_adjustsFontForContentSizeCategory on the @c defaultManager instance. - */ -@property(class, nonatomic, readwrite, setter=mdc_setAdjustsFontForContentSizeCategory:) - BOOL mdc_adjustsFontForContentSizeCategory; - /** Bound to @c delegate on the @c defaultManager instance. */ @@ -379,7 +337,7 @@ /** The color for the message text in the Snackbar message view. */ -@property(nonatomic, strong, nullable) UIColor *messageTextColor __deprecated_msg( +@property(class, nonatomic, strong, nullable) UIColor *messageTextColor __deprecated_msg( "Use MDCSnackbarManager.defaultManager.messageTextColor instead."); /** @@ -395,6 +353,13 @@ UIFont *buttonFont __deprecated_msg("Use MDCSnackbarManager.defaultManager.buttonFont instead.") ; +/** + Bound to @c mdc_adjustsFontForContentSizeCategory on the @c defaultManager instance. + */ +@property(class, nonatomic, readwrite, setter=mdc_setAdjustsFontForContentSizeCategory:) + BOOL mdc_adjustsFontForContentSizeCategory __deprecated_msg( + "Use MDCSnackbarManager.defaultManager.mdc_adjustsFontForContentSizeCategory instead."); + /** Bound to @c shouldApplyStyleChangesToVisibleSnackbars on the @c defaultManager instance. */ @@ -407,4 +372,49 @@ + (nullable UIColor *)buttonTitleColorForState:(UIControlState)state __deprecated_msg("Use [MDCSnackbarManager.defaultManager buttonTitleColorForState:] instead."); +/** + Calls @c -resumeMessagesWithToken: on the @c defaultManager instance. + */ ++ (void)resumeMessagesWithToken:(nullable id)token + __deprecated_msg("Use [MDCSnackbarManager.defaultManager resumeMessagesWithToken:] instead."); + +/** + Calls @c -suspendAllMessages on the @c defaultManager instance. + */ ++ (nullable id)suspendAllMessages __deprecated_msg( + "Use [MDCSnackbarManager.defaultManager suspendAllMessages] instead."); + +/** + Calls @c -suspendMessagesWithCategory: on the @c defaultManager instance. + */ ++ (nullable id)suspendMessagesWithCategory:(nullable NSString *)category + __deprecated_msg( + "Use [MDCSnackbarManager.defaultManager suspendMessagesWithCategory:] instead."); + +/** + Calls @c -setButtonTitleColor:forState: on the @c defaultManager instance. + */ ++ (void)setButtonTitleColor:(nullable UIColor *)titleColor + forState:(UIControlState)state + __deprecated_msg( + "Use [MDCSnackbarManager.defaultManager setButtonTitleColor:forState:] instead."); + +/** + Calls -setBottomOffset: on the @c defaultManager instance. + */ ++ (void)setBottomOffset:(CGFloat)offset + __deprecated_msg("Use [MDCSnackbarManager.defaultManager setBottomOffset:] instead."); + +/** + Calls @c -hasMessagesShowingORQueued on the @c defaultManager instance. + */ ++ (BOOL)hasMessagesShowingOrQueued __deprecated_msg( + "Use [MDCSnackbarManager.defaultManager hasMessagesShowingOrQueued] instead."); + +/** + Calls @c -setPresentationHostView: on the @c defaultManager instance. + */ ++ (void)setPresentationHostView:(nullable UIView *)hostView + __deprecated_msg("Use [MDCSnackbarManager.defaultManager setPresentationHostView:] instead."); + @end From c48cd65ade15bc29ab2329ed9e8032e1f5f01a2c Mon Sep 17 00:00:00 2001 From: Nobody Date: Wed, 12 Aug 2020 10:50:44 -0700 Subject: [PATCH 04/20] Material Snackbar support for selecting which window to present on. PiperOrigin-RevId: 326265567 --- components/Snackbar/src/MDCSnackbarManager.m | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/components/Snackbar/src/MDCSnackbarManager.m b/components/Snackbar/src/MDCSnackbarManager.m index 931ebb09543..f4831ddbafe 100644 --- a/components/Snackbar/src/MDCSnackbarManager.m +++ b/components/Snackbar/src/MDCSnackbarManager.m @@ -366,10 +366,7 @@ - (void)activateOverlay:(UIView *)overlay forMessage:(MDCSnackbarMessage *)messa } else if (self.presentationHostView) { targetView = self.presentationHostView; } else if ([window isKindOfClass:[MDCOverlayWindow class]]) { - // If the application's window is an overlay window, take advantage of it. Otherwise, just add - // our overlay view into the main view controller's hierarchy. - MDCOverlayWindow *overlayWindow = (MDCOverlayWindow *)window; - [overlayWindow activateOverlay:overlay withLevel:UIWindowLevelNormal]; + targetView = window; } else { // Find the most top view controller to display overlay. UIViewController *topViewController = [window rootViewController]; @@ -379,7 +376,12 @@ - (void)activateOverlay:(UIView *)overlay forMessage:(MDCSnackbarMessage *)messa targetView = [topViewController view]; } - if (targetView) { + if ([targetView isKindOfClass:[MDCOverlayWindow class]]) { + // If target view is an overlay window, take advantage of it. Otherwise, just add + // our overlay view into the main view controller's hierarchy. + MDCOverlayWindow *overlayWindow = (MDCOverlayWindow *)targetView; + [overlayWindow activateOverlay:overlay withLevel:UIWindowLevelNormal]; + } else if (targetView) { overlay.frame = targetView.bounds; overlay.autoresizingMask = (UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight); overlay.translatesAutoresizingMaskIntoConstraints = YES; From 225cd3c0cba6ac194c77a418188d1c4421227f2e Mon Sep 17 00:00:00 2001 From: Andrew Overton Date: Wed, 12 Aug 2020 13:47:00 -0700 Subject: [PATCH 05/20] [BottomSheet] Ensure transition controller pass-through accessors work following presentation controller initialization PiperOrigin-RevId: 326304121 --- .../unit/MDCActionSheetControllerTests.m | 23 ++++++++++++++++++- .../src/MDCBottomSheetTransitionController.m | 11 +++++++++ 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/components/ActionSheet/tests/unit/MDCActionSheetControllerTests.m b/components/ActionSheet/tests/unit/MDCActionSheetControllerTests.m index d16bf7b7cfc..21e4ad4e1fd 100644 --- a/components/ActionSheet/tests/unit/MDCActionSheetControllerTests.m +++ b/components/ActionSheet/tests/unit/MDCActionSheetControllerTests.m @@ -15,9 +15,11 @@ #import #import "../../src/private/MDCActionSheetHeaderView.h" +#import "MaterialActionSheet.h" #import "MDCActionSheetTestHelper.h" -#import "MaterialMath.h" +#import "MaterialBottomSheet.h" #import "MaterialShadowElevations.h" +#import "MaterialMath.h" static const CGFloat kSafeAreaAmount = 20; static const CGFloat kDefaultDividerOpacity = (CGFloat)0.12; @@ -259,6 +261,25 @@ - (void)testSetAlignTitlesWhenNoActionsHaveImages { XCTAssertFalse(self.actionSheet.addLeadingPaddingToCell); } +- (void)testPassThroughPropertiesToPresentationControllerWorkAfterItsInitialization { + // Given + [self.actionSheet addAction:[MDCActionSheetAction actionWithTitle:@"An action" + image:nil + handler:nil]]; + NSString *expectedScrimAccessibilityLabel = + @"Accessibility label to be passed to presentation controller"; + __unused UIView *forceLoadedViewResultingInInitializationOfPresentationController = + self.actionSheet.view; + + // When + self.actionSheet.transitionController.scrimAccessibilityLabel = expectedScrimAccessibilityLabel; + + // Then + NSString *actualScrimAccessibilityLabel = + self.actionSheet.mdc_bottomSheetPresentationController.scrimAccessibilityLabel; + XCTAssertEqualObjects(expectedScrimAccessibilityLabel, actualScrimAccessibilityLabel); +} + #pragma mark - Opening height - (CGRect)setUpActionSheetWithHeight:(CGFloat)height diff --git a/components/BottomSheet/src/MDCBottomSheetTransitionController.m b/components/BottomSheet/src/MDCBottomSheetTransitionController.m index 5d3af43f8e4..55a6d5f00ca 100644 --- a/components/BottomSheet/src/MDCBottomSheetTransitionController.m +++ b/components/BottomSheet/src/MDCBottomSheetTransitionController.m @@ -18,6 +18,11 @@ static const NSTimeInterval MDCBottomSheetTransitionDuration = 0.25; +@interface MDCBottomSheetTransitionController () +@property(nonatomic, weak, nullable) + MDCBottomSheetPresentationController *currentPresentationController; +@end + @implementation MDCBottomSheetTransitionController { @protected UIColor *_scrimColor; @@ -57,6 +62,7 @@ - (instancetype)init { presentationController.scrimAccessibilityHint = _scrimAccessibilityHint; presentationController.scrimAccessibilityLabel = _scrimAccessibilityLabel; presentationController.preferredSheetHeight = _preferredSheetHeight; + _currentPresentationController = presentationController; return presentationController; } @@ -157,6 +163,7 @@ - (CGRect)frameOfPresentedViewController:(UIViewController *)presentedViewContro - (void)setScrimColor:(UIColor *)scrimColor { _scrimColor = scrimColor; + _currentPresentationController.scrimColor = scrimColor; } - (UIColor *)scrimColor { @@ -165,6 +172,7 @@ - (UIColor *)scrimColor { - (void)setIsScrimAccessibilityElement:(BOOL)isScrimAccessibilityElement { _isScrimAccessibilityElement = isScrimAccessibilityElement; + _currentPresentationController.isScrimAccessibilityElement = isScrimAccessibilityElement; } - (BOOL)isScrimAccessibilityElement { @@ -173,6 +181,7 @@ - (BOOL)isScrimAccessibilityElement { - (void)setScrimAccessibilityLabel:(NSString *)scrimAccessibilityLabel { _scrimAccessibilityLabel = scrimAccessibilityLabel; + _currentPresentationController.scrimAccessibilityLabel = scrimAccessibilityLabel; } - (NSString *)scrimAccessibilityLabel { @@ -181,6 +190,7 @@ - (NSString *)scrimAccessibilityLabel { - (void)setScrimAccessibilityHint:(NSString *)scrimAccessibilityHint { _scrimAccessibilityHint = scrimAccessibilityHint; + _currentPresentationController.scrimAccessibilityHint = scrimAccessibilityHint; } - (NSString *)scrimAccessibilityHint { @@ -189,6 +199,7 @@ - (NSString *)scrimAccessibilityHint { - (void)setScrimAccessibilityTraits:(UIAccessibilityTraits)scrimAccessibilityTraits { _scrimAccessibilityTraits = scrimAccessibilityTraits; + _currentPresentationController.scrimAccessibilityTraits = scrimAccessibilityTraits; } - (UIAccessibilityTraits)scrimAccessibilityTraits { From 1baab835a533df37b5e599444937f75deadca056 Mon Sep 17 00:00:00 2001 From: Jeff Verkoeyen Date: Thu, 13 Aug 2020 09:36:41 -0700 Subject: [PATCH 06/20] Add a new ScalableFontDescriptor library. This library can be used to represent custom scalable fonts on iOS 11 and up. PiperOrigin-RevId: 326463440 --- .../src/MDCScalableFontDescriptor.h | 131 ++++++++++++ .../src/MDCScalableFontDescriptor.m | 122 +++++++++++ .../src/MaterialScalableFontDescriptor.h | 16 ++ .../unit/MDCScalableFontDescriptorTests.m | 190 ++++++++++++++++++ 4 files changed, 459 insertions(+) create mode 100644 components/ScalableFontDescriptor/src/MDCScalableFontDescriptor.h create mode 100644 components/ScalableFontDescriptor/src/MDCScalableFontDescriptor.m create mode 100644 components/ScalableFontDescriptor/src/MaterialScalableFontDescriptor.h create mode 100644 components/ScalableFontDescriptor/tests/unit/MDCScalableFontDescriptorTests.m diff --git a/components/ScalableFontDescriptor/src/MDCScalableFontDescriptor.h b/components/ScalableFontDescriptor/src/MDCScalableFontDescriptor.h new file mode 100644 index 00000000000..a63c6f7381d --- /dev/null +++ b/components/ScalableFontDescriptor/src/MDCScalableFontDescriptor.h @@ -0,0 +1,131 @@ +// Copyright 2020-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 +#import + +/** + A light-weight representation of a scalable font. + + On iOS 11 and up, this type enables you to describe a custom font and the corresponding + UIFontMetrics that enable the font to scale in response to Dynamic Type settings. + + Pre iOS 11, this type can only be used to describe unscalable custom fonts. + + ## Usage notes + + This type enables you to pair font descriptors with specific UIFontMetrics. This is most commonly + used for describing the metrics of a collection of custom fonts. If you just need to create a + one-off custom font, prefer using a snippet of code like below instead: + + ``` + guard let customFont = UIFont(name: "CustomFont-Light", size: UIFont.labelFontSize) else { + fatalError(""" + Failed to load the "CustomFont-Light" font. + Make sure the font file is included in the project and the font name is spelled correctly. + """ + ) + } + if #available(iOS 11.0, *) { + label.font = UIFontMetrics(forTextStyle: .headline).scaledFont(for: customFont) + label.adjustsFontForContentSizeCategory = true + } else { + label.font = customFont + } + ``` + + When using this type, the above snippet of code looks like this instead: + + ``` + // Create the type scale. + let fontDescriptor = UIFontDescriptor(name: "CustomFont-Light", size: UIFont.labelFontSize) + let scalableFontDescriptor: MDCScalableFontDescriptor + if #available(iOS 11, *) { + scalableFontDescriptor = MDCScalableFontDescriptor( + fontDescriptor: fontDescriptor, + fontMetrics: UIFontMetrics(forTextStyle: .largeTitle) + ) + } else { + scalableFontDescriptor = MDCScalableFontDescriptor(fontDescriptor: fontDescriptor) + } + + // Use the scalable font descriptor. + if #available(iOS 11, *) { + label.font = scalableFontDescriptor.preferredFont(compatibleWith: label.traitCollection) + label.adjustsFontForContentSizeCategory = true + } else { + label.font = scalableFontDescriptor.baseFont() + } + ``` + + @seealso https://developer.apple.com/documentation/uikit/uifont/scaling_fonts_automatically + */ +__attribute__((objc_subclassing_restricted)) @interface MDCScalableFontDescriptor : NSObject + +/** + A description of the font that will be loaded by baseFont and + preferredFontCompatibleWithTraitCollection:. + */ +@property(nonnull, nonatomic, readonly) UIFontDescriptor *fontDescriptor; + +/** + A representation of the specific text style that will be used to scale the font. + */ +@property(nonnull, nonatomic, readonly) UIFontMetrics *fontMetrics API_AVAILABLE(ios(11.0)); + +/** + Creates a new type scale with the provided font descriptor and font metrics. + + @param fontDescriptor A collection of attributes that describe the desired font. + @param fontMetrics A representation of the specific text style that will be used to scale the font. + */ +- (nonnull instancetype)initWithFontDescriptor:(nonnull UIFontDescriptor *)fontDescriptor + fontMetrics:(nonnull UIFontMetrics *)fontMetrics + API_AVAILABLE(ios(11.0)); + +/** + Creates a new type scale with the provided font descriptor. + + @param fontDescriptor A collection of attributes that describe the desired font. + */ +- (nonnull instancetype)initWithFontDescriptor:(nonnull UIFontDescriptor *)fontDescriptor + API_DEPRECATED("Use initWithFontDescriptor:fontMetrics: instead.", ios(9.0, 11.0)) + NS_DESIGNATED_INITIALIZER; + +/** + Returns an unscaled, unscaling font. + + @note If a custom font is defined in @c fontDescriptor, it is the caller's responsibility to ensure + that the font has been loaded into memory. + */ +- (nonnull UIFont *)baseFont; + +/** + Returns a pre-scaled, scalable font configured with this type scale's fontMetrics. + + If @c traitCollection is @c nil, then the current system content size category is used. + + @note If a custom font is defined in @c fontDescriptor, it is the caller's responsibility to ensure + that the font has been loaded into memory. + @param traitCollection The trait collection to use when pre-scaling the font. The returned + font is appropriate for use in an interface that adopts the specified + traits. + @return A font that is pre-scaled for the given @c traitCollection. + */ +- (nonnull UIFont *)preferredFontCompatibleWithTraitCollection: + (nullable UITraitCollection *)traitCollection API_AVAILABLE(ios(11.0)); + +- (null_unspecified instancetype)init NS_UNAVAILABLE; + +@end diff --git a/components/ScalableFontDescriptor/src/MDCScalableFontDescriptor.m b/components/ScalableFontDescriptor/src/MDCScalableFontDescriptor.m new file mode 100644 index 00000000000..10e692d70c3 --- /dev/null +++ b/components/ScalableFontDescriptor/src/MDCScalableFontDescriptor.m @@ -0,0 +1,122 @@ +// Copyright 2020-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 "MDCScalableFontDescriptor.h" + +@implementation MDCScalableFontDescriptor + +- (instancetype)initWithFontDescriptor:(UIFontDescriptor *)fontDescriptor + fontMetrics:(UIFontMetrics *)fontMetrics { + self = [self initWithFontDescriptor:fontDescriptor]; + if (self) { + _fontDescriptor = fontDescriptor; + _fontMetrics = fontMetrics; + } + return self; +} + +- (instancetype)initWithFontDescriptor:(UIFontDescriptor *)fontDescriptor { + self = [super init]; + if (self) { + _fontDescriptor = fontDescriptor; + } + return self; +} + +- (UIFont *)baseFont { + return [UIFont fontWithDescriptor:self.fontDescriptor size:self.fontDescriptor.pointSize]; +} + +- (UIFont *)preferredFontCompatibleWithTraitCollection:(UITraitCollection *)traitCollection { + return [self.fontMetrics scaledFontForFont:self.baseFont + compatibleWithTraitCollection:traitCollection]; +} + +#if DEBUG + +// NOTE: UIFontMetric detection is potentially broken on Forge on Mac due to b/142536380. +- (NSString *)description { + // This method is unoptimized, and assumes it's only called in testing environments. + if (@available(iOS 11.0, *)) { + static NSArray *textStyles; + static NSArray *sizeCategories; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + textStyles = @[ + UIFontTextStyleLargeTitle, + UIFontTextStyleTitle1, + UIFontTextStyleTitle2, + UIFontTextStyleTitle3, + UIFontTextStyleHeadline, + UIFontTextStyleSubheadline, + UIFontTextStyleBody, + UIFontTextStyleCallout, + UIFontTextStyleCaption1, + UIFontTextStyleCaption2, + UIFontTextStyleFootnote, + ]; + + sizeCategories = @[ + UIContentSizeCategoryExtraSmall, + UIContentSizeCategorySmall, + UIContentSizeCategoryMedium, + UIContentSizeCategoryLarge, + UIContentSizeCategoryExtraLarge, + UIContentSizeCategoryExtraExtraLarge, + UIContentSizeCategoryExtraExtraExtraLarge, + UIContentSizeCategoryAccessibilityMedium, + UIContentSizeCategoryAccessibilityLarge, + UIContentSizeCategoryAccessibilityExtraLarge, + UIContentSizeCategoryAccessibilityExtraExtraLarge, + UIContentSizeCategoryAccessibilityExtraExtraExtraLarge, + ]; + }); + + NSMutableString *descriptionString = [NSMutableString + stringWithFormat:@"%@ Descriptor: %@\n", [super description], self.fontDescriptor]; + UIFont *testFont = [UIFont systemFontOfSize:99 weight:UIFontWeightRegular]; + + // Check all UIFontTextStyle values and search for one that scales fonts equivalently to this + // style's `fontMetrics`. + for (UIFontTextStyle textStyle in textStyles) { + BOOL matchedTextStyle = YES; + UIFontMetrics *textStyleMetrics = [UIFontMetrics metricsForTextStyle:textStyle]; + // Compare the scaled font at all UIContentSizeCategory values to determine if the metrics are + // equivalent. + for (UIContentSizeCategory sizeCategory in sizeCategories) { + UITraitCollection *traitCollection = + [UITraitCollection traitCollectionWithPreferredContentSizeCategory:sizeCategory]; + UIFont *selfScaledFont = [self.fontMetrics scaledFontForFont:testFont + compatibleWithTraitCollection:traitCollection]; + UIFont *otherScaledFont = [textStyleMetrics scaledFontForFont:testFont + compatibleWithTraitCollection:traitCollection]; + if (![selfScaledFont isEqual:otherScaledFont]) { + matchedTextStyle = NO; + break; + } + } + if (matchedTextStyle) { + [descriptionString appendFormat:@"Metrics: %@", textStyle]; + break; + } + } + return [descriptionString copy]; + } else { + return [super description]; + } +} + +#endif + +@end diff --git a/components/ScalableFontDescriptor/src/MaterialScalableFontDescriptor.h b/components/ScalableFontDescriptor/src/MaterialScalableFontDescriptor.h new file mode 100644 index 00000000000..8b97658d147 --- /dev/null +++ b/components/ScalableFontDescriptor/src/MaterialScalableFontDescriptor.h @@ -0,0 +1,16 @@ +// Copyright 2020-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 "MDCScalableFontDescriptor.h" diff --git a/components/ScalableFontDescriptor/tests/unit/MDCScalableFontDescriptorTests.m b/components/ScalableFontDescriptor/tests/unit/MDCScalableFontDescriptorTests.m new file mode 100644 index 00000000000..282e064a8a8 --- /dev/null +++ b/components/ScalableFontDescriptor/tests/unit/MDCScalableFontDescriptorTests.m @@ -0,0 +1,190 @@ +// Copyright 2020-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 +#import + +#import "MaterialScalableFontDescriptor.h" + +@interface MDCScalableFontDescriptorTests : XCTestCase + +/** Reusable font metrics for the @c body text style. */ +@property(nonatomic, strong) UIFontMetrics *bodyFontMetrics NS_AVAILABLE_IOS(11.0); + +/** Reusable trait collection that has the default (@c .large) content size category. */ +@property(nonatomic, strong) UITraitCollection *largeContentSizeTraitCollection; + +@end + +@implementation MDCScalableFontDescriptorTests + +- (void)setUp { + [super setUp]; + + if (@available(iOS 11.0, *)) { + self.bodyFontMetrics = [UIFontMetrics metricsForTextStyle:UIFontTextStyleBody]; + } + self.largeContentSizeTraitCollection = [UITraitCollection + traitCollectionWithPreferredContentSizeCategory:UIContentSizeCategoryLarge]; +} + +- (void)tearDown { + self.largeContentSizeTraitCollection = nil; + if (@available(iOS 11.0, *)) { + self.bodyFontMetrics = nil; + } + + [super tearDown]; +} + +- (void)testBaseFontReturnsSomethingForUnavailableCustomFont { + if (@available(iOS 11.0, *)) { + // Given + MDCScalableFontDescriptor *style = [[MDCScalableFontDescriptor alloc] + initWithFontDescriptor:[UIFontDescriptor fontDescriptorWithName:@"NotAFont" size:14] + fontMetrics:[UIFontMetrics metricsForTextStyle:UIFontTextStyleBody]]; + + // When + UIFont *fallbackFont = style.baseFont; + + // Then + XCTAssertNotNil(fallbackFont); + } +} + +- (void)testPreferredFontReturnsSomethingForUnavailableCustomFont { + if (@available(iOS 11.0, *)) { + // Given + MDCScalableFontDescriptor *style = [[MDCScalableFontDescriptor alloc] + initWithFontDescriptor:[UIFontDescriptor fontDescriptorWithName:@"NotAFont" size:14] + fontMetrics:[UIFontMetrics metricsForTextStyle:UIFontTextStyleBody]]; + + // When + UIFont *fallbackFont = [style preferredFontCompatibleWithTraitCollection:nil]; + + // Then + XCTAssertNotNil(fallbackFont); + } +} + +- (void)testBaseFontForSystemFont { + if (@available(iOS 11.0, *)) { + // Given + MDCScalableFontDescriptor *style = [[MDCScalableFontDescriptor alloc] + initWithFontDescriptor:[UIFont systemFontOfSize:14].fontDescriptor + fontMetrics:[UIFontMetrics metricsForTextStyle:UIFontTextStyleBody]]; + + // When + UIFont *systemFont = style.baseFont; + + // Then + XCTAssertNotNil(systemFont); + XCTAssertEqualWithAccuracy(systemFont.pointSize, style.fontDescriptor.pointSize, 0.001); + } +} + +- (void)testPreferredFontForSystemFont { + if (@available(iOS 11.0, *)) { + // Given + MDCScalableFontDescriptor *style = [[MDCScalableFontDescriptor alloc] + initWithFontDescriptor:[UIFont systemFontOfSize:14].fontDescriptor + fontMetrics:[UIFontMetrics metricsForTextStyle:UIFontTextStyleBody]]; + + // When + UIFont *systemFont = + [style preferredFontCompatibleWithTraitCollection:self.largeContentSizeTraitCollection]; + + // Then + XCTAssertNotNil(systemFont); + XCTAssertEqualWithAccuracy(systemFont.pointSize, style.fontDescriptor.pointSize, 0.001); + } +} + +- (void)testPreferredFontScalesWithContentSize { + if (@available(iOS 11.0, *)) { + // Given + MDCScalableFontDescriptor *style = [[MDCScalableFontDescriptor alloc] + initWithFontDescriptor:[UIFont systemFontOfSize:14].fontDescriptor + fontMetrics:[UIFontMetrics metricsForTextStyle:UIFontTextStyleBody]]; + UIFont *baseFont = + [style preferredFontCompatibleWithTraitCollection:self.largeContentSizeTraitCollection]; + + // When + UIFont *aXXXLFont = + [style preferredFontCompatibleWithTraitCollection: + [UITraitCollection traitCollectionWithPreferredContentSizeCategory: + UIContentSizeCategoryAccessibilityExtraExtraExtraLarge]]; + + // Then + XCTAssertGreaterThan(aXXXLFont.pointSize, baseFont.pointSize); + } +} + +/** + Verifies that the @c description method will include the font name, point size, and text style used + sufficient to recreate the type scale style. + */ +- (void)testDescriptionPrintsSufficientInformationForGeneralDebugging { + if (@available(iOS 11.0, *)) { + // Given + static NSArray *testTextStyles; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + testTextStyles = @[ + UIFontTextStyleLargeTitle, + UIFontTextStyleTitle1, + UIFontTextStyleTitle2, + // TODO(b/142536380): Re-enable testing of .title3 once Forge on Mac fixes a scaling bug. + // UIFontTextStyleTitle3, + UIFontTextStyleHeadline, + UIFontTextStyleSubheadline, + UIFontTextStyleBody, + UIFontTextStyleCallout, + UIFontTextStyleCaption1, + UIFontTextStyleCaption2, + UIFontTextStyleFootnote, + ]; + }); + UIFontDescriptor *fontDescriptor = [UIFontDescriptor fontDescriptorWithName:@"NotAFont" + size:10]; + for (UIFontTextStyle textStyle in testTextStyles) { + // Given + UIFontMetrics *metrics = [UIFontMetrics metricsForTextStyle:textStyle]; + MDCScalableFontDescriptor *style = + [[MDCScalableFontDescriptor alloc] initWithFontDescriptor:fontDescriptor + fontMetrics:metrics]; + + // When + NSString *description = [style description]; + NSLog(@"%@", description); + + // Then + NSRange textStyleRange = [description rangeOfString:[textStyle description]]; + XCTAssertNotEqual(textStyleRange.location, NSNotFound); + XCTAssertEqual(textStyleRange.length, textStyle.length); + + NSRange fontPointSizeRange = + [description rangeOfString:[NSString stringWithFormat:@"%.0f", fontDescriptor.pointSize]]; + XCTAssertNotEqual(fontPointSizeRange.location, NSNotFound); + XCTAssertGreaterThan(fontPointSizeRange.length, 0); + + NSString *descriptorFontName = fontDescriptor.fontAttributes[UIFontDescriptorNameAttribute]; + NSRange fontNameRange = [description rangeOfString:descriptorFontName]; + XCTAssertNotEqual(fontNameRange.location, NSNotFound); + XCTAssertEqual(fontNameRange.length, descriptorFontName.length); + } + } +} + +@end From 4a3058d59175eb455ef2f4e38df262b3cb6040cf Mon Sep 17 00:00:00 2001 From: Alyssa Weiss Date: Thu, 13 Aug 2020 14:42:12 -0700 Subject: [PATCH 07/20] [BottomNavigation] modify layouts with division to use floor(), as not using this was causing some non integer pixel values, which then resulted in a blurry layout PiperOrigin-RevId: 326527243 --- .../src/MDCBottomNavigationBar.m | 18 ++++---- .../src/private/MDCBottomNavigationItemView.m | 45 ++++++++++--------- .../unit/BottomNavigationItemViewTests.m | 8 ++-- .../tests/unit/MDCBottomNavigationBarTests.m | 27 +++++++++++ 4 files changed, 65 insertions(+), 33 deletions(-) diff --git a/components/BottomNavigation/src/MDCBottomNavigationBar.m b/components/BottomNavigation/src/MDCBottomNavigationBar.m index da41073e7d4..45e971dfb34 100644 --- a/components/BottomNavigation/src/MDCBottomNavigationBar.m +++ b/components/BottomNavigation/src/MDCBottomNavigationBar.m @@ -321,9 +321,9 @@ - (void)sizeItemsLayoutViewItemsDistributed:(BOOL)itemsDistributed CGFloat layoutFrameWidth = maxItemWidth * self.items.count; layoutFrameWidth = MIN(bottomNavWidthInset, layoutFrameWidth); containerWidth = MIN(bottomNavWidthInset, MAX(containerWidth, layoutFrameWidth)); - CGFloat clusteredOffsetX = (bottomNavSize.width - containerWidth) / 2; + CGFloat clusteredOffsetX = MDCFloor((bottomNavSize.width - containerWidth) / 2); self.itemsLayoutView.frame = CGRectMake(clusteredOffsetX, 0, containerWidth, barHeight); - CGFloat itemLayoutFrameOffsetX = (containerWidth - layoutFrameWidth) / 2; + CGFloat itemLayoutFrameOffsetX = MDCFloor((containerWidth - layoutFrameWidth) / 2); self.itemLayoutFrame = CGRectMake(itemLayoutFrameOffsetX, 0, layoutFrameWidth, barHeight); } } @@ -340,13 +340,15 @@ - (void)layoutItemViews { MDCBottomNavigationItemView *itemView = self.itemViews[i]; itemView.titleBelowIcon = self.isTitleBelowIcon; if (layoutDirection == UIUserInterfaceLayoutDirectionLeftToRight) { - itemView.frame = CGRectMake( - CGRectGetMinX(self.itemLayoutFrame) + i * itemWidth + self.itemsHorizontalPadding, 0, - itemWidth - 2 * self.itemsHorizontalPadding, navBarHeight); + itemView.frame = + CGRectMake(MDCFloor(CGRectGetMinX(self.itemLayoutFrame) + i * itemWidth + + self.itemsHorizontalPadding), + 0, MDCFloor(itemWidth - 2 * self.itemsHorizontalPadding), navBarHeight); } else { - itemView.frame = CGRectMake( - CGRectGetMaxX(self.itemLayoutFrame) - (i + 1) * itemWidth + self.itemsHorizontalPadding, - 0, itemWidth - 2 * self.itemsHorizontalPadding, navBarHeight); + itemView.frame = + CGRectMake(MDCFloor(CGRectGetMaxX(self.itemLayoutFrame) - (i + 1) * itemWidth + + self.itemsHorizontalPadding), + 0, MDCFloor(itemWidth - 2 * self.itemsHorizontalPadding), navBarHeight); } } } diff --git a/components/BottomNavigation/src/private/MDCBottomNavigationItemView.m b/components/BottomNavigation/src/private/MDCBottomNavigationItemView.m index fdbfbf32d3b..0f5324d6872 100644 --- a/components/BottomNavigation/src/private/MDCBottomNavigationItemView.m +++ b/components/BottomNavigation/src/private/MDCBottomNavigationItemView.m @@ -197,12 +197,12 @@ - (CGSize)sizeThatFitsForVerticalLayout { 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); + CGRectMake(MDCFloor(badgeCenter.x - badgeSize.width / 2), + MDCFloor(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, + labelFrame = CGRectMake(MDCFloor(CGRectGetMidX(iconFrame) - labelSize.width / 2), CGRectGetMaxY(iconFrame) + self.contentVerticalMargin, labelSize.width, labelSize.height); } @@ -216,12 +216,12 @@ - (CGSize)sizeThatFitsForHorizontalLayout { 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); + CGRectMake(MDCFloor(badgeCenter.x - badgeSize.width / 2), + MDCFloor(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); + MDCFloor(CGRectGetMidY(iconFrame) - labelSize.height / 2), + labelSize.width, labelSize.height); return CGRectStandardize(CGRectUnion(labelFrame, CGRectUnion(iconFrame, badgeFrame))).size; } @@ -256,9 +256,10 @@ - (void)calculateVerticalLayoutInBounds:(CGRect)contentBounds // 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. + MAX(MDCFloor(CGRectGetMidY(contentBoundingRect) - totalContentHeight / 2 + + iconHeight / 2), // Content centered + MDCFloor(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. @@ -272,13 +273,13 @@ - (void)calculateVerticalLayoutInBounds:(CGRect)contentBounds // 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); + *outLabelFrame = CGRectMake(MDCFloor(labelCenter.x - (labelSize.width / 2)), + MDCFloor(labelCenter.y - (labelSize.height / 2)), labelSize.width, + labelSize.height); } if (outIconFrame != NULL) { - *outIconFrame = CGRectMake(iconImageViewCenter.x - (iconImageViewSize.width / 2), - iconImageViewCenter.y - (iconImageViewSize.height / 2), + *outIconFrame = CGRectMake(MDCFloor(iconImageViewCenter.x - (iconImageViewSize.width / 2)), + MDCFloor(iconImageViewCenter.y - (iconImageViewSize.height / 2)), iconImageViewSize.width, iconImageViewSize.height); } } @@ -316,7 +317,7 @@ - (void)calculateHorizontalLayoutInBounds:(CGRect)contentBounds CGFloat centerY = CGRectGetMidY(contentBoundingRect); // Amount icon center is offset from the leading edge. - CGFloat iconCenterOffset = contentPadding + (iconImageViewSize.width / 2); + CGFloat iconCenterOffset = contentPadding + iconImageViewSize.width / 2; // Determine the position of the label and icon CGPoint iconImageViewCenter = @@ -329,13 +330,13 @@ - (void)calculateHorizontalLayoutInBounds:(CGRect)contentBounds // 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); + *outLabelFrame = CGRectMake(MDCFloor(labelCenter.x - (labelSize.width / 2)), + MDCFloor(labelCenter.y - (labelSize.height / 2)), labelSize.width, + labelSize.height); } if (outIconFrame != NULL) { - *outIconFrame = CGRectMake(iconImageViewCenter.x - (iconImageViewSize.width / 2), - iconImageViewCenter.y - (iconImageViewSize.height / 2), + *outIconFrame = CGRectMake(MDCFloor(iconImageViewCenter.x - (iconImageViewSize.width / 2)), + MDCFloor(iconImageViewCenter.y - (iconImageViewSize.height / 2)), iconImageViewSize.width, iconImageViewSize.height); } } @@ -446,7 +447,7 @@ - (CGPoint)badgeCenterFromIconFrame:(CGRect)iconFrame isRTL:(BOOL)isRTL { // https://material.io/tools/icons/?icon=chrome_reader_mode&style=baseline CGFloat badgeCenterY = CGRectGetMinY(iconFrame) + (badgeSize.height / 2); - CGFloat badgeCenterXOffset = kBadgeXOffsetFromIconEdgeWithTextLTR + (badgeSize.width / 2); + CGFloat badgeCenterXOffset = kBadgeXOffsetFromIconEdgeWithTextLTR + badgeSize.width / 2; if (self.badgeValue.length == 0) { badgeCenterXOffset = kBadgeXOffsetFromIconEdgeEmptyLTR; } diff --git a/components/BottomNavigation/tests/unit/BottomNavigationItemViewTests.m b/components/BottomNavigation/tests/unit/BottomNavigationItemViewTests.m index 1683cf93b70..9338ac38ec9 100644 --- a/components/BottomNavigation/tests/unit/BottomNavigationItemViewTests.m +++ b/components/BottomNavigation/tests/unit/BottomNavigationItemViewTests.m @@ -476,9 +476,11 @@ - (void)testPointerEffectHoverRectIsFrameForOneVisibleView { CGRect expectedRect = CGRectInset( itemView.label.frame, MDCButtonNavigationItemViewPointerEffectHighlightRectInset.width, MDCButtonNavigationItemViewPointerEffectHighlightRectInset.height); - XCTAssert(CGRectEqualToRect([itemView pointerEffectHighlightRect], expectedRect), @"%@", - [self errorStringForExpectedPointerRect:expectedRect - doesNotMatchActualRect:[itemView pointerEffectHighlightRect]]); + CGRect actualRect = [itemView pointerEffectHighlightRect]; + XCTAssertEqualWithAccuracy(actualRect.size.width, expectedRect.size.width, 0.001); + XCTAssertEqualWithAccuracy(actualRect.size.height, expectedRect.size.height, 0.001); + XCTAssertEqualWithAccuracy(actualRect.origin.x, expectedRect.origin.x, 0.001); + XCTAssertEqualWithAccuracy(actualRect.origin.y, expectedRect.origin.y, 0.001); } /** diff --git a/components/BottomNavigation/tests/unit/MDCBottomNavigationBarTests.m b/components/BottomNavigation/tests/unit/MDCBottomNavigationBarTests.m index c041fd05798..1c173187c32 100644 --- a/components/BottomNavigation/tests/unit/MDCBottomNavigationBarTests.m +++ b/components/BottomNavigation/tests/unit/MDCBottomNavigationBarTests.m @@ -450,6 +450,33 @@ - (void)testSizeThatFitsExplicitlyExcludesSafeArea { NSStringFromCGSize(finalSize), NSStringFromCGSize(initialSize)); } +/* + Tests when the bottomNavBar width is not evenly divisible by the number of items, the difference + between start of the frame and center of the first element and end of the frame and center of the + last element is max one pixel + */ +- (void)testLaysoutAcrossBarEvenly { + // Given + UITabBarItem *item1 = [[UITabBarItem alloc] initWithTitle:@"1" image:nil tag:0]; + UITabBarItem *item2 = [[UITabBarItem alloc] initWithTitle:@"2" image:nil tag:0]; + UITabBarItem *item3 = [[UITabBarItem alloc] initWithTitle:@"3" image:nil tag:0]; + UITabBarItem *item4 = [[UITabBarItem alloc] initWithTitle:@"4" image:nil tag:0]; + UITabBarItem *item5 = [[UITabBarItem alloc] initWithTitle:@"5" image:nil tag:0]; + CGFloat bottomNavBarWidth = 304; + self.bottomNavBar.frame = CGRectMake(0, 0, bottomNavBarWidth, 56); + + // When + self.bottomNavBar.items = @[ item1, item2, item3, item4, item5 ]; + [self.bottomNavBar layoutIfNeeded]; + + // Then + MDCBottomNavigationItemView *viewForItem1 = + (MDCBottomNavigationItemView *)[self.bottomNavBar viewForItem:item1]; + MDCBottomNavigationItemView *viewForItem5 = + (MDCBottomNavigationItemView *)[self.bottomNavBar viewForItem:item5]; + XCTAssertEqualWithAccuracy(viewForItem1.center.x, bottomNavBarWidth - viewForItem5.center.x, 1.0); +} + #pragma mark - Autolayout support - (void)testIntrinsicContentSizeIgnoresSafeArea { From 70d7a73cb4beaf4aa67e1d51626c6e1ab9b12b6a Mon Sep 17 00:00:00 2001 From: Wenyu Zhang Date: Thu, 13 Aug 2020 16:11:36 -0700 Subject: [PATCH 08/20] [Chips] Add a snapshot test case for MDCChipView with minimumSize set and centerVisible set to YES. PiperOrigin-RevId: 326545217 --- .../Chips/tests/snapshot/MDCChipViewSnapshotTests.m | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/components/Chips/tests/snapshot/MDCChipViewSnapshotTests.m b/components/Chips/tests/snapshot/MDCChipViewSnapshotTests.m index 2c55492e59d..ebc6308756f 100644 --- a/components/Chips/tests/snapshot/MDCChipViewSnapshotTests.m +++ b/components/Chips/tests/snapshot/MDCChipViewSnapshotTests.m @@ -695,6 +695,18 @@ - (void)testChipWithCustomFrameWhenCenterVisibleArea { [self snapshotVerifyView:snapshotView]; } +- (void)testChipWithMinimumSizeWhenCenterVisibleAreaIsYES { + // When + self.chip.centerVisibleArea = YES; + self.chip.minimumSize = CGSizeMake(80, 44); + self.chip.bounds = CGRectMake(0, 0, 80, 44); + [self.chip layoutIfNeeded]; + + // Then + UIView *snapshotView = [self.chip mdc_addToBackgroundView]; + [self snapshotVerifyView:snapshotView]; +} + - (void)testChipWithCustomCornerRadiusAndCustomFrameWhenCenterVisibleArea { // When self.chip.centerVisibleArea = YES; From 7f9d2cc3504b12da7a8dec3d8028c6b6102cd75e Mon Sep 17 00:00:00 2001 From: Yarden Eitan Date: Fri, 14 Aug 2020 07:33:21 -0700 Subject: [PATCH 09/20] [ButtonBar] Adds Large Content Viewer support to MDCButtonBar, MDCNavigationBar, and MDCAppBar. PiperOrigin-RevId: 326652230 --- components/ButtonBar/src/MDCButtonBar.h | 24 +++ components/ButtonBar/src/MDCButtonBar.m | 68 ++++++- components/ButtonBar/src/MDCButtonBarButton.h | 14 ++ .../src/private/MDCAppBarButtonBarBuilder.h | 14 ++ .../src/private/MDCAppBarButtonBarBuilder.m | 13 ++ .../src/private/MDCButtonBarButton.m | 26 +++ .../ButtonBar/tests/unit/MDCButtonBarTests.m | 183 ++++++++++++++++++ 7 files changed, 335 insertions(+), 7 deletions(-) diff --git a/components/ButtonBar/src/MDCButtonBar.h b/components/ButtonBar/src/MDCButtonBar.h index 5f4d32daf65..9702aff275b 100644 --- a/components/ButtonBar/src/MDCButtonBar.h +++ b/components/ButtonBar/src/MDCButtonBar.h @@ -14,6 +14,8 @@ #import +#import "MaterialAvailability.h" + /** The position of the button bar, typically aligned with the leading or trailing edge of the screen. @@ -234,3 +236,25 @@ typedef NS_OPTIONS(NSUInteger, MDCBarButtonItemLayoutHints) { /** Whether or not this bar button item is the last button in the list. */ MDCBarButtonItemLayoutHintsIsLastButton = 1 << 1, }; + +#if MDC_AVAILABLE_SDK_IOS(13_0) +/** + This component supports UIKit's Large Content Viewer. It is recommended that images associated with + each bar button item be backed with a PDF image with "preserve vector data" enabled within the + assets entry in the catalog. This ensures that the image is scaled appropriately in the content + viewer. + + Alternatively specify an image to use for the large content viewer using UIBarButtonItem's property + @c largeContentSizeImage . If an image is specified, the given image is used as-is for the large + content viewer and will not be scaled. + + If the image is not backed by PDF and a @c largeContentSizeImage is not specified, the given + @c image will be scaled and may be blurry. + + For more details on the Large Content Viewer see: + https://developer.apple.com/videos/play/wwdc2019/261/ + */ +@interface MDCButtonBar (UILargeContentViewerInteractionDelegate) < + UILargeContentViewerInteractionDelegate> +@end +#endif // MDC_AVAILABLE_SDK_IOS(13_0) diff --git a/components/ButtonBar/src/MDCButtonBar.m b/components/ButtonBar/src/MDCButtonBar.m index fc72cf16340..f70ef9f9a55 100644 --- a/components/ButtonBar/src/MDCButtonBar.m +++ b/components/ButtonBar/src/MDCButtonBar.m @@ -48,6 +48,14 @@ - (void)commonMDCButtonBarInit { _layoutPosition = MDCButtonBarLayoutPositionNone; _defaultBuilder = [[MDCAppBarButtonBarBuilder alloc] init]; + +#if MDC_AVAILABLE_SDK_IOS(13_0) + if (@available(iOS 13, *)) { + // If clients report conflicting gesture recognizers please see proposed solution in the + // internal document: go/mdc-ios-bottomnavigation-largecontentvieweritem + [self addInteraction:[[UILargeContentViewerInteraction alloc] initWithDelegate:self]]; + } +#endif // MDC_AVAILABLE_SDK_IOS(13_0) } - (instancetype)initWithFrame:(CGRect)frame { @@ -316,25 +324,36 @@ - (void)observeValueForKeyPath:(NSString *)keyPath } #if MDC_AVAILABLE_SDK_IOS(14_0) - else if (@available(iOS 14.0, *)) { - if ([keyPath isEqualToString:NSStringFromSelector(@selector(menu))]) { + else if ([keyPath isEqualToString:NSStringFromSelector(@selector(menu))]) { + if (@available(iOS 14.0, *)) { if ([buttonView isKindOfClass:[UIButton class]]) { ((UIButton *)buttonView).menu = newValue; if (!self.items[itemIndex].primaryAction) { ((UIButton *)buttonView).showsMenuAsPrimaryAction = YES; } } - } else if ([keyPath isEqualToString:NSStringFromSelector(@selector(primaryAction))]) { + } + } else if ([keyPath isEqualToString:NSStringFromSelector(@selector(primaryAction))]) { + if (@available(iOS 14.0, *)) { // As of iOS 14.0 there is no public API to change the primary action of a button. // It's only possible to provide the action upon initialization of the view, so all // views get reloaded. [self reloadButtonViews]; - } else { - NSLog(@"Unknown key path notification received by %@ for %@.", - NSStringFromClass([self class]), keyPath); } } #endif +#if MDC_AVAILABLE_SDK_IOS(13_0) + else if ([keyPath isEqualToString:NSStringFromSelector(@selector(largeContentSizeImage))]) { + if (@available(iOS 13.0, *)) { + buttonView.largeContentImage = newValue; + } + } else if ([keyPath isEqualToString:NSStringFromSelector(@selector + (largeContentSizeImageInsets))]) { + if (@available(iOS 13.0, *)) { + buttonView.largeContentImageInsets = [newValue UIEdgeInsetsValue]; + } + } +#endif // MDC_AVAILABLE_SDK_IOS(13_0) else { NSLog(@"Unknown key path notification received by %@ for %@.", NSStringFromClass([self class]), keyPath); @@ -439,7 +458,9 @@ - (void)setItems:(NSArray *)items { NSStringFromSelector(@selector(accessibilityLabel)), NSStringFromSelector(@selector(accessibilityValue)), kEnabledSelector, NSStringFromSelector(@selector(image)), NSStringFromSelector(@selector(tag)), - NSStringFromSelector(@selector(tintColor)), NSStringFromSelector(@selector(title)) + NSStringFromSelector(@selector(tintColor)), NSStringFromSelector(@selector(title)), + NSStringFromSelector(@selector(largeContentSizeImage)), + NSStringFromSelector(@selector(largeContentSizeImageInsets)) ]; #if MDC_AVAILABLE_SDK_IOS(14_0) if (@available(iOS 14.0, *)) { @@ -613,4 +634,37 @@ - (UIPointerStyle *)pointerInteraction:(UIPointerInteraction *)interaction } #endif +#pragma mark - UILargeContentViewerInteractionDelegate + +/** + Returns the item view at the given point. Nil if there is no view at the given point. + + point is assumed to be in the coordinate space of the button bar's bounds. + */ +- (UIView *)buttonItemForPoint:(CGPoint)point { + for (NSUInteger i = 0; i < self.items.count; i++) { + UIBarButtonItem *barButtonItem = self.items[i]; + UIView *buttonView = _buttonViews[i]; + CGRect rect = [self rectForItem:barButtonItem inCoordinateSpace:self]; + if (CGRectContainsPoint(rect, point)) { + return buttonView; + } + } + return nil; +} + +#if MDC_AVAILABLE_SDK_IOS(13_0) +- (id)largeContentViewerInteraction: + (UILargeContentViewerInteraction *)interaction + itemAtPoint:(CGPoint)point + NS_AVAILABLE_IOS(13_0) { + if (!CGRectContainsPoint(self.bounds, point)) { + // The touch has wandered outside of the view. Do not display the content viewer. + return nil; + } + + return [self buttonItemForPoint:point]; +} +#endif // MDC_AVAILABLE_SDK_IOS(13_0) + @end diff --git a/components/ButtonBar/src/MDCButtonBarButton.h b/components/ButtonBar/src/MDCButtonBarButton.h index 9cc2be16a4a..860d2ea021f 100644 --- a/components/ButtonBar/src/MDCButtonBarButton.h +++ b/components/ButtonBar/src/MDCButtonBarButton.h @@ -29,4 +29,18 @@ */ - (void)setTitleFont:(nullable UIFont *)font forState:(UIControlState)state UI_APPEARANCE_SELECTOR; +#pragma mark - UILargeContentViewerItem + +/** + The title to display in the large content viewer. If set to nil, this property will return + @c title. + */ +@property(nonatomic, copy, nullable) NSString *largeContentTitle NS_AVAILABLE_IOS(13_0); + +/** + The image to display in the large content viwer. If set to nil, the property will return + @c image . If set to nil (or not set) @c scalesLargeContentImage will return YES otherwise NO. + */ +@property(nonatomic, nullable) UIImage *largeContentImage NS_AVAILABLE_IOS(13_0); + @end diff --git a/components/ButtonBar/src/private/MDCAppBarButtonBarBuilder.h b/components/ButtonBar/src/private/MDCAppBarButtonBarBuilder.h index 47e41d87907..76a4f087849 100644 --- a/components/ButtonBar/src/private/MDCAppBarButtonBarBuilder.h +++ b/components/ButtonBar/src/private/MDCAppBarButtonBarBuilder.h @@ -80,4 +80,18 @@ */ - (void)updateTitleColorForButton:(UIButton *)button withItem:(UIBarButtonItem *)item; +#pragma mark - UILargeContentViewerItem + +/** + The title to display in the large content viewer. If set to nil, this property will return + @c title. + */ +@property(nonatomic, copy, nullable) NSString *largeContentTitle NS_AVAILABLE_IOS(13_0); + +/** + The image to display in the large content viwer. If set to nil, the property will return + @c image . If set to nil (or not set) @c scalesLargeContentImage will return YES otherwise NO. + */ +@property(nonatomic, nullable) UIImage *largeContentImage NS_AVAILABLE_IOS(13_0); + @end diff --git a/components/ButtonBar/src/private/MDCAppBarButtonBarBuilder.m b/components/ButtonBar/src/private/MDCAppBarButtonBarBuilder.m index 529261e392b..0e9ad6630e0 100644 --- a/components/ButtonBar/src/private/MDCAppBarButtonBarBuilder.m +++ b/components/ButtonBar/src/private/MDCAppBarButtonBarBuilder.m @@ -165,6 +165,13 @@ - (UIView *)buttonBar:(MDCButtonBar *)buttonBar } [self updateTitleColorForButton:button withItem:buttonItem]; +#if MDC_AVAILABLE_SDK_IOS(13_0) + if (@available(iOS 13.0, *)) { + button.largeContentImage = self.largeContentImage; + button.largeContentTitle = self.largeContentTitle; + } +#endif + [self updateButton:button withItem:buttonItem barMetrics:UIBarMetricsDefault]; #ifdef __IPHONE_13_4 @@ -304,6 +311,12 @@ + (void)configureButton:(MDCButton *)destinationButton } destinationButton.tag = sourceButtonItem.tag; + +#if MDC_AVAILABLE_SDK_IOS(13_0) + if (@available(iOS 13.0, *)) { + destinationButton.largeContentImageInsets = sourceButtonItem.largeContentSizeImageInsets; + } +#endif } - (void)updateButton:(UIButton *)button diff --git a/components/ButtonBar/src/private/MDCButtonBarButton.m b/components/ButtonBar/src/private/MDCButtonBarButton.m index fb9495b4e8d..12500b02139 100644 --- a/components/ButtonBar/src/private/MDCButtonBarButton.m +++ b/components/ButtonBar/src/private/MDCButtonBarButton.m @@ -57,4 +57,30 @@ - (void)setTitleFont:(nullable UIFont *)font forState:(UIControlState)state { [super setTitleFont:font forState:state]; } +#pragma mark - UILargeContentViewerItem + +- (BOOL)showsLargeContentViewer { + return YES; +} + +- (NSString *)largeContentTitle { + if (_largeContentTitle) { + return _largeContentTitle; + } + + return [self titleForState:UIControlStateNormal]; +} + +- (UIImage *)largeContentImage { + if (_largeContentImage) { + return _largeContentImage; + } + + return [self imageForState:UIControlStateNormal]; +} + +- (BOOL)scalesLargeContentImage { + return _largeContentImage == nil; +} + @end diff --git a/components/ButtonBar/tests/unit/MDCButtonBarTests.m b/components/ButtonBar/tests/unit/MDCButtonBarTests.m index 17a6983efed..5bb8489dd0e 100644 --- a/components/ButtonBar/tests/unit/MDCButtonBarTests.m +++ b/components/ButtonBar/tests/unit/MDCButtonBarTests.m @@ -14,8 +14,13 @@ #import +#import "MaterialAvailability.h" #import "MaterialButtonBar.h" +@interface MDCButtonBar (Testing) +- (NSArray *)viewsForItems:(NSArray *)barButtonItems; +@end + /// Unit tests for MDCButtonBar. @interface MDCButtonBarTests : XCTestCase @@ -29,6 +34,7 @@ - (void)setUp { [super setUp]; self.buttonBar = [[MDCButtonBar alloc] init]; + self.buttonBar.uppercasesButtonTitles = NO; self.buttonBar.items = @[ [[UIBarButtonItem alloc] initWithTitle:@"Test" style:UIBarButtonItemStylePlain target:nil @@ -64,4 +70,181 @@ - (void)testTraitCollectionDidChangeBlockCalledWithExpectedParameters { XCTAssertEqual(passedButtonBar, self.buttonBar); } +#pragma mark - UILargeContentViewerItem + +#if MDC_AVAILABLE_SDK_IOS(13_0) + +- (void)testLargeContentTitleEqualsToTitle { + if (@available(iOS 13.0, *)) { + // Given + NSArray *itemViews = [self.buttonBar viewsForItems:self.buttonBar.items]; + + // When + NSString *largeContentTitle = itemViews.firstObject.largeContentTitle; + + // Then + XCTAssertEqualObjects(largeContentTitle, self.buttonBar.items.firstObject.title); + } +} + +/** + Tests the large content image is the @c image property when no @c largeContentImage is + specified. + */ +- (void)testLargeContentImageEqualsToDefaultImage { + if (@available(iOS 13.0, *)) { + // Given + NSArray *itemViews = [self.buttonBar viewsForItems:self.buttonBar.items]; + + // When + UIImage *largeContentImage = itemViews.firstObject.largeContentImage; + + // Then + XCTAssertEqualObjects(largeContentImage, self.buttonBar.items.firstObject.image); + } +} + +- (void)testSettingLargeContentImageOnBarButtonSetsItOnButtonBarView { + if (@available(iOS 13.0, *)) { + // Given + UIImage *image = [[UIImage alloc] init]; + UIBarButtonItem *item = [[UIBarButtonItem alloc] initWithImage:image + style:UIBarButtonItemStylePlain + target:nil + action:nil]; + item.largeContentSizeImage = image; + self.buttonBar.items = @[ item ]; + + // When + NSArray *itemViews = [self.buttonBar viewsForItems:self.buttonBar.items]; + UIImage *largeContentImage = itemViews.firstObject.largeContentImage; + + // Then + XCTAssertEqualObjects(largeContentImage, image); + } +} + +- (void)testSettingLargeContentSizeImageInsetsOnBarButtonSetsItOnButtonBarView { + if (@available(iOS 13.0, *)) { + // Given + UIEdgeInsets insets = UIEdgeInsetsMake(10, 10, 10, 10); + UIBarButtonItem *item = [[UIBarButtonItem alloc] initWithTitle:@"Title" + style:UIBarButtonItemStylePlain + target:nil + action:nil]; + item.largeContentSizeImageInsets = insets; + self.buttonBar.items = @[ item ]; + + // When + NSArray *itemViews = [self.buttonBar viewsForItems:self.buttonBar.items]; + UIEdgeInsets largeImageInsets = itemViews.firstObject.largeContentImageInsets; + + // Then + XCTAssertEqual(largeImageInsets.bottom, insets.bottom); + XCTAssertEqual(largeImageInsets.top, insets.top); + XCTAssertEqual(largeImageInsets.left, insets.left); + XCTAssertEqual(largeImageInsets.right, insets.right); + } +} + +- (void)testLargeContentImageUpdatesWhenButtonBarPropertyUpdates { + if (@available(iOS 13.0, *)) { + // Given + UIImage *image = [[UIImage alloc] init]; + UIBarButtonItem *item = [[UIBarButtonItem alloc] initWithImage:image + style:UIBarButtonItemStylePlain + target:nil + action:nil]; + self.buttonBar.items = @[ item ]; + + // When + item.largeContentSizeImage = image; + NSArray *itemViews = [self.buttonBar viewsForItems:self.buttonBar.items]; + UIImage *largeContentImage = itemViews.firstObject.largeContentImage; + + // Then + XCTAssertEqualObjects(largeContentImage, image); + } +} + +- (void)testLargeContentInsetUpdatesWhenButtonBarPropertyUpdates { + if (@available(iOS 13.0, *)) { + // Given + UIEdgeInsets insets = UIEdgeInsetsMake(10, 10, 10, 10); + UIBarButtonItem *item = [[UIBarButtonItem alloc] initWithTitle:@"Title" + style:UIBarButtonItemStylePlain + target:nil + action:nil]; + self.buttonBar.items = @[ item ]; + + // When + item.largeContentSizeImageInsets = insets; + NSArray *itemViews = [self.buttonBar viewsForItems:self.buttonBar.items]; + UIEdgeInsets largeImageInsets = itemViews.firstObject.largeContentImageInsets; + + // Then + XCTAssertEqual(largeImageInsets.bottom, insets.bottom); + XCTAssertEqual(largeImageInsets.top, insets.top); + XCTAssertEqual(largeImageInsets.left, insets.left); + XCTAssertEqual(largeImageInsets.right, insets.right); + } +} + +- (void)testLargeContentViewerInteractionWhenItemIsSelectedThenDeselectedButStillInNavBarBounds { + if (@available(iOS 13.0, *)) { + // Given + NSString *title1 = @"Title1"; + UIBarButtonItem *item1 = [[UIBarButtonItem alloc] initWithTitle:title1 + style:UIBarButtonItemStylePlain + target:nil + action:nil]; + self.buttonBar.items = @[ item1 ]; + self.buttonBar.frame = CGRectMake(0, 0, 350, 125); + [self.buttonBar layoutIfNeeded]; + UILargeContentViewerInteraction *interaction = [[UILargeContentViewerInteraction alloc] init]; + self.continueAfterFailure = NO; + + // When/Then + XCTAssertTrue([self.buttonBar respondsToSelector:@selector(largeContentViewerInteraction: + itemAtPoint:)]); + NSArray *itemViews = [self.buttonBar viewsForItems:self.buttonBar.items]; + CGPoint itemViewOrigin = itemViews.firstObject.frame.origin; + id largeContentItem = + [self.buttonBar largeContentViewerInteraction:interaction itemAtPoint:itemViewOrigin]; + XCTAssertEqualObjects(largeContentItem.largeContentTitle, title1); + + largeContentItem = [self.buttonBar largeContentViewerInteraction:interaction + itemAtPoint:CGPointZero]; + XCTAssertEqualObjects(largeContentItem.largeContentTitle, title1); + } +} + +/** + Tests the large content item is nil when the touch point is outside the navigation bar bounds. + */ +- (void)testLargeContentViewerInteractionWhenPointIsOutSideNavBarBounds { + if (@available(iOS 13.0, *)) { + // Given + UIBarButtonItem *item = [[UIBarButtonItem alloc] initWithTitle:@"Title" + style:UIBarButtonItemStylePlain + target:nil + action:nil]; + self.buttonBar.items = @[ item ]; + self.buttonBar.frame = CGRectMake(0, 0, 350, 125); + [self.buttonBar layoutIfNeeded]; + UILargeContentViewerInteraction *interaction = [[UILargeContentViewerInteraction alloc] init]; + self.continueAfterFailure = NO; + + // When/Then + XCTAssertTrue([self.buttonBar respondsToSelector:@selector(largeContentViewerInteraction: + itemAtPoint:)]); + CGPoint pointOutsideNavBar = CGPointMake(-1, -1); + + id largeContentItem = + [self.buttonBar largeContentViewerInteraction:interaction itemAtPoint:pointOutsideNavBar]; + XCTAssertNil(largeContentItem); + } +} +#endif // MDC_AVAILABLE_SDK_IOS(13_0) + @end From 3022102dbfd9833f325fbebe9ff7bceccb0c5778 Mon Sep 17 00:00:00 2001 From: Jeff Verkoeyen Date: Fri, 14 Aug 2020 10:09:52 -0700 Subject: [PATCH 10/20] [TabBarView] Animate text and icon colors when switching tabs. The text and icon colors are expected to animate alongside the transition between selected tabs. This change moves the icon + font changes into the animation block, and adds a separate transition animation for UILabel's textColor because textColor is not implicitly animatable. PiperOrigin-RevId: 326677877 --- .../Tabs/src/TabBarView/MDCTabBarView.m | 33 ++++++++++++------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/components/Tabs/src/TabBarView/MDCTabBarView.m b/components/Tabs/src/TabBarView/MDCTabBarView.m index 309cc4171e0..d5ed67a8050 100644 --- a/components/Tabs/src/TabBarView/MDCTabBarView.m +++ b/components/Tabs/src/TabBarView/MDCTabBarView.m @@ -296,9 +296,7 @@ - (void)setSelectedItem:(UITabBarItem *)selectedItem animated:(BOOL)animated { // Handle setting to `nil` without passing it to the nonnull parameter in `indexOfObject:` if (!selectedItem) { _selectedItem = selectedItem; - [self updateTitleColorForAllViews]; - [self updateImageTintColorForAllViews]; - [self updateTitleFontForAllViews]; + [self updateTitleColorForAllViewsAnimated:animated]; [self didSelectItemAtIndex:NSNotFound animateTransition:animated]; return; } @@ -319,9 +317,7 @@ - (void)setSelectedItem:(UITabBarItem *)selectedItem animated:(BOOL)animated { (UIView *)newSelectedItemView; [customViewableView setSelected:YES animated:animated]; } - [self updateTitleColorForAllViews]; - [self updateImageTintColorForAllViews]; - [self updateTitleFontForAllViews]; + [self updateTitleColorForAllViewsAnimated:animated]; [self scrollToItem:self.items[itemIndex] animated:animated]; [self didSelectItemAtIndex:itemIndex animateTransition:animated]; } @@ -363,7 +359,7 @@ - (UIColor *)imageTintColorForState:(UIControlState)state { return color; } -- (void)updateTitleColorForAllViews { +- (void)updateTitleColorForAllViewsAnimated:(BOOL)animated { for (UITabBarItem *item in self.items) { NSUInteger indexOfItem = [self.items indexOfObject:item]; // This is a significant error, but defensive coding is preferred. @@ -377,17 +373,30 @@ - (void)updateTitleColorForAllViews { continue; } MDCTabBarViewItemView *tabBarViewItemView = (MDCTabBarViewItemView *)itemView; - if (item == self.selectedItem) { - tabBarViewItemView.titleLabel.textColor = [self titleColorForState:UIControlStateSelected]; + void (^animations)(void) = ^{ + if (item == self.selectedItem) { + tabBarViewItemView.titleLabel.textColor = [self titleColorForState:UIControlStateSelected]; + } else { + tabBarViewItemView.titleLabel.textColor = [self titleColorForState:UIControlStateNormal]; + } + }; + if (animated) { + // UILabel::textColor can't be implicitly animated, so we use a cross-fade dissolve transition + // on the label to accomplish the effect instead. + [UIView transitionWithView:tabBarViewItemView.titleLabel + duration:self.selectionChangeAnimationDuration + options:UIViewAnimationOptionTransitionCrossDissolve + animations:animations + completion:nil]; } else { - tabBarViewItemView.titleLabel.textColor = [self titleColorForState:UIControlStateNormal]; + animations(); } } } - (void)setTitleColor:(UIColor *)titleColor forState:(UIControlState)state { self.stateToTitleColor[@(state)] = titleColor; - [self updateTitleColorForAllViews]; + [self updateTitleColorForAllViewsAnimated:NO]; } - (UIColor *)titleColorForState:(UIControlState)state { @@ -1227,6 +1236,8 @@ - (void)updateSelectionIndicatorToIndex:(NSUInteger)index { */ - (void)didSelectItemAtIndex:(NSUInteger)index animateTransition:(BOOL)animate { void (^animationBlock)(void) = ^{ + [self updateImageTintColorForAllViews]; + [self updateTitleFontForAllViews]; [self updateSelectionIndicatorToIndex:index]; // Force layout so any changes to the selection indicator are captured by the animation block. From b22eef526fcd0ecb164f5eab0d8e42a36fc313de Mon Sep 17 00:00:00 2001 From: Andrew Overton Date: Fri, 14 Aug 2020 11:45:45 -0700 Subject: [PATCH 11/20] [BottomNavigation] Move MDCBottomNavigationBarController to main podspec PiperOrigin-RevId: 326698570 --- MaterialComponents.podspec | 21 +++++++++- MaterialComponentsBeta.podspec | 41 ------------------- MaterialComponentsExamples.podspec | 1 - MaterialComponentsSnapshotTests.podspec | 1 - catalog/MDCCatalog.xcodeproj/project.pbxproj | 2 - catalog/MDCDragons.xcodeproj/project.pbxproj | 2 - catalog/Podfile | 8 +--- ...onBarControllerExampleViewController.swift | 29 ++++++++----- ...omNavigation+BottomNavigationController.h} | 0 ...ttomNavigationBarControllerDelegateTests.m | 2 +- .../MDCBottomNavigationBarControllerTests.m | 2 +- .../private/Beta/src/MaterialBetaComponent.h | 16 -------- 12 files changed, 42 insertions(+), 83 deletions(-) delete mode 100644 MaterialComponentsBeta.podspec rename components/BottomNavigation/src/{MaterialBottomNavigationBeta.h => MaterialBottomNavigation+BottomNavigationController.h} (100%) delete mode 100644 components/private/Beta/src/MaterialBetaComponent.h diff --git a/MaterialComponents.podspec b/MaterialComponents.podspec index 34ef7b318a7..351075fe933 100644 --- a/MaterialComponents.podspec +++ b/MaterialComponents.podspec @@ -311,7 +311,7 @@ Pod::Spec.new do |mdc| ] component.exclude_files = [ "components/#{component.base_name}/src/MDCBottomNavigationBarController.*", - "components/#{component.base_name}/src/MaterialBottomNavigationBeta.h" + "components/#{component.base_name}/src/MaterialBottomNavigation+BottomNavigationController.h" ] component.resources = [ "components/#{component.base_name}/src/Material#{component.base_name}.bundle" @@ -341,6 +341,25 @@ Pod::Spec.new do |mdc| end end + mdc.subspec "BottomNavigation+BottomNavigationController" do |component| + component.ios.deployment_target = '10.0' + component.public_header_files = [ + "components/#{component.base_name.split('+')[0]}/src/MDCBottomNavigationBarController.h", + "components/#{component.base_name.split('+')[0]}/src/MaterialBottomNavigation+BottomNavigationController.h", + ] + component.source_files = [ + "components/#{component.base_name.split('+')[0]}/src/MDCBottomNavigationBarController.*", + "components/#{component.base_name.split('+')[0]}/src/MaterialBottomNavigation+BottomNavigationController.h", + ] + component.dependency "MaterialComponents/BottomNavigation" + component.test_spec 'UnitTests' do |unit_tests| + unit_tests.source_files = [ + "components/#{component.base_name.split('+')[0]}/tests/unit/MDCBottomNavigationBarControllerTests.m", + "components/#{component.base_name.split('+')[0]}/tests/unit/MDCBottomNavigationBarControllerDelegateTests.m" + ] + end + end + mdc.subspec "BottomNavigation+Theming" do |extension| extension.ios.deployment_target = '10.0' extension.public_header_files = "components/#{extension.base_name.split('+')[0]}/src/#{extension.base_name.split('+')[1]}/*.h" diff --git a/MaterialComponentsBeta.podspec b/MaterialComponentsBeta.podspec deleted file mode 100644 index 3204166f076..00000000000 --- a/MaterialComponentsBeta.podspec +++ /dev/null @@ -1,41 +0,0 @@ -Pod::Spec.new do |mdc| - mdc.name = "MaterialComponentsBeta" - mdc.version = "113.0.0" - mdc.authors = "The Material Components authors." - mdc.summary = "A collection of stand-alone alpha UI libraries that are not yet guaranteed to be ready for general production use. Use with caution." - mdc.homepage = "https://github.com/material-components/material-components-ios" - mdc.license = "Apache 2.0" - mdc.source = { :git => "https://github.com/material-components/material-components-ios.git", :tag => "v#{mdc.version}" } - mdc.platform = :ios - mdc.requires_arc = true - mdc.ios.deployment_target = '10.0' - - # See MaterialComponents.podspec for the subspec structure and template. - - mdc.subspec "BottomNavigation" do |component| - component.ios.deployment_target = '10.0' - component.public_header_files = "components/#{component.base_name}/src/MDCBottomNavigationBarController.h", "components/#{component.base_name}/src/MaterialBottomNavigationBeta.h" - component.source_files = "components/#{component.base_name}/src/MDCBottomNavigationBarController.*", "components/#{component.base_name}/src/MaterialBottomNavigationBeta.h" - component.dependency "MaterialComponents/BottomNavigation" - - component.test_spec 'UnitTests' do |unit_tests| - unit_tests.source_files = [ - "components/#{component.base_name}/tests/unit/MDCBottomNavigationBarControllerTests.m", - "components/#{component.base_name}/tests/unit/MDCBottomNavigationBarControllerDelegateTests.m" - ] - end - end - - # Private - - mdc.subspec "private" do |private_spec| - # CocoaPods requires at least one file to show up in a subspec, so we depend on the fake - # "Beta" component as a baseline. - private_spec.subspec "Beta" do |component| - component.ios.deployment_target = '10.0' - component.public_header_files = "components/private/#{component.base_name}/src/*.h" - component.source_files = "components/private/#{component.base_name}/src/*.{h,m}" - end - end - -end diff --git a/MaterialComponentsExamples.podspec b/MaterialComponentsExamples.podspec index 951ebb6aab0..e61cf222ed5 100644 --- a/MaterialComponentsExamples.podspec +++ b/MaterialComponentsExamples.podspec @@ -13,6 +13,5 @@ Pod::Spec.new do |s| s.resources = ['components/*/examples/resources/*', 'components/private/*/examples/resources/*', 'components/schemes/*/examples/resources/*'] s.dependency 'MaterialComponents' - s.dependency 'MaterialComponentsBeta' s.public_header_files = ['components/*/examples/*.h', 'components/*/examples/supplemental/*.h', 'components/private/*/examples/*.h', 'components/schemes/*/examples/*.h'] end diff --git a/MaterialComponentsSnapshotTests.podspec b/MaterialComponentsSnapshotTests.podspec index 24b9677e019..31804a189c1 100644 --- a/MaterialComponentsSnapshotTests.podspec +++ b/MaterialComponentsSnapshotTests.podspec @@ -62,7 +62,6 @@ Pod::Spec.new do |s| s.platform = :ios, '10.0' s.requires_arc = true s.dependency 'MaterialComponents' - s.dependency 'MaterialComponentsBeta' s.dependency 'MaterialComponentsExamples' # Top level sources are required. Without them, unit test targets do not show up in Xcode. diff --git a/catalog/MDCCatalog.xcodeproj/project.pbxproj b/catalog/MDCCatalog.xcodeproj/project.pbxproj index 0fc181c258f..61bcdeea207 100644 --- a/catalog/MDCCatalog.xcodeproj/project.pbxproj +++ b/catalog/MDCCatalog.xcodeproj/project.pbxproj @@ -420,7 +420,6 @@ "${BUILT_PRODUCTS_DIR}/MDFTextAccessibility/MDFTextAccessibility.framework", "${BUILT_PRODUCTS_DIR}/MaterialCatalog/MaterialCatalog.framework", "${BUILT_PRODUCTS_DIR}/MaterialComponents/MaterialComponents.framework", - "${BUILT_PRODUCTS_DIR}/MaterialComponentsBeta/MaterialComponentsBeta.framework", "${BUILT_PRODUCTS_DIR}/MaterialComponentsExamples/MaterialComponentsExamples.framework", "${BUILT_PRODUCTS_DIR}/MaterialComponentsSnapshotTests/MaterialComponentsSnapshotTests.framework", "${BUILT_PRODUCTS_DIR}/MotionAnimator/MotionAnimator.framework", @@ -433,7 +432,6 @@ "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MDFTextAccessibility.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MaterialCatalog.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MaterialComponents.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MaterialComponentsBeta.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MaterialComponentsExamples.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MaterialComponentsSnapshotTests.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MotionAnimator.framework", diff --git a/catalog/MDCDragons.xcodeproj/project.pbxproj b/catalog/MDCDragons.xcodeproj/project.pbxproj index ef88c19a674..9eae5f973d0 100644 --- a/catalog/MDCDragons.xcodeproj/project.pbxproj +++ b/catalog/MDCDragons.xcodeproj/project.pbxproj @@ -206,7 +206,6 @@ "${BUILT_PRODUCTS_DIR}/MDFInternationalization/MDFInternationalization.framework", "${BUILT_PRODUCTS_DIR}/MDFTextAccessibility/MDFTextAccessibility.framework", "${BUILT_PRODUCTS_DIR}/MaterialComponents/MaterialComponents.framework", - "${BUILT_PRODUCTS_DIR}/MaterialComponentsBeta/MaterialComponentsBeta.framework", "${BUILT_PRODUCTS_DIR}/MaterialComponentsExamples/MaterialComponentsExamples.framework", "${BUILT_PRODUCTS_DIR}/MotionAnimator/MotionAnimator.framework", "${BUILT_PRODUCTS_DIR}/MotionInterchange/MotionInterchange.framework", @@ -217,7 +216,6 @@ "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MDFInternationalization.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MDFTextAccessibility.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MaterialComponents.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MaterialComponentsBeta.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MaterialComponentsExamples.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MotionAnimator.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MotionInterchange.framework", diff --git a/catalog/Podfile b/catalog/Podfile index 669978a7d2d..7684f213ab0 100644 --- a/catalog/Podfile +++ b/catalog/Podfile @@ -19,6 +19,7 @@ target "MDCCatalog" do 'Banner+Theming/UnitTests', 'BottomAppBar/UnitTests', 'BottomNavigation/UnitTests', + 'BottomNavigation+BottomNavigationController/UnitTests', 'BottomNavigation+Theming/UnitTests', 'BottomSheet/UnitTests', 'ButtonBar/UnitTests', @@ -86,9 +87,6 @@ target "MDCCatalog" do 'Themes/UnitTests', 'Typography/UnitTests', ] - pod 'MaterialComponentsBeta', :path => '../', :testspecs => [ - 'BottomNavigation/UnitTests', - ] pod 'MaterialComponentsSnapshotTests', :path => '../', :testspecs => [ 'SnapshotTests' ] @@ -103,7 +101,6 @@ target "MDCActionExtension" do project 'MDCCatalog.xcodeproj' pod 'MaterialComponentsExamples', :path => '../' pod 'MaterialComponents', :path => '../' - pod 'MaterialComponentsBeta', :path => '../' pod 'CatalogByConvention', "~> 2.5" pod 'MaterialCatalog', :path => 'MaterialCatalog/' @@ -115,7 +112,6 @@ target "MDCDragons" do project 'MDCDragons.xcodeproj' pod 'CatalogByConvention', "~> 2.5" pod 'MaterialComponents', :path => '../' - pod 'MaterialComponentsBeta', :path => '../' pod 'MaterialComponentsExamples', :path => '../' use_frameworks! end @@ -124,7 +120,7 @@ post_install do |installer| mdc_xcconfigs = [] target_support_files_path = File.dirname(installer.pods_project.path) + "/" + "Target Support Files" - subdirectories = ["MaterialComponents", "MaterialComponentsBeta", "MaterialComponentsExamples", "Pods-MDCCatalog"] + subdirectories = ["MaterialComponents", "MaterialComponentsExamples", "Pods-MDCCatalog"] subdirectories.each do |subdirectory| subdirectory_path = target_support_files_path + "/" + subdirectory Dir.foreach(subdirectory_path) do |file| diff --git a/components/BottomNavigation/examples/BottomNavigationBarControllerExampleViewController.swift b/components/BottomNavigation/examples/BottomNavigationBarControllerExampleViewController.swift index 45b4819bdda..2a65b04c7bc 100644 --- a/components/BottomNavigation/examples/BottomNavigationBarControllerExampleViewController.swift +++ b/components/BottomNavigation/examples/BottomNavigationBarControllerExampleViewController.swift @@ -13,10 +13,9 @@ // limitations under the License. import UIKit - -import MaterialComponentsBeta.MaterialBottomNavigationBeta import MaterialComponents.MaterialBottomNavigation -import MaterialComponents.MaterialBottomNavigation_Theming +import MaterialComponents.MaterialBottomNavigation_BottomNavigationController +import MaterialComponents.MaterialBottomNavigation_Theming import MaterialComponents.MaterialContainerScheme class BottomNavigationControllerExampleFixedChildViewController: UIViewController { @@ -62,11 +61,15 @@ class BottomNavigationControllerExampleScrollableChildViewController: UICollecti return 1 } - override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { + override func collectionView( + _ collectionView: UICollectionView, numberOfItemsInSection section: Int + ) -> Int { return numberOfItems } - override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { + override func collectionView( + _ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath + ) -> UICollectionViewCell { let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) let hue = CGFloat(indexPath.row) / CGFloat(numberOfItems) cell.backgroundColor = UIColor(hue: hue, saturation: 1, brightness: 1, alpha: 1) @@ -101,25 +104,29 @@ class BottomNavigationControllerExampleViewController: MDCBottomNavigationBarCon let flowLayout = UICollectionViewFlowLayout() flowLayout.estimatedItemSize = CGSize(width: 96, height: 48) - let viewController1 = BottomNavigationControllerExampleScrollableChildViewController(collectionViewLayout: flowLayout) + let viewController1 = BottomNavigationControllerExampleScrollableChildViewController( + collectionViewLayout: flowLayout) viewController1.collectionView.backgroundColor = containerScheme.colorScheme.primaryColorVariant - viewController1.tabBarItem = UITabBarItem(title: "Item 1", image: UIImage(named: "ic_home"), tag: 0) + viewController1.tabBarItem = UITabBarItem( + title: "Item 1", image: UIImage(named: "ic_home"), tag: 0) let viewController2 = BottomNavigationControllerExampleFixedChildViewController() viewController2.containerScheme = containerScheme - viewController2.tabBarItem = UITabBarItem(title: "Item 2", image: UIImage(named: "ic_favorite"), tag: 1) + viewController2.tabBarItem = UITabBarItem( + title: "Item 2", image: UIImage(named: "ic_favorite"), tag: 1) let viewController3 = UIViewController() viewController3.view.backgroundColor = containerScheme.colorScheme.surfaceColor - viewController3.tabBarItem = UITabBarItem(title: "Item 3", image: UIImage(named: "ic_search"), tag: 2) + viewController3.tabBarItem = UITabBarItem( + title: "Item 3", image: UIImage(named: "ic_search"), tag: 2) - viewControllers = [ viewController1, viewController2, viewController3 ] + viewControllers = [viewController1, viewController2, viewController3] } @objc class func catalogMetadata() -> [String: Any] { return [ "breadcrumbs": ["Bottom Navigation", "Bottom Navigation Controller"], - "presentable": false + "presentable": false, ] } } diff --git a/components/BottomNavigation/src/MaterialBottomNavigationBeta.h b/components/BottomNavigation/src/MaterialBottomNavigation+BottomNavigationController.h similarity index 100% rename from components/BottomNavigation/src/MaterialBottomNavigationBeta.h rename to components/BottomNavigation/src/MaterialBottomNavigation+BottomNavigationController.h diff --git a/components/BottomNavigation/tests/unit/MDCBottomNavigationBarControllerDelegateTests.m b/components/BottomNavigation/tests/unit/MDCBottomNavigationBarControllerDelegateTests.m index 0344fe58b1e..75f1cd8db7f 100644 --- a/components/BottomNavigation/tests/unit/MDCBottomNavigationBarControllerDelegateTests.m +++ b/components/BottomNavigation/tests/unit/MDCBottomNavigationBarControllerDelegateTests.m @@ -14,7 +14,7 @@ #import -#import "MaterialBottomNavigationBeta.h" +#import "MaterialBottomNavigation+BottomNavigationController.h" /** Delegate that implements no optional APIs. */ @interface MDCBottomNavigationBarControllerDelegateWithNoOptionals diff --git a/components/BottomNavigation/tests/unit/MDCBottomNavigationBarControllerTests.m b/components/BottomNavigation/tests/unit/MDCBottomNavigationBarControllerTests.m index 21dd7315547..d47503dc23c 100644 --- a/components/BottomNavigation/tests/unit/MDCBottomNavigationBarControllerTests.m +++ b/components/BottomNavigation/tests/unit/MDCBottomNavigationBarControllerTests.m @@ -15,7 +15,7 @@ #import #import -#import +#import "MaterialBottomNavigation+BottomNavigationController.h" static CGFloat const kDefaultExpectationTimeout = 15; diff --git a/components/private/Beta/src/MaterialBetaComponent.h b/components/private/Beta/src/MaterialBetaComponent.h deleted file mode 100644 index 9f963210c3b..00000000000 --- a/components/private/Beta/src/MaterialBetaComponent.h +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright 2018-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. - -// This header's sole purpose is to provide a single file for the MaterialComponentsBeta -// podspec to pick up, in the event that we have no components presently in an Alpha state. From 453eaa8a9a33e344276892ebb005ceaec205bab8 Mon Sep 17 00:00:00 2001 From: Jeff Verkoeyen Date: Fri, 14 Aug 2020 12:57:16 -0700 Subject: [PATCH 12/20] [BottomAppBar] Fix bug where FAB shadow would "bounce" when the position changed. There are two related fixes involved here: 1. The horizontal shift animations were using the default CABasicAnimation timing function, Linear. These have been adjusted to EaseInEaseOut to match the guidelines. 2. The shadow was doing a wonky "bounce" animation by always animating to an elevation of 1 followed by an animation to the target elevation. This bounce animation has been removed. Note that, while the bouncing has been removed, the shadow now does not animate at all. This is intentional for two reasons: 1. The shadow is expected to swap z-ordering with the bottom bar. 2. The animation is very subtle. The result of these two constraints is that if we animate the shadow to the secondary position, then we need to change the z-order at the end of the animation. This would result in the shadow being "clipped" by the bar as the shadow moves from the foreground to the background. This ends up being more visible than the animation itself, and more distracting than the lack of an animation. We could animate to the primary position, but at this point handling this logic would add complexity for a very subtle effect that doesn't necessarily improve the effect. PiperOrigin-RevId: 326713093 --- .../BottomAppBar/src/MDCBottomAppBarView.m | 30 ++++++++++--------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/components/BottomAppBar/src/MDCBottomAppBarView.m b/components/BottomAppBar/src/MDCBottomAppBarView.m index b178e66bc73..2e8d0cdf771 100644 --- a/components/BottomAppBar/src/MDCBottomAppBarView.m +++ b/components/BottomAppBar/src/MDCBottomAppBarView.m @@ -18,10 +18,13 @@ #import -#import "MaterialMath.h" -#import "MaterialNavigationBar.h" #import "private/MDCBottomAppBarAttributes.h" #import "private/MDCBottomAppBarLayer.h" +#import "MaterialButtons.h" +#import "MaterialElevation.h" +#import "MaterialNavigationBar.h" +#import "MaterialShadowElevations.h" +#import "MaterialMath.h" static NSString *kMDCBottomAppBarViewAnimKeyString = @"AnimKey"; static NSString *kMDCBottomAppBarViewPathString = @"path"; @@ -29,7 +32,6 @@ static const CGFloat kMDCBottomAppBarViewFloatingButtonCenterToNavigationBarTopOffset = 0; static const CGFloat kMDCBottomAppBarViewFloatingButtonElevationPrimary = 6; static const CGFloat kMDCBottomAppBarViewFloatingButtonElevationSecondary = 4; -static const int kMDCButtonAnimationDuration = 200; @interface MDCBottomAppBarCutView : UIView @@ -175,6 +177,8 @@ - (void)cutBottomAppBarViewAnimated:(BOOL)animated { if (animated) { CABasicAnimation *pathAnimation = [CABasicAnimation animationWithKeyPath:kMDCBottomAppBarViewPathString]; + pathAnimation.timingFunction = + [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]; pathAnimation.duration = kMDCFloatingButtonExitDuration; pathAnimation.fromValue = (id)self.bottomBarLayer.presentationLayer.path; pathAnimation.toValue = (__bridge id _Nullable)(pathWithCut); @@ -197,6 +201,8 @@ - (void)healBottomAppBarViewAnimated:(BOOL)animated { if (animated) { CABasicAnimation *pathAnimation = [CABasicAnimation animationWithKeyPath:kMDCBottomAppBarViewPathString]; + pathAnimation.timingFunction = + [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]; pathAnimation.duration = kMDCFloatingButtonEnterDuration; pathAnimation.fromValue = (id)self.bottomBarLayer.presentationLayer.path; pathAnimation.toValue = (__bridge id _Nullable)(pathWithoutCut); @@ -217,6 +223,8 @@ - (void)moveFloatingButtonCenterAnimated:(BOOL)animated { if (animated) { CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:kMDCBottomAppBarViewPositionString]; + animation.timingFunction = + [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]; animation.duration = kMDCFloatingButtonExitDuration; animation.fromValue = [NSValue valueWithCGPoint:self.floatingButton.center]; animation.toValue = [NSValue valueWithCGPoint:endPoint]; @@ -346,17 +354,11 @@ - (void)setFloatingButtonElevation:(MDCBottomAppBarFloatingButtonElevation)float elevation = kMDCBottomAppBarViewFloatingButtonElevationSecondary; subViewIndex = 0; } - if (animated) { - [_floatingButton setElevation:1 forState:UIControlStateNormal]; - dispatch_after(dispatch_time(DISPATCH_TIME_NOW, kMDCButtonAnimationDuration * NSEC_PER_MSEC), - dispatch_get_main_queue(), ^{ - [self insertSubview:self.floatingButton atIndex:subViewIndex]; - [self.floatingButton setElevation:elevation forState:UIControlStateNormal]; - }); - } else { - [self insertSubview:_floatingButton atIndex:subViewIndex]; - [_floatingButton setElevation:elevation forState:UIControlStateNormal]; - } + // Immediately move the button to the correct z-ordering so that the shadow clipping effect isn't + // as apparent. If we did this at the end of the animation, then the shadow would appear to + // suddenly clip at the end of the animation. + [self insertSubview:_floatingButton atIndex:subViewIndex]; + [_floatingButton setElevation:elevation forState:UIControlStateNormal]; } - (void)setFloatingButtonPosition:(MDCBottomAppBarFloatingButtonPosition)floatingButtonPosition { From ae3fc08aac3722feb31148011d52af7a7dc926f5 Mon Sep 17 00:00:00 2001 From: Yarden Eitan Date: Mon, 17 Aug 2020 07:05:21 -0700 Subject: [PATCH 13/20] [ButtonBar] Fall back to accessibilityLabel for UILargeContentView when there is an image and no title. PiperOrigin-RevId: 327013666 --- .../src/private/MDCButtonBarButton.m | 7 ++++++- .../ButtonBar/tests/unit/MDCButtonBarTests.m | 20 +++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/components/ButtonBar/src/private/MDCButtonBarButton.m b/components/ButtonBar/src/private/MDCButtonBarButton.m index 12500b02139..0fc029f7942 100644 --- a/components/ButtonBar/src/private/MDCButtonBarButton.m +++ b/components/ButtonBar/src/private/MDCButtonBarButton.m @@ -68,7 +68,12 @@ - (NSString *)largeContentTitle { return _largeContentTitle; } - return [self titleForState:UIControlStateNormal]; + NSString *title = [self titleForState:UIControlStateNormal]; + if (!title && self.largeContentImage) { + return self.accessibilityLabel; + } + + return title; } - (UIImage *)largeContentImage { diff --git a/components/ButtonBar/tests/unit/MDCButtonBarTests.m b/components/ButtonBar/tests/unit/MDCButtonBarTests.m index 5bb8489dd0e..94b48239fe6 100644 --- a/components/ButtonBar/tests/unit/MDCButtonBarTests.m +++ b/components/ButtonBar/tests/unit/MDCButtonBarTests.m @@ -124,6 +124,26 @@ - (void)testSettingLargeContentImageOnBarButtonSetsItOnButtonBarView { } } +- (void)testSettingImageWihNoTitleFallbacksToAccessibilityLabel { + if (@available(iOS 13.0, *)) { + // Given + UIImage *image = [[UIImage alloc] init]; + UIBarButtonItem *item = [[UIBarButtonItem alloc] initWithImage:image + style:UIBarButtonItemStylePlain + target:nil + action:nil]; + item.accessibilityLabel = @"foo"; + self.buttonBar.items = @[ item ]; + + // When + NSArray *itemViews = [self.buttonBar viewsForItems:self.buttonBar.items]; + NSString *largeContentTitle = itemViews.firstObject.largeContentTitle; + + // Then + XCTAssertEqualObjects(largeContentTitle, item.accessibilityLabel); + } +} + - (void)testSettingLargeContentSizeImageInsetsOnBarButtonSetsItOnButtonBarView { if (@available(iOS 13.0, *)) { // Given From b928b4d5c8e854288450be0938b8bfc3927ad46e Mon Sep 17 00:00:00 2001 From: Jeff Verkoeyen Date: Mon, 17 Aug 2020 07:09:21 -0700 Subject: [PATCH 14/20] [Snackbar] Deprecate remaining class methods on MDCSnackbarManager. PiperOrigin-RevId: 327014172 --- components/Snackbar/src/MDCSnackbarManager.h | 38 ++++++++++---------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/components/Snackbar/src/MDCSnackbarManager.h b/components/Snackbar/src/MDCSnackbarManager.h index 4bdfd8bf051..151854dc587 100644 --- a/components/Snackbar/src/MDCSnackbarManager.h +++ b/components/Snackbar/src/MDCSnackbarManager.h @@ -293,25 +293,6 @@ @protocol MDCSnackbarSuspensionToken @end -@interface MDCSnackbarManager (ToBeDeprecated) - -/** - Calls @c -showMessage: on the @c defaultManager instance. - */ -+ (void)showMessage:(nullable MDCSnackbarMessage *)message; - -/** - Calls @c -dismissAndCallCompletionBlocksWithCategory: on the @c defaultManager instance. - */ -+ (void)dismissAndCallCompletionBlocksWithCategory:(nullable NSString *)category; - -/** - Bound to @c delegate on the @c defaultManager instance. - */ -@property(class, nonatomic, weak, nullable) id delegate; - -@end - @interface MDCSnackbarManager (Deprecated) /** @@ -366,6 +347,12 @@ @property(class, nonatomic, assign) BOOL shouldApplyStyleChangesToVisibleSnackbars __deprecated_msg( "Use MDCSnackbarManager.defaultManager.shouldApplyStyleChangesToVisibleSnackbars instead."); +/** + Bound to @c delegate on the @c defaultManager instance. + */ +@property(class, nonatomic, weak, nullable) id delegate + __deprecated_msg("Use MDCSnackbarManager.defaultManager.delegate instead."); + /** Calls @c -buttonTitleColorForState: on the @c defaultManager instance. */ @@ -417,4 +404,17 @@ + (void)setPresentationHostView:(nullable UIView *)hostView __deprecated_msg("Use [MDCSnackbarManager.defaultManager setPresentationHostView:] instead."); +/** + Calls @c -dismissAndCallCompletionBlocksWithCategory: on the @c defaultManager instance. + */ ++ (void)dismissAndCallCompletionBlocksWithCategory:(nullable NSString *)category + __deprecated_msg("Use [MDCSnackbarManager.defaultManager " + "dismissAndCallCompletionBlocksWithCategory:] instead."); + +/** + Calls @c -showMessage: on the @c defaultManager instance. + */ ++ (void)showMessage:(nullable MDCSnackbarMessage *)message + __deprecated_msg("Use [MDCSnackbarManager.defaultManager showMessage:] instead."); + @end From 4511a9f5e4d2909dbc0c84b65af45a7e0d7e03f2 Mon Sep 17 00:00:00 2001 From: Nobody Date: Mon, 17 Aug 2020 07:32:09 -0700 Subject: [PATCH 15/20] [ButtonBar] Removes nullability modifiers from file. PiperOrigin-RevId: 327016891 --- components/ButtonBar/src/private/MDCAppBarButtonBarBuilder.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/components/ButtonBar/src/private/MDCAppBarButtonBarBuilder.h b/components/ButtonBar/src/private/MDCAppBarButtonBarBuilder.h index 76a4f087849..7f1d70d1db6 100644 --- a/components/ButtonBar/src/private/MDCAppBarButtonBarBuilder.h +++ b/components/ButtonBar/src/private/MDCAppBarButtonBarBuilder.h @@ -86,12 +86,12 @@ The title to display in the large content viewer. If set to nil, this property will return @c title. */ -@property(nonatomic, copy, nullable) NSString *largeContentTitle NS_AVAILABLE_IOS(13_0); +@property(nonatomic, copy) NSString *largeContentTitle NS_AVAILABLE_IOS(13_0); /** The image to display in the large content viwer. If set to nil, the property will return @c image . If set to nil (or not set) @c scalesLargeContentImage will return YES otherwise NO. */ -@property(nonatomic, nullable) UIImage *largeContentImage NS_AVAILABLE_IOS(13_0); +@property(nonatomic) UIImage *largeContentImage NS_AVAILABLE_IOS(13_0); @end From eb13147f839302bd0b0b42fcb74f90cf94fe72f1 Mon Sep 17 00:00:00 2001 From: Yarden Eitan Date: Mon, 17 Aug 2020 11:04:45 -0700 Subject: [PATCH 16/20] [TabBarView] Adds Large Content Viewer support to MDCTabBarView. PiperOrigin-RevId: 327056620 --- .../Tabs/src/TabBarView/MDCTabBarView.h | 20 +++ .../Tabs/src/TabBarView/MDCTabBarView.m | 141 ++++++++++++++- .../private/MDCTabBarViewItemView.h | 14 ++ .../private/MDCTabBarViewItemView.m | 31 ++++ .../unit/TabBarView/MDCTabBarViewTests.m | 169 ++++++++++++++++++ 5 files changed, 372 insertions(+), 3 deletions(-) diff --git a/components/Tabs/src/TabBarView/MDCTabBarView.h b/components/Tabs/src/TabBarView/MDCTabBarView.h index 2a1c10f107b..50987868730 100644 --- a/components/Tabs/src/TabBarView/MDCTabBarView.h +++ b/components/Tabs/src/TabBarView/MDCTabBarView.h @@ -14,6 +14,8 @@ #import +#import "MaterialAvailability.h" + @protocol MDCTabBarViewDelegate; @protocol MDCTabBarViewIndicatorTemplate; @@ -235,3 +237,21 @@ __attribute__((objc_subclassing_restricted)) @interface MDCTabBarView : UIScroll inCoordinateSpace:(nonnull id)coordinateSpace; @end + +#if MDC_AVAILABLE_SDK_IOS(13_0) +/** + This component supports UIKit's Large Content Viewer. It is recommended that images associated with + each tab bar item be backed with a PDF image with "preserve vector data" enabled within the assets + entry in the catalog. This ensures that the image is scaled appropriately in the content viewer. + Alternatively specify an image to use for the large content viewer using UITabBarItem's property + @c largeContentSizeImage . If an image is specified, the given image is used as-is for the large + content viewer and will not be scaled. + If the image is not backed by PDF and a @c largeContentSizeImage is not specified, the given + @c image will be scaled and may be blurry. + For more details on the Large Content Viewer see: + https://developer.apple.com/videos/play/wwdc2019/261/ + */ +@interface MDCTabBarView (UILargeContentViewerInteractionDelegate) < + UILargeContentViewerInteractionDelegate> +@end +#endif // MDC_AVAILABLE_SDK_IOS(13_0) diff --git a/components/Tabs/src/TabBarView/MDCTabBarView.m b/components/Tabs/src/TabBarView/MDCTabBarView.m index d5ed67a8050..ca2eff29ab7 100644 --- a/components/Tabs/src/TabBarView/MDCTabBarView.m +++ b/components/Tabs/src/TabBarView/MDCTabBarView.m @@ -53,6 +53,9 @@ static NSString *const kAccessibilityHintKeyPath = @"accessibilityHint"; static NSString *const kAccessibilityIdentifierKeyPath = @"accessibilityIdentifier"; static NSString *const kAccessibilityTraitsKeyPath = @"accessibilityTraits"; +static NSString *const kTitlePositionAdjustment = @"titlePositionAdjustment"; +static NSString *const kLargeContentSizeImage = @"largeContentSizeImage"; +static NSString *const kLargeContentSizeImageInsets = @"largeContentSizeImageInsets"; #ifdef __IPHONE_13_4 @interface MDCTabBarView (PointerInteractions) @@ -93,6 +96,15 @@ The content padding (as UIEdgeInsets) for each layout style. The layout style is @property(nonnull, nonatomic, strong) NSMutableDictionary *layoutStyleToContentPadding; +#if defined(__IPHONE_13_0) && (__IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_13_0) +/** + The last large content viewer item displayed by the content viewer while the interaction is + running. When the interaction ends this property is nil. + */ +@property(nonatomic, nullable) id lastLargeContentViewerItem + NS_AVAILABLE_IOS(13_0); +#endif // defined(__IPHONE_13_0) && (__IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_13_0) + @end @implementation MDCTabBarView @@ -142,6 +154,14 @@ - (instancetype)init { if (@available(iOS 11.0, *)) { [super setContentInsetAdjustmentBehavior:UIScrollViewContentInsetAdjustmentAlways]; } + +#if defined(__IPHONE_13_0) && (__IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_13_0) + if (@available(iOS 13, *)) { + // If clients report conflicting gesture recognizers please see proposed solution in the + // internal document: go/mdc-ios-bottomnavigation-largecontentvieweritem + [self addInteraction:[[UILargeContentViewerInteraction alloc] initWithDelegate:self]]; + } +#endif // defined(__IPHONE_13_0) && (__IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_13_0) } return self; } @@ -233,6 +253,14 @@ - (void)setItems:(NSArray *)items { mdcItemView.selectedImage = item.selectedImage; mdcItemView.rippleTouchController.rippleView.rippleColor = self.rippleColor; mdcItemView.rippleTouchController.shouldProcessRippleWithScrollViewGestures = NO; + +#if defined(__IPHONE_13_0) && (__IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_13_0) + if (@available(iOS 13, *)) { + mdcItemView.largeContentImageInsets = item.largeContentSizeImageInsets; + mdcItemView.largeContentImage = item.largeContentSizeImage; + } +#endif // defined(__IPHONE_13_0) && (__IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_13_0) + itemView = mdcItemView; } UITapGestureRecognizer *tapGesture = @@ -544,6 +572,18 @@ - (void)addObserversToTabBarItems { forKeyPath:kAccessibilityTraitsKeyPath options:NSKeyValueObservingOptionNew context:kKVOContextMDCTabBarView]; + [item addObserver:self + forKeyPath:kTitlePositionAdjustment + options:NSKeyValueObservingOptionNew + context:kKVOContextMDCTabBarView]; + [item addObserver:self + forKeyPath:kLargeContentSizeImage + options:NSKeyValueObservingOptionNew + context:kKVOContextMDCTabBarView]; + [item addObserver:self + forKeyPath:kLargeContentSizeImageInsets + options:NSKeyValueObservingOptionNew + context:kKVOContextMDCTabBarView]; } } @@ -564,6 +604,11 @@ - (void)removeObserversFromTabBarItems { [item removeObserver:self forKeyPath:kAccessibilityTraitsKeyPath context:kKVOContextMDCTabBarView]; + [item removeObserver:self forKeyPath:kTitlePositionAdjustment context:kKVOContextMDCTabBarView]; + [item removeObserver:self forKeyPath:kLargeContentSizeImage context:kKVOContextMDCTabBarView]; + [item removeObserver:self + forKeyPath:kLargeContentSizeImageInsets + context:kKVOContextMDCTabBarView]; } } @@ -613,7 +658,18 @@ - (void)observeValueForKeyPath:(NSString *)keyPath tabBarItemView.accessibilityTraits = (tabBarItemView.accessibilityTraits | UIAccessibilityTraitSelected); } +#if defined(__IPHONE_13_0) && (__IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_13_0) + } else if ([keyPath isEqualToString:NSStringFromSelector(@selector(largeContentSizeImage))]) { + if (@available(iOS 13.0, *)) { + tabBarItemView.largeContentImage = newValue; + } + } else if ([keyPath + isEqualToString:NSStringFromSelector(@selector(largeContentSizeImageInsets))]) { + if (@available(iOS 13.0, *)) { + tabBarItemView.largeContentImageInsets = [newValue UIEdgeInsetsValue]; + } } +#endif // defined(__IPHONE_13_0) && (__IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_13_0) } else { [super observeValueForKeyPath:keyPath ofObject:object change:change context:context]; } @@ -1170,14 +1226,18 @@ - (void)didTapItemView:(UITapGestureRecognizer *)tap { return; } + [self didReleaseTapOnTabBarItem:self.items[index]]; +} + +- (void)didReleaseTapOnTabBarItem:(UITabBarItem *)item { if ([self.tabBarDelegate respondsToSelector:@selector(tabBarView:shouldSelectItem:)] && - ![self.tabBarDelegate tabBarView:self shouldSelectItem:self.items[index]]) { + ![self.tabBarDelegate tabBarView:self shouldSelectItem:item]) { return; } - self.selectedItem = self.items[index]; + self.selectedItem = item; if ([self.tabBarDelegate respondsToSelector:@selector(tabBarView:didSelectItem:)]) { - [self.tabBarDelegate tabBarView:self didSelectItem:self.items[index]]; + [self.tabBarDelegate tabBarView:self didSelectItem:item]; } } @@ -1275,4 +1335,79 @@ - (UIPointerStyle *)pointerInteraction:(UIPointerInteraction *)interaction } #endif +#pragma mark - UILargeContentViewerInteractionDelegate + +/** Returns the item view at the given point. Nil if there is no view at the given point. */ +- (UIView *)itemViewForPoint:(CGPoint)point { + for (NSUInteger i = 0; i < self.itemViews.count; i++) { + UIView *itemView = self.itemViews[i]; + if (CGRectContainsPoint(itemView.frame, point)) { + return itemView; + } + } + + return nil; +} + +#if MDC_AVAILABLE_SDK_IOS(13_0) +- (id)largeContentViewerInteraction: + (UILargeContentViewerInteraction *)interaction + itemAtPoint:(CGPoint)point + NS_AVAILABLE_IOS(13_0) { + if (!CGRectContainsPoint(self.bounds, point)) { + // The touch has wandered outside of the view. Do not display the content viewer. + if ([self.lastLargeContentViewerItem isKindOfClass:[MDCTabBarViewItemView class]]) { + [((MDCTabBarViewItemView *)self.lastLargeContentViewerItem).rippleTouchController.rippleView + cancelAllRipplesAnimated:NO + completion:nil]; + } + self.lastLargeContentViewerItem = nil; + return nil; + } + + UIView *itemView = [self itemViewForPoint:point]; + if (!itemView) { + // The touch is still within the navigation bar. Return the last seen item view. + return self.lastLargeContentViewerItem; + } + + if (self.lastLargeContentViewerItem && self.lastLargeContentViewerItem != itemView) { + if ([self.lastLargeContentViewerItem isKindOfClass:[MDCTabBarViewItemView class]]) { + [((MDCTabBarViewItemView *)self.lastLargeContentViewerItem).rippleTouchController.rippleView + cancelAllRipplesAnimated:NO + completion:nil]; + } + if ([itemView isKindOfClass:[MDCTabBarViewItemView class]]) { + [((MDCTabBarViewItemView *)itemView).rippleTouchController.rippleView + beginRippleTouchDownAtPoint:itemView.center + animated:NO + completion:nil]; + } + } + + self.lastLargeContentViewerItem = itemView; + return itemView; +} + +- (void)largeContentViewerInteraction:(UILargeContentViewerInteraction *)interaction + didEndOnItem:(id)item + atPoint:(CGPoint)point NS_AVAILABLE_IOS(13_0) { + if (item) { + for (NSUInteger i = 0; i < self.items.count; i++) { + UIView *itemView = self.itemViews[i]; + if (item == itemView) { + if ([itemView isKindOfClass:[MDCTabBarViewItemView class]]) { + [((MDCTabBarViewItemView *)itemView).rippleTouchController.rippleView + beginRippleTouchUpAnimated:YES + completion:nil]; + } + [self didReleaseTapOnTabBarItem:self.items[i]]; + } + } + } + + self.lastLargeContentViewerItem = nil; +} +#endif // MDC_AVAILABLE_SDK_IOS(13_0) + @end diff --git a/components/Tabs/src/TabBarView/private/MDCTabBarViewItemView.h b/components/Tabs/src/TabBarView/private/MDCTabBarViewItemView.h index 2341bafcb14..27af04ae9f2 100644 --- a/components/Tabs/src/TabBarView/private/MDCTabBarViewItemView.h +++ b/components/Tabs/src/TabBarView/private/MDCTabBarViewItemView.h @@ -36,4 +36,18 @@ /** The ripple contronller to display the ripple touch effect. */ @property(nonatomic, strong) MDCRippleTouchController *rippleTouchController; +#pragma mark - UILargeContentViewerItem + +/** + The title to display in the large content viewer. If set to nil, this property will return + @c title. + */ +@property(nonatomic, copy) NSString *largeContentTitle NS_AVAILABLE_IOS(13_0); + +/** + The image to display in the large content viwer. If set to nil, the property will return + @c image . If set to nil (or not set) @c scalesLargeContentImage will return YES otherwise NO. + */ +@property(nonatomic) UIImage *largeContentImage NS_AVAILABLE_IOS(13_0); + @end diff --git a/components/Tabs/src/TabBarView/private/MDCTabBarViewItemView.m b/components/Tabs/src/TabBarView/private/MDCTabBarViewItemView.m index 583ea24720d..178e34e649d 100644 --- a/components/Tabs/src/TabBarView/private/MDCTabBarViewItemView.m +++ b/components/Tabs/src/TabBarView/private/MDCTabBarViewItemView.m @@ -322,4 +322,35 @@ - (CGRect)contentFrameForTitleAndImageLayout { self.window.screen.scale); } +#pragma mark - UILargeContentViewerItem + +- (BOOL)showsLargeContentViewer { + return YES; +} + +- (NSString *)largeContentTitle { + if (_largeContentTitle) { + return _largeContentTitle; + } + + NSString *title = self.titleLabel.text; + if (!title && self.largeContentImage) { + return self.accessibilityLabel; + } + + return title; +} + +- (UIImage *)largeContentImage { + if (_largeContentImage) { + return _largeContentImage; + } + + return self.image; +} + +- (BOOL)scalesLargeContentImage { + return _largeContentImage == nil; +} + @end diff --git a/components/Tabs/tests/unit/TabBarView/MDCTabBarViewTests.m b/components/Tabs/tests/unit/TabBarView/MDCTabBarViewTests.m index f557e4a33df..41d8d8a42ed 100644 --- a/components/Tabs/tests/unit/TabBarView/MDCTabBarViewTests.m +++ b/components/Tabs/tests/unit/TabBarView/MDCTabBarViewTests.m @@ -15,6 +15,7 @@ #import #import "../../../src/TabBarView/private/MDCTabBarViewItemView.h" +#import "MaterialAvailability.h" #import "MDCTabBarItem.h" #import "MDCTabBarView.h" #import "MDCTabBarViewCustomViewable.h" @@ -89,6 +90,7 @@ - (UIView *)view { /** Category exposing implementation methods to aid testing. */ @interface MDCTabBarView (UnitTestingExposesPrivateMethods) +@property(nonnull, nonatomic, copy) NSArray *itemViews; - (void)didTapItemView:(UITapGestureRecognizer *)tap; @end @@ -1347,4 +1349,171 @@ - (void)testItemViewsHavePointerInteractions { #endif } +#pragma mark - UILargeContentViewerItem + +#if MDC_AVAILABLE_SDK_IOS(13_0) + +- (void)testLargeContentTitleEqualsToTitle { + if (@available(iOS 13.0, *)) { + // Given + UITabBarItem *item = [[UITabBarItem alloc] initWithTitle:@"Title" image:nil tag:0]; + self.tabBarView.items = @[ item ]; + + // When + NSString *largeContentTitle = self.tabBarView.itemViews.firstObject.largeContentTitle; + + // Then + XCTAssertEqualObjects(largeContentTitle, item.title); + } +} + +- (void)testLargeContentImageEqualsToDefaultImage { + if (@available(iOS 13.0, *)) { + // Given + UIImage *image = [[UIImage alloc] init]; + UITabBarItem *item = [[UITabBarItem alloc] initWithTitle:nil image:image tag:0]; + item.largeContentSizeImage = image; + self.tabBarView.items = @[ item ]; + + // When + UIImage *largeContentImage = self.tabBarView.itemViews.firstObject.largeContentImage; + + // Then + XCTAssertEqualObjects(largeContentImage, image); + } +} + +- (void)testSettingImageWihNoTitleFallbacksToAccessibilityLabel { + if (@available(iOS 13.0, *)) { + // Given + UIImage *image = [[UIImage alloc] init]; + UITabBarItem *item = [[UITabBarItem alloc] initWithTitle:nil image:image tag:0]; + item.accessibilityLabel = @"foo"; + self.tabBarView.items = @[ item ]; + + // When + NSString *largeContentTitle = self.tabBarView.itemViews.firstObject.largeContentTitle; + + // Then + XCTAssertEqualObjects(largeContentTitle, item.accessibilityLabel); + } +} + +- (void)testSettingLargeContentImageOnTabBarSetsItOnButtonBarView { + if (@available(iOS 13.0, *)) { + // Given + UIImage *image = [[UIImage alloc] init]; + UITabBarItem *item = [[UITabBarItem alloc] initWithTitle:nil image:nil tag:0]; + item.largeContentSizeImage = image; + self.tabBarView.items = @[ item ]; + + // When + UIImage *largeContentImage = self.tabBarView.itemViews.firstObject.largeContentImage; + + // Then + XCTAssertEqualObjects(largeContentImage, image); + } +} + +- (void)testSettingLargeContentSizeImageInsetsOnTabBarItemSetsItOnTabBarViewItem { + if (@available(iOS 13.0, *)) { + // Given + UIEdgeInsets insets = UIEdgeInsetsMake(10, 10, 10, 10); + UITabBarItem *item = [[UITabBarItem alloc] initWithTitle:nil image:nil tag:0]; + item.largeContentSizeImageInsets = insets; + self.tabBarView.items = @[ item ]; + + // When + UIEdgeInsets largeImageInsets = self.tabBarView.itemViews.firstObject.largeContentImageInsets; + + // Then + XCTAssertEqual(largeImageInsets.bottom, insets.bottom); + XCTAssertEqual(largeImageInsets.top, insets.top); + XCTAssertEqual(largeImageInsets.left, insets.left); + XCTAssertEqual(largeImageInsets.right, insets.right); + } +} + +- (void)testLargeContentImageUpdatesWhenTabBarPropertyUpdates { + if (@available(iOS 13.0, *)) { + // Given + UIImage *image = [[UIImage alloc] init]; + UITabBarItem *item = [[UITabBarItem alloc] initWithTitle:nil image:nil tag:0]; + self.tabBarView.items = @[ item ]; + + // When + item.largeContentSizeImage = image; + UIImage *largeContentImage = self.tabBarView.itemViews.firstObject.largeContentImage; + + // Then + XCTAssertEqualObjects(largeContentImage, image); + } +} + +- (void)testLargeContentInsetUpdatesWhenTabBarPropertyUpdates { + if (@available(iOS 13.0, *)) { + // Given + UIEdgeInsets insets = UIEdgeInsetsMake(10, 10, 10, 10); + UITabBarItem *item = [[UITabBarItem alloc] initWithTitle:nil image:nil tag:0]; + self.tabBarView.items = @[ item ]; + + // When + item.largeContentSizeImageInsets = insets; + UIEdgeInsets largeContentInsets = self.tabBarView.itemViews.firstObject.largeContentImageInsets; + + // Then + XCTAssertEqual(largeContentInsets.left, insets.left); + XCTAssertEqual(largeContentInsets.bottom, insets.bottom); + XCTAssertEqual(largeContentInsets.right, insets.right); + XCTAssertEqual(largeContentInsets.top, insets.top); + } +} + +- (void)testLargeContentViewerInteractionWhenItemIsSelectedThenDeselectedButStillInNavBarBounds { + if (@available(iOS 13.0, *)) { + // Given + NSString *title1 = @"Title1"; + UITabBarItem *item1 = [[UITabBarItem alloc] initWithTitle:title1 image:nil tag:0]; + self.tabBarView.items = @[ item1 ]; + self.tabBarView.frame = CGRectMake(0, 0, 350, 125); + [self.tabBarView layoutIfNeeded]; + UILargeContentViewerInteraction *interaction = [[UILargeContentViewerInteraction alloc] init]; + self.continueAfterFailure = NO; + + // When/Then + XCTAssertTrue([self.tabBarView respondsToSelector:@selector(largeContentViewerInteraction: + itemAtPoint:)]); + CGPoint itemViewOrigin = self.tabBarView.itemViews.firstObject.frame.origin; + id largeContentItem = + [self.tabBarView largeContentViewerInteraction:interaction itemAtPoint:itemViewOrigin]; + XCTAssertEqualObjects(largeContentItem.largeContentTitle, title1); + + largeContentItem = [self.tabBarView largeContentViewerInteraction:interaction + itemAtPoint:CGPointZero]; + XCTAssertEqualObjects(largeContentItem.largeContentTitle, title1); + } +} + +- (void)testLargeContentViewerInteractionWhenPointIsOutSideNavBarBounds { + if (@available(iOS 13.0, *)) { + // Given + UITabBarItem *item = [[UITabBarItem alloc] initWithTitle:@"Title1" image:nil tag:0]; + self.tabBarView.items = @[ item ]; + self.tabBarView.frame = CGRectMake(0, 0, 350, 125); + [self.tabBarView layoutIfNeeded]; + UILargeContentViewerInteraction *interaction = [[UILargeContentViewerInteraction alloc] init]; + self.continueAfterFailure = NO; + + // When/Then + XCTAssertTrue([self.tabBarView respondsToSelector:@selector(largeContentViewerInteraction: + itemAtPoint:)]); + CGPoint pointOutsideNavBar = CGPointMake(-1, -1); + + id largeContentItem = + [self.tabBarView largeContentViewerInteraction:interaction itemAtPoint:pointOutsideNavBar]; + XCTAssertNil(largeContentItem); + } +} +#endif // MDC_AVAILABLE_SDK_IOS(13_0) + @end From d63936f671a2b113eb57cbcc589b4ba14a160309 Mon Sep 17 00:00:00 2001 From: Nobody Date: Mon, 17 Aug 2020 12:10:49 -0700 Subject: [PATCH 17/20] [MDCBottomDrawerViewController] Adds shouldDisplayMobileLandscapeFullscreen property to MDCBottomDrawerViewController. This adds a new property to MDCBottomDrawerViewController that allows for non-fullscreen mobile landscape bottom drawer view. PiperOrigin-RevId: 327070504 --- .../NavigationDrawer/src/MDCBottomDrawerViewController.h | 9 +++++++++ .../NavigationDrawer/src/MDCBottomDrawerViewController.m | 4 +++- .../tests/unit/MDCNavigationDrawerScrollViewTests.m | 5 +++++ 3 files changed, 17 insertions(+), 1 deletion(-) diff --git a/components/NavigationDrawer/src/MDCBottomDrawerViewController.h b/components/NavigationDrawer/src/MDCBottomDrawerViewController.h index fa090b210e3..53ef078eb93 100644 --- a/components/NavigationDrawer/src/MDCBottomDrawerViewController.h +++ b/components/NavigationDrawer/src/MDCBottomDrawerViewController.h @@ -196,6 +196,15 @@ */ @property(nonatomic) BOOL adjustLayoutForIPadSlideOver; +/** + Whether to display mobile landscape view as fullscreen. + + When enabled, the drawer will fill the screen in landscape on mobile devices. + + Defaults to YES. +*/ +@property(nonatomic) BOOL shouldDisplayMobileLandscapeFullscreen; + /** Sets the top corners radius for an MDCBottomDrawerState drawerState diff --git a/components/NavigationDrawer/src/MDCBottomDrawerViewController.m b/components/NavigationDrawer/src/MDCBottomDrawerViewController.m index c416cd0596f..49d9941bac6 100644 --- a/components/NavigationDrawer/src/MDCBottomDrawerViewController.m +++ b/components/NavigationDrawer/src/MDCBottomDrawerViewController.m @@ -72,6 +72,7 @@ - (void)commonMDCBottomDrawerViewControllerInit { _dismissOnBackgroundTap = YES; _shouldForwardBackgroundTouchEvents = NO; + _shouldDisplayMobileLandscapeFullscreen = YES; _isDrawerClosed = YES; _lastOffset = NSNotFound; } @@ -169,7 +170,8 @@ - (BOOL)isMobileLandscape { return self.traitCollection.verticalSizeClass == UIUserInterfaceSizeClassCompact; } - (BOOL)shouldPresentFullScreen { - return [self isAccessibilityMode] || [self isMobileLandscape]; + return [self isAccessibilityMode] || + (self.shouldDisplayMobileLandscapeFullscreen && [self isMobileLandscape]); } - (BOOL)contentReachesFullScreen { diff --git a/components/NavigationDrawer/tests/unit/MDCNavigationDrawerScrollViewTests.m b/components/NavigationDrawer/tests/unit/MDCNavigationDrawerScrollViewTests.m index 2b2c347c99a..f2de2984f4d 100644 --- a/components/NavigationDrawer/tests/unit/MDCNavigationDrawerScrollViewTests.m +++ b/components/NavigationDrawer/tests/unit/MDCNavigationDrawerScrollViewTests.m @@ -738,6 +738,11 @@ - (void)testSetShouldForwardTouchEventsCorrectly { self.drawerViewController.nextResponder); } +- (void)testSetShouldDisplayMobileLandscapeFullscreenCorrectly { + self.drawerViewController.shouldDisplayMobileLandscapeFullscreen = NO; + XCTAssertFalse(self.drawerViewController.shouldDisplayMobileLandscapeFullscreen); +} + - (void)testBottomDrawerTopInset { // Given MDCNavigationDrawerFakeHeaderViewController *fakeHeader = From e87c5dfe07be0c955f52bf3cdb0be582a95dbe2e Mon Sep 17 00:00:00 2001 From: Yarden Eitan Date: Mon, 17 Aug 2020 17:33:46 -0400 Subject: [PATCH 18/20] Automatic changelog preparation for release. --- .gitattributes | 28 ++++++++++++++++++++--- CHANGELOG.md | 62 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 87 insertions(+), 3 deletions(-) diff --git a/.gitattributes b/.gitattributes index c13f46c3bdc..f56e2dfb895 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,3 +1,25 @@ -# Do not merge this version into `stable`. # DO NOT CHANGE THIS FILE -snapshot_test_goldens/**/*.png filter=lfs diff=lfs merge=lfs -text # DO NOT EDIT THE LINE BELOW. -.gitattributes merge=gitattributes +# DO NOT CHANGE THIS FILE +# DO NOT EDIT THE LINE BELOW. +/.gitattributes merge=gitattributes +# DO NOT EDIT THE LINE ABOVE. +# +# You can of course edit this file, but make sure you understand what you are +# doing. This file defines a custom filter driver that prevents snapshot test +# images from being merged into `stable`. Snapshot test images are only +# valuable in `develop` because they are only intended to help developers +# identify changes in the appearance of the library. +# +# Before you change this file, please carefully consider whether such a change +# is actually necessary. When you do change this file, it should almost always +# be done in a dedicated commit directly on the `stable` branch and not part +# of a release. If you see this file being changed as part of a release, +# block the release and work with the releaser to ensure that the change needs +# to be propagated from the `develop` branch to the `stable` branch. In nearly +# all cases, it should not be propagated from `develop` to `stable`. +# +# If you are a releaser and see this file change and you're not sure why, you +# might have accidentally skipped [setting the correct +# driver in your cloned +# repository](https://github.com/material-components/material-components-ios/blob/develop/contributing/releasing.md#configure-the-merge-strategy-for-gitattributes). +# If that's the case, please either revert the accidental change manually or +# restart the release with a fresh clone and the correct driver. diff --git a/CHANGELOG.md b/CHANGELOG.md index fd26965ccac..b0b3069be59 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,65 @@ +# #develop# + +Replace this text with a summarized description of this release's contents. +## Breaking changes + +Replace this explanations for how to resolve the breaking changes. +## New deprecations + +Replace this text with links to deprecation guides. +## New features + +Replace this text with example code for each new feature. +## API changes + +## Component changes + +### BottomAppBar + +* [Fix bug where FAB shadow would "bounce" when the position changed.](https://github.com/material-components/material-components-ios/commit/453eaa8a9a33e344276892ebb005ceaec205bab8) (Jeff Verkoeyen) + +### BottomNavigation + +* [modify layouts with division to use floor(), as not using this was causing some non integer pixel values, which then resulted in a blurry layout](https://github.com/material-components/material-components-ios/commit/4a3058d59175eb455ef2f4e38df262b3cb6040cf) (Alyssa Weiss) + +### ButtonBar + +* [Adds Large Content Viewer support to MDCButtonBar, MDCNavigationBar, and MDCAppBar.](https://github.com/material-components/material-components-ios/commit/7f9d2cc3504b12da7a8dec3d8028c6b6102cd75e) (Yarden Eitan) +* [Fall back to accessibilityLabel for UILargeContentView when there is an image and no title.](https://github.com/material-components/material-components-ios/commit/ae3fc08aac3722feb31148011d52af7a7dc926f5) (Yarden Eitan) +* [Removes nullability modifiers from file.](https://github.com/material-components/material-components-ios/commit/4511a9f5e4d2909dbc0c84b65af45a7e0d7e03f2) (Nobody) + +### Chips + +* [Add a snapshot test case for MDCChipView with minimumSize set and centerVisible set to YES.](https://github.com/material-components/material-components-ios/commit/70d7a73cb4beaf4aa67e1d51626c6e1ab9b12b6a) (Wenyu Zhang) + +### NavigationDrawer + +* [Adds shouldDisplayMobileLandscapeFullscreen property to MDCBottomDrawerViewController.](https://github.com/material-components/material-components-ios/commit/d63936f671a2b113eb57cbcc589b4ba14a160309) (Nobody) + +### ScalableFontDescriptor + +* [Add a new ScalableFontDescriptor library.](https://github.com/material-components/material-components-ios/commit/1baab835a533df37b5e599444937f75deadca056) (Jeff Verkoeyen) + +### Snackbar + +* [ Material Snackbar support for selecting which window to present on.](https://github.com/material-components/material-components-ios/commit/c48cd65ade15bc29ab2329ed9e8032e1f5f01a2c) (Nobody) +* [Deprecate more class methods on MDCSnackbarManager.](https://github.com/material-components/material-components-ios/commit/8dc2768f7fabd5451cf8b8956098020465ac9088) (Jeff Verkoeyen) +* [Deprecate remaining class methods on MDCSnackbarManager.](https://github.com/material-components/material-components-ios/commit/b928b4d5c8e854288450be0938b8bfc3927ad46e) (Jeff Verkoeyen) +* [Internal change](https://github.com/material-components/material-components-ios/commit/ad2b16134b8dc04667604a7b8609686a1c401494) (Jeff Verkoeyen) + +### Tabs + +* [Adds Large Content Viewer support to MDCTabBarView.](https://github.com/material-components/material-components-ios/commit/eb13147f839302bd0b0b42fcb74f90cf94fe72f1) (Yarden Eitan) +* [Animate text and icon colors when switching tabs.](https://github.com/material-components/material-components-ios/commit/3022102dbfd9833f325fbebe9ff7bceccb0c5778) (Jeff Verkoeyen) + +## Multi-component changes + +* [Add isPointerInteractionEnabled availability check.](https://github.com/material-components/material-components-ios/commit/91a2953b0d052bd6411c178be22923445ed03b35) (Bryan Oltman) +* [Ensure transition controller pass-through accessors work following presentation controller initialization](https://github.com/material-components/material-components-ios/commit/225cd3c0cba6ac194c77a418188d1c4421227f2e) (Andrew Overton) +* [Move MDCBottomNavigationBarController to main podspec](https://github.com/material-components/material-components-ios/commit/b22eef526fcd0ecb164f5eab0d8e42a36fc313de) (Andrew Overton) + +--- + # 113.0.0 In this major release we have dropped support for iOS 9.0, added support to Catalyst for our catalogs, and enlarged the minimum touch target of the thumb view of our Slider component. From 40002f8936c1397ab8b61c0b504ba70b689e3b2e Mon Sep 17 00:00:00 2001 From: Yarden Eitan Date: Mon, 17 Aug 2020 17:51:26 -0400 Subject: [PATCH 19/20] Hand-modified CHANGELOG.md API diff. --- CHANGELOG.md | 41 ++++++++++++++++++++++++++++++++++------- 1 file changed, 34 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b0b3069be59..4bd51f012fb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,16 +1,43 @@ -# #develop# +# 113.1.0 -Replace this text with a summarized description of this release's contents. -## Breaking changes +In this minor release we have added a ScalableFontDescriptor library to represent custom scalable fonts from iOS 11 and up, added UILargeContentViewer support to AppBar, NavigationBar, TabBarView, as well as other improvements. -Replace this explanations for how to resolve the breaking changes. ## New deprecations -Replace this text with links to deprecation guides. +* We have deprecated some MDCSnackbarManager class methods. Please use MDCSnackbarManager.defaultManager and their corresponding instance methods instead. + ## New features -Replace this text with example code for each new feature. -## API changes +On iOS 11 and up, using ScalableFontDescriptor enables you to describe a custom font and the corresponding UIFontMetrics that enable the font to scale in response to Dynamic Type settings. +This type enables you to pair font descriptors with specific UIFontMetrics. This is most commonly used for describing the metrics of a collection of custom fonts. + +```swift +// Create the type scale. +let fontDescriptor = UIFontDescriptor(name: "CustomFont-Light", size: UIFont.labelFontSize) +let scalableFontDescriptor: MDCScalableFontDescriptor +if #available(iOS 11, *) { + scalableFontDescriptor = MDCScalableFontDescriptor( + fontDescriptor: fontDescriptor, + fontMetrics: UIFontMetrics(forTextStyle: .largeTitle) + ) +} else { + scalableFontDescriptor = MDCScalableFontDescriptor(fontDescriptor: fontDescriptor) +} +// Use the scalable font descriptor. +if #available(iOS 11, *) { + label.font = scalableFontDescriptor.preferredFont(compatibleWith: label.traitCollection) + label.adjustsFontForContentSizeCategory = true +} else { + label.font = scalableFontDescriptor.baseFont() +} +``` + +You can now toggle if you would like that your BottomDrawer will display at fullscreen or not when in mobile landscape by setting the `shouldDisplayMobileLandscapeFullscreen` property on `MDCBottomDrawerViewController`. + +```swift +let controller = MDCBottomDrawerViewController() +controller.shouldDisplayMobileLandscapeFullscreen = false +``` ## Component changes From 5d54632e3b9deb7c9fe00b55c31d6d46c7e1b92b Mon Sep 17 00:00:00 2001 From: Yarden Eitan Date: Mon, 17 Aug 2020 17:51:53 -0400 Subject: [PATCH 20/20] Bumped version number to 113.1.0. --- MaterialComponents.podspec | 2 +- MaterialComponentsEarlGreyTests.podspec | 2 +- MaterialComponentsExamples.podspec | 2 +- MaterialComponentsSnapshotTests.podspec | 2 +- VERSION | 2 +- catalog/MDCCatalog/Info.plist | 4 ++-- catalog/MDCDragons/Info.plist | 4 ++-- catalog/MaterialCatalog/MaterialCatalog.podspec | 2 +- components/LibraryInfo/src/MDCLibraryInfo.m | 2 +- components/LibraryInfo/tests/unit/LibraryInfoTests.m | 2 +- demos/supplemental/RemoteImageServiceForMDCDemos.podspec | 2 +- 11 files changed, 13 insertions(+), 13 deletions(-) diff --git a/MaterialComponents.podspec b/MaterialComponents.podspec index 351075fe933..9980e421755 100644 --- a/MaterialComponents.podspec +++ b/MaterialComponents.podspec @@ -2,7 +2,7 @@ load 'scripts/generated/icons.rb' Pod::Spec.new do |mdc| mdc.name = "MaterialComponents" - mdc.version = "113.0.0" + mdc.version = "113.1.0" mdc.authors = "The Material Components authors." mdc.summary = "A collection of stand-alone production-ready UI libraries focused on design details." mdc.homepage = "https://github.com/material-components/material-components-ios" diff --git a/MaterialComponentsEarlGreyTests.podspec b/MaterialComponentsEarlGreyTests.podspec index 0c088b8f592..bf200d0d036 100644 --- a/MaterialComponentsEarlGreyTests.podspec +++ b/MaterialComponentsEarlGreyTests.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "MaterialComponentsEarlGreyTests" - s.version = "113.0.0" + s.version = "113.1.0" s.authors = "The Material Components authors." s.summary = "This spec is an aggregate of all the Material Components EarlGrey tests." s.description = "This spec is made for use in the MDC Catalog." diff --git a/MaterialComponentsExamples.podspec b/MaterialComponentsExamples.podspec index e61cf222ed5..72513823726 100644 --- a/MaterialComponentsExamples.podspec +++ b/MaterialComponentsExamples.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "MaterialComponentsExamples" - s.version = "113.0.0" + s.version = "113.1.0" s.authors = "The Material Components authors." s.summary = "This spec is an aggregate of all the Material Components examples." s.description = "This spec is made for use in the MDC Catalog. Used in conjunction with CatalogByConvention we create our Material Catalog." diff --git a/MaterialComponentsSnapshotTests.podspec b/MaterialComponentsSnapshotTests.podspec index 31804a189c1..621fb353445 100644 --- a/MaterialComponentsSnapshotTests.podspec +++ b/MaterialComponentsSnapshotTests.podspec @@ -53,7 +53,7 @@ end Pod::Spec.new do |s| s.name = "MaterialComponentsSnapshotTests" - s.version = "113.0.0" + s.version = "113.1.0" s.authors = "The Material Components authors." s.summary = "This spec is an aggregate of all the Material Components snapshot tests." s.homepage = "https://github.com/material-components/material-components-ios" diff --git a/VERSION b/VERSION index 40d44569d78..263ea44b8f2 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -113.0.0 +113.1.0 diff --git a/catalog/MDCCatalog/Info.plist b/catalog/MDCCatalog/Info.plist index 17009b87df8..06147cfd13e 100644 --- a/catalog/MDCCatalog/Info.plist +++ b/catalog/MDCCatalog/Info.plist @@ -15,11 +15,11 @@ CFBundlePackageType APPL CFBundleShortVersionString - 113.0.0 + 113.1.0 CFBundleSignature ???? CFBundleVersion - 113.0.0 + 113.1.0 LSRequiresIPhoneOS UIAppFonts diff --git a/catalog/MDCDragons/Info.plist b/catalog/MDCDragons/Info.plist index fc8079233f4..f5b054b8111 100644 --- a/catalog/MDCDragons/Info.plist +++ b/catalog/MDCDragons/Info.plist @@ -15,9 +15,9 @@ CFBundlePackageType APPL CFBundleShortVersionString - 113.0.0 + 113.1.0 CFBundleVersion - 113.0.0 + 113.1.0 LSRequiresIPhoneOS UILaunchStoryboardName diff --git a/catalog/MaterialCatalog/MaterialCatalog.podspec b/catalog/MaterialCatalog/MaterialCatalog.podspec index 611d32c4a79..f609ccd12a9 100644 --- a/catalog/MaterialCatalog/MaterialCatalog.podspec +++ b/catalog/MaterialCatalog/MaterialCatalog.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "MaterialCatalog" - s.version = "113.0.0" + s.version = "113.1.0" s.summary = "Helper Objective-C classes for the MDC catalog." s.description = "This spec is made for use in the MDC Catalog." s.homepage = "https://github.com/material-components/material-components-ios" diff --git a/components/LibraryInfo/src/MDCLibraryInfo.m b/components/LibraryInfo/src/MDCLibraryInfo.m index 8b634b1388b..3279da608ec 100644 --- a/components/LibraryInfo/src/MDCLibraryInfo.m +++ b/components/LibraryInfo/src/MDCLibraryInfo.m @@ -19,7 +19,7 @@ // This string is updated automatically as a part of the release process and should not be edited // manually. Do not rename this constant or change the formatting without updating the release // scripts. -static NSString* const kMDCLibraryInfoVersionString = @"113.0.0"; +static NSString* const kMDCLibraryInfoVersionString = @"113.1.0"; @implementation MDCLibraryInfo diff --git a/components/LibraryInfo/tests/unit/LibraryInfoTests.m b/components/LibraryInfo/tests/unit/LibraryInfoTests.m index 94f9488f353..47b5c89aa37 100644 --- a/components/LibraryInfo/tests/unit/LibraryInfoTests.m +++ b/components/LibraryInfo/tests/unit/LibraryInfoTests.m @@ -26,7 +26,7 @@ - (void)testVersionFormat { // Given // This regex pattern does the following: - // Accept: "113.0.0", etc. + // Accept: "113.1.0", etc. // Reject: "0.0.0", "1.2", "1", "-1.2.3", "Hi, I'm a version 1.2.3", "1.2.3 is my version", etc. // // Note the major version must be >= 1 since "0.0.0" is used as the version when something goes diff --git a/demos/supplemental/RemoteImageServiceForMDCDemos.podspec b/demos/supplemental/RemoteImageServiceForMDCDemos.podspec index 31fd3c70f94..c9319a63ca3 100644 --- a/demos/supplemental/RemoteImageServiceForMDCDemos.podspec +++ b/demos/supplemental/RemoteImageServiceForMDCDemos.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "RemoteImageServiceForMDCDemos" - s.version = "113.0.0" + s.version = "113.1.0" s.summary = "A helper image class for the MDC demos." s.description = "This spec is made for use in the MDC demos. It gets images via url." s.homepage = "https://github.com/material-components/material-components-ios"