From 0a0f7347fac7639ac132934705e88f69d7fedc6b Mon Sep 17 00:00:00 2001 From: Wenyu Zhang Date: Mon, 20 Jul 2020 10:56:01 -0700 Subject: [PATCH 01/43] [ButtonBar] Fix ButtonBar typical example's crash by making containerScheme a property. PiperOrigin-RevId: 322179582 --- .../examples/ButtonBarTypicalUseExample.m | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/components/ButtonBar/examples/ButtonBarTypicalUseExample.m b/components/ButtonBar/examples/ButtonBarTypicalUseExample.m index 046e9e0481c..ca0607150af 100644 --- a/components/ButtonBar/examples/ButtonBarTypicalUseExample.m +++ b/components/ButtonBar/examples/ButtonBarTypicalUseExample.m @@ -20,6 +20,7 @@ @interface ButtonBarTypicalUseExample : UIViewController @property(nonatomic, strong) MDCSemanticColorScheme *colorScheme; @property(nonatomic, strong) MDCTypographyScheme *typographyScheme; +@property(nonatomic, strong) MDCContainerScheme *containerScheme; @end @implementation ButtonBarTypicalUseExample @@ -27,22 +28,19 @@ @implementation ButtonBarTypicalUseExample - (id)init { self = [super init]; if (self) { - self.colorScheme = + _colorScheme = [[MDCSemanticColorScheme alloc] initWithDefaults:MDCColorSchemeDefaultsMaterial201804]; - self.typographyScheme = + _typographyScheme = [[MDCTypographyScheme alloc] initWithDefaults:MDCTypographySchemeDefaultsMaterial201804]; + MDCContainerScheme *containerScheme = [[MDCContainerScheme alloc] init]; + containerScheme.colorScheme = _colorScheme; + containerScheme.typographyScheme = _typographyScheme; + _containerScheme = containerScheme; self.title = @"Button Bar"; } return self; } -- (MDCContainerScheme *)containerScheme { - MDCContainerScheme *scheme = [[MDCContainerScheme alloc] init]; - scheme.colorScheme = self.colorScheme; - scheme.typographyScheme = self.typographyScheme; - return scheme; -} - - (void)viewDidLoad { [super viewDidLoad]; From 330125967a95335c7c678982715b2bbdb085d9de Mon Sep 17 00:00:00 2001 From: Cody Weaver Date: Mon, 20 Jul 2020 12:25:41 -0700 Subject: [PATCH 02/43] [MDC/Button] Add rippleEdgeInsets API. Currently we make the ripple the entire size of the button. This limits clients being able to customize if the ripple is inset or outset on a particular side of the button. Since MDCFloatingButton sets its contentEdgeInsets this API is needed to allow for the two values to be independent. https://developer.apple.com/documentation/uikit/uibutton/1624036-contentedgeinsets PiperOrigin-RevId: 322199718 --- components/Buttons/src/MDCButton.h | 5 +++++ components/Buttons/src/MDCButton.m | 7 +++++++ .../snapshot/ButtonsRippleSnapshotTests.m | 10 ++++++++++ .../Buttons/tests/unit/MDCButtonRippleTests.m | 19 +++++++++++++++++++ 4 files changed, 41 insertions(+) diff --git a/components/Buttons/src/MDCButton.h b/components/Buttons/src/MDCButton.h index 5aba27b9a5d..60ea6133d62 100644 --- a/components/Buttons/src/MDCButton.h +++ b/components/Buttons/src/MDCButton.h @@ -96,6 +96,11 @@ */ @property(nonatomic) CGSize inkViewOffset; +/** + The inset or outset margins for the rectangle surrounding the button’s ripple. + */ +@property(nonatomic, assign) UIEdgeInsets rippleEdgeInsets; + /** The minimum size of the button’s alignment rect. If either the height or width are non-positive (negative or zero), they will be ignored and that axis will adjust to its content size. diff --git a/components/Buttons/src/MDCButton.m b/components/Buttons/src/MDCButton.m index fa7262ae0db..b999961564c 100644 --- a/components/Buttons/src/MDCButton.m +++ b/components/Buttons/src/MDCButton.m @@ -337,6 +337,7 @@ - (void)layoutSubviews { } else { CGRect bounds = CGRectStandardize(self.bounds); bounds = CGRectOffset(bounds, self.inkViewOffset.width, self.inkViewOffset.height); + bounds = UIEdgeInsetsInsetRect(bounds, self.rippleEdgeInsets); _inkView.frame = bounds; self.rippleView.frame = bounds; } @@ -641,6 +642,12 @@ - (void)setInkViewOffset:(CGSize)inkViewOffset { [self setNeedsLayout]; } +- (void)setRippleEdgeInsets:(UIEdgeInsets)rippleEdgeInsets { + _rippleEdgeInsets = rippleEdgeInsets; + + [self setNeedsLayout]; +} + - (void)setEnableRippleBehavior:(BOOL)enableRippleBehavior { _enableRippleBehavior = enableRippleBehavior; diff --git a/components/Buttons/tests/snapshot/ButtonsRippleSnapshotTests.m b/components/Buttons/tests/snapshot/ButtonsRippleSnapshotTests.m index e502b49f68d..933acf51c7f 100644 --- a/components/Buttons/tests/snapshot/ButtonsRippleSnapshotTests.m +++ b/components/Buttons/tests/snapshot/ButtonsRippleSnapshotTests.m @@ -160,4 +160,14 @@ - (void)testButtonWhenRippleTouchDownAtPointIsCalledGeneratesCorrectImage { [self generateSnapshotAndVerifyForView:self.button]; } +- (void)testButtonRippleWhenContentEdgeInsetsAreSet { + // When + self.button.rippleEdgeInsets = UIEdgeInsetsMake(1, 2, 3, 4); + self.button.rippleView.backgroundColor = UIColor.purpleColor; + [self.button layoutIfNeeded]; + + // Then + [self generateSnapshotAndVerifyForView:self.button]; +} + @end diff --git a/components/Buttons/tests/unit/MDCButtonRippleTests.m b/components/Buttons/tests/unit/MDCButtonRippleTests.m index 37517b828a4..3e0d79f54ca 100644 --- a/components/Buttons/tests/unit/MDCButtonRippleTests.m +++ b/components/Buttons/tests/unit/MDCButtonRippleTests.m @@ -182,4 +182,23 @@ - (void)testSettingSelectedUpdatesRippleTheming { XCTAssertFalse(self.button.rippleView.isSelected); } +- (void)testSettingContentEdgeInsetsUpdatesRipple { + // Given + self.button.enableRippleBehavior = YES; + CGRect buttonFrame = CGRectMake(0, 0, 100, 100); + self.button.frame = buttonFrame; + UIEdgeInsets buttonRippleInsets = UIEdgeInsetsMake(1, 2, 3, 4); + + // When + self.button.rippleEdgeInsets = buttonRippleInsets; + [self.button layoutIfNeeded]; + + // Then + XCTAssertTrue(CGRectEqualToRect(UIEdgeInsetsInsetRect(buttonFrame, buttonRippleInsets), + self.button.rippleView.frame), + @"%@ is not equal to %@", + NSStringFromCGRect(UIEdgeInsetsInsetRect(buttonFrame, buttonRippleInsets)), + NSStringFromCGRect(self.button.rippleView.frame)); +} + @end From 3132beea793356e3b0f6cb77c32c9c5a1a23e68b Mon Sep 17 00:00:00 2001 From: Nobody Date: Mon, 20 Jul 2020 22:02:04 -0700 Subject: [PATCH 03/43] Fix lint error `UppercaseAttributedString` in `MDCButton` PiperOrigin-RevId: 322291368 --- components/Buttons/src/MDCButton.m | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/components/Buttons/src/MDCButton.m b/components/Buttons/src/MDCButton.m index b999961564c..7866f74641d 100644 --- a/components/Buttons/src/MDCButton.m +++ b/components/Buttons/src/MDCButton.m @@ -65,7 +65,7 @@ static inline CGSize CGSizeShrinkWithInsets(CGSize size, UIEdgeInsets edgeInsets MAX(0, size.height - (edgeInsets.top + edgeInsets.bottom))); } -static NSAttributedString *uppercaseAttributedString(NSAttributedString *string) { +static NSAttributedString *UppercaseAttributedString(NSAttributedString *string) { // Store the attributes. NSMutableArray *attributes = [NSMutableArray array]; [string enumerateAttributesInRange:NSMakeRange(0, [string length]) @@ -560,7 +560,7 @@ - (void)setAttributedTitle:(NSAttributedString *)title forState:(UIControlState) } if (_uppercaseTitle) { - title = uppercaseAttributedString(title); + title = UppercaseAttributedString(title); } [super setAttributedTitle:title forState:state]; } From 6aea60a148025c15ddf8b8558192f4366abfb672 Mon Sep 17 00:00:00 2001 From: Alyssa Weiss Date: Tue, 21 Jul 2020 07:33:13 -0700 Subject: [PATCH 04/43] Internal Change PiperOrigin-RevId: 322356277 --- components/private/ThumbTrack/src/MDCThumbTrack.m | 1 + 1 file changed, 1 insertion(+) diff --git a/components/private/ThumbTrack/src/MDCThumbTrack.m b/components/private/ThumbTrack/src/MDCThumbTrack.m index 952242be4f5..14d25e50c63 100644 --- a/components/private/ThumbTrack/src/MDCThumbTrack.m +++ b/components/private/ThumbTrack/src/MDCThumbTrack.m @@ -153,6 +153,7 @@ - (instancetype)initWithFrame:(CGRect)frame onTintColor:(UIColor *)onTintColor { _trackOnLayer = [CALayer layer]; [_trackView.layer addSublayer:_trackOnLayer]; + _trackView.layer.masksToBounds = YES; [self addSubview:_trackView]; From b18a9191cc84973f74003e277627676bb245177f Mon Sep 17 00:00:00 2001 From: Andrew Overton Date: Tue, 21 Jul 2020 11:49:13 -0700 Subject: [PATCH 05/43] [TextControls] Fix secureTextEntry layout pass infinite loop Closes #10041. PiperOrigin-RevId: 322408226 --- .../src/BaseTextAreas/MDCBaseTextArea.m | 16 ++++++++++++---- .../src/BaseTextFields/MDCBaseTextField.m | 16 ++++++++++++---- 2 files changed, 24 insertions(+), 8 deletions(-) diff --git a/components/TextControls/src/BaseTextAreas/MDCBaseTextArea.m b/components/TextControls/src/BaseTextAreas/MDCBaseTextArea.m index fb434de3a59..1f3174b74bb 100644 --- a/components/TextControls/src/BaseTextAreas/MDCBaseTextArea.m +++ b/components/TextControls/src/BaseTextAreas/MDCBaseTextArea.m @@ -453,10 +453,18 @@ - (void)applyColorViewModel:(MDCTextControlColorViewModel *)colorViewModel } else if (labelPosition == MDCTextControlLabelPositionFloating) { labelColor = colorViewModel.floatingLabelColor; } - self.textView.textColor = colorViewModel.textColor; - self.leadingAssistiveLabel.textColor = colorViewModel.leadingAssistiveLabelColor; - self.trailingAssistiveLabel.textColor = colorViewModel.trailingAssistiveLabelColor; - self.label.textColor = labelColor; + if (![self.textView.textColor isEqual:colorViewModel.textColor]) { + self.textView.textColor = colorViewModel.textColor; + } + if (![self.leadingAssistiveLabel.textColor isEqual:colorViewModel.leadingAssistiveLabelColor]) { + self.leadingAssistiveLabel.textColor = colorViewModel.leadingAssistiveLabelColor; + } + if (![self.trailingAssistiveLabel.textColor isEqual:colorViewModel.trailingAssistiveLabelColor]) { + self.trailingAssistiveLabel.textColor = colorViewModel.trailingAssistiveLabelColor; + } + if (![self.label.textColor isEqual:labelColor]) { + self.label.textColor = labelColor; + } } - (void)setTextControlColorViewModel:(MDCTextControlColorViewModel *)TextControlColorViewModel diff --git a/components/TextControls/src/BaseTextFields/MDCBaseTextField.m b/components/TextControls/src/BaseTextFields/MDCBaseTextField.m index 76fe9450360..9e2624f08bd 100644 --- a/components/TextControls/src/BaseTextFields/MDCBaseTextField.m +++ b/components/TextControls/src/BaseTextFields/MDCBaseTextField.m @@ -622,10 +622,18 @@ - (void)applyColorViewModel:(MDCTextControlColorViewModel *)colorViewModel } else if (labelPosition == MDCTextControlLabelPositionFloating) { labelColor = colorViewModel.floatingLabelColor; } - self.textColor = colorViewModel.textColor; - self.leadingAssistiveLabel.textColor = colorViewModel.leadingAssistiveLabelColor; - self.trailingAssistiveLabel.textColor = colorViewModel.trailingAssistiveLabelColor; - self.label.textColor = labelColor; + if (![self.textColor isEqual:colorViewModel.textColor]) { + self.textColor = colorViewModel.textColor; + } + if (![self.leadingAssistiveLabel.textColor isEqual:colorViewModel.leadingAssistiveLabelColor]) { + self.leadingAssistiveLabel.textColor = colorViewModel.leadingAssistiveLabelColor; + } + if (![self.trailingAssistiveLabel.textColor isEqual:colorViewModel.trailingAssistiveLabelColor]) { + self.trailingAssistiveLabel.textColor = colorViewModel.trailingAssistiveLabelColor; + } + if (![self.label.textColor isEqual:labelColor]) { + self.label.textColor = labelColor; + } } - (void)setTextControlColorViewModel:(MDCTextControlColorViewModel *)colorViewModel From 7b45e787db8aaa9885e8c42bb94aa6406658f3dc Mon Sep 17 00:00:00 2001 From: Bryan Oltman Date: Tue, 21 Jul 2020 12:03:47 -0700 Subject: [PATCH 06/43] [Buttons] Remove unused TitleColorAccessibilityMutator. PiperOrigin-RevId: 322411522 --- .../MDCButtonTitleColorAccessibilityMutator.h | 31 ---- .../MDCButtonTitleColorAccessibilityMutator.m | 57 -------- ...alButtons+TitleColorAccessibilityMutator.h | 15 -- ...uttonTitleColorAccessibilityMutatorTests.m | 135 ------------------ 4 files changed, 238 deletions(-) delete mode 100644 components/Buttons/src/TitleColorAccessibilityMutator/MDCButtonTitleColorAccessibilityMutator.h delete mode 100644 components/Buttons/src/TitleColorAccessibilityMutator/MDCButtonTitleColorAccessibilityMutator.m delete mode 100644 components/Buttons/src/TitleColorAccessibilityMutator/MaterialButtons+TitleColorAccessibilityMutator.h delete mode 100644 components/Buttons/tests/unit/MDCButtonTitleColorAccessibilityMutatorTests.m diff --git a/components/Buttons/src/TitleColorAccessibilityMutator/MDCButtonTitleColorAccessibilityMutator.h b/components/Buttons/src/TitleColorAccessibilityMutator/MDCButtonTitleColorAccessibilityMutator.h deleted file mode 100644 index 32179da24ae..00000000000 --- a/components/Buttons/src/TitleColorAccessibilityMutator/MDCButtonTitleColorAccessibilityMutator.h +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright 2016-present the Material Components for iOS authors. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#import - -#import "MaterialButtons.h" - -/** - A Mutator that will change an instance of MDCButton to have a high enough contrast text between - its background. - */ -@interface MDCButtonTitleColorAccessibilityMutator : NSObject - -/** - This method will change the title color for each state of the button to ensure a high accessiblity - contrast with its background. - */ -+ (void)changeTitleColorOfButton:(MDCButton *)button; - -@end diff --git a/components/Buttons/src/TitleColorAccessibilityMutator/MDCButtonTitleColorAccessibilityMutator.m b/components/Buttons/src/TitleColorAccessibilityMutator/MDCButtonTitleColorAccessibilityMutator.m deleted file mode 100644 index 25340701a5a..00000000000 --- a/components/Buttons/src/TitleColorAccessibilityMutator/MDCButtonTitleColorAccessibilityMutator.m +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright 2016-present the Material Components for iOS authors. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#import "MDCButtonTitleColorAccessibilityMutator.h" - -#import -#import "MaterialButtons.h" -#import "MaterialTypography.h" - -@implementation MDCButtonTitleColorAccessibilityMutator - -+ (void)changeTitleColorOfButton:(MDCButton *)button { - // This ensures title colors will be accessible against the buttons backgrounds. - UIControlState allControlStates = UIControlStateNormal | UIControlStateHighlighted | - UIControlStateDisabled | UIControlStateSelected; - MDFTextAccessibilityOptions options = 0; - if ([MDFTextAccessibility isLargeForContrastRatios:button.titleLabel.font]) { - options = MDFTextAccessibilityOptionsLargeFont; - } - for (NSUInteger controlState = 0; controlState <= allControlStates; ++controlState) { - UIColor *backgroundColor = [button backgroundColorForState:controlState]; - if ([self isTransparentColor:backgroundColor]) { - // TODO(randallli): We could potentially traverse the view heirarchy instead. - backgroundColor = button.underlyingColorHint; - } - if (backgroundColor) { - UIColor *existingColor = [button titleColorForState:controlState]; - if (![MDFTextAccessibility textColor:existingColor - passesOnBackgroundColor:backgroundColor - options:options]) { - UIColor *color = - [MDFTextAccessibility textColorOnBackgroundColor:backgroundColor - targetTextAlpha:[MDCTypography buttonFontOpacity] - options:options]; - [button setTitleColor:color forState:controlState]; - } - } - } -} - -/** Returns YES if the color is transparent (including a nil color). */ -+ (BOOL)isTransparentColor:(UIColor *)color { - return !color || [color isEqual:[UIColor clearColor]] || CGColorGetAlpha(color.CGColor) == 0; -} - -@end diff --git a/components/Buttons/src/TitleColorAccessibilityMutator/MaterialButtons+TitleColorAccessibilityMutator.h b/components/Buttons/src/TitleColorAccessibilityMutator/MaterialButtons+TitleColorAccessibilityMutator.h deleted file mode 100644 index 2fcca5a0a3b..00000000000 --- a/components/Buttons/src/TitleColorAccessibilityMutator/MaterialButtons+TitleColorAccessibilityMutator.h +++ /dev/null @@ -1,15 +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. - -#import "MDCButtonTitleColorAccessibilityMutator.h" diff --git a/components/Buttons/tests/unit/MDCButtonTitleColorAccessibilityMutatorTests.m b/components/Buttons/tests/unit/MDCButtonTitleColorAccessibilityMutatorTests.m deleted file mode 100644 index 49721d55572..00000000000 --- a/components/Buttons/tests/unit/MDCButtonTitleColorAccessibilityMutatorTests.m +++ /dev/null @@ -1,135 +0,0 @@ -// Copyright 2016-present the Material Components for iOS authors. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#import - -#import "MaterialButtons+TitleColorAccessibilityMutator.h" -#import "MaterialButtons.h" - -// A value greater than the largest value created by combining normal values of UIControlState. -// This is a complete hack, but UIControlState doesn't expose anything useful here. -// This assumes that UIControlState is actually a set of bitfields and ignores application-specific -// values. -static const UIControlState kNumUIControlStates = 2 * UIControlStateSelected - 1; -static const UIControlState kUIControlStateDisabledHighlighted = - UIControlStateHighlighted | UIControlStateDisabled; - -static NSArray *testColors() { - return @[ - [UIColor whiteColor], [UIColor blackColor], [UIColor redColor], [UIColor orangeColor], - [UIColor greenColor], [UIColor blueColor], [UIColor grayColor] - ]; -} - -static NSString *controlStateDescription(UIControlState controlState); - -@interface MDCButtonTitleColorAccessibilityMutatorTests : XCTestCase -@end - -@implementation MDCButtonTitleColorAccessibilityMutatorTests - -- (void)testMutateChangesTextColor { - for (UIColor *color in testColors()) { - for (NSUInteger controlState = 0; controlState <= kNumUIControlStates; ++controlState) { - // Given - MDCButton *button = [[MDCButton alloc] init]; - // Making the background color the same as the title color. - [button setBackgroundColor:color forState:(UIControlState)controlState]; - [button setTitleColor:color forState:(UIControlState)controlState]; - UIControlState disabledHighlighed = UIControlStateHighlighted | UIControlStateDisabled; - if ((controlState & disabledHighlighed) == disabledHighlighed) { - // Skip disabled highlighted state because UIButton ignores setTitleColor:forState: when - // passed that state. See `UIButton strangeness` in ButtonTests.m - continue; - } - - // When - [MDCButtonTitleColorAccessibilityMutator changeTitleColorOfButton:button]; - - // Then - XCTAssertNotEqualObjects([button titleColorForState:controlState], color, - @"for control state:%@ ", controlStateDescription(controlState)); - } - } -} - -- (void)testMutateKeepsAccessibleTextColor { - NSDictionary *colors = @{[UIColor redColor] : [UIColor blackColor]}; - for (UIColor *color in colors) { - for (NSUInteger controlState = 0; controlState <= kNumUIControlStates; ++controlState) { - if (controlState & kUIControlStateDisabledHighlighted) { - // We skip the Disabled Highlighted state because UIButton setter ignores it. - // see: testTitleColorForStateDisabledHighlight - continue; - } - // Given - MDCButton *button = [[MDCButton alloc] init]; - UIColor *backgroundColor = colors[color]; - UIColor *titleColor = color; - // Making the background color the same as the title color. - [button setBackgroundColor:backgroundColor forState:(UIControlState)controlState]; - [button setTitleColor:titleColor forState:(UIControlState)controlState]; - - // When - [MDCButtonTitleColorAccessibilityMutator changeTitleColorOfButton:button]; - - // Then - XCTAssertEqualObjects([button titleColorForState:controlState], titleColor, - @"for control state:%@ ", controlStateDescription(controlState)); - } - } -} - -- (void)testMutateUsesUnderlyingColorIfButtonBackgroundColorIsTransparent { - for (UIColor *color in testColors()) { - for (NSUInteger controlState = 0; controlState <= kNumUIControlStates; ++controlState) { - if ((controlState & kUIControlStateDisabledHighlighted) == - kUIControlStateDisabledHighlighted) { - // Skip since it's tested with either .highlighted or (.highlighted | .selected) - continue; - } - // Given - MDCButton *button = [[MDCButton alloc] init]; - button.underlyingColorHint = color; - [button setBackgroundColor:[UIColor clearColor] forState:controlState]; - [button setTitleColor:color forState:(UIControlState)controlState]; - - // When - [MDCButtonTitleColorAccessibilityMutator changeTitleColorOfButton:button]; - - // Then - XCTAssertNotEqualObjects([button titleColorForState:controlState], color, - @"for control state:%@ ", controlStateDescription(controlState)); - } - } -} - -@end - -static NSString *controlStateDescription(UIControlState controlState) { - if (controlState == UIControlStateNormal) { - return @"Normal"; - } - NSMutableString *string = [NSMutableString string]; - if ((UIControlStateHighlighted & controlState) == UIControlStateHighlighted) { - [string appendString:@"Highlighted "]; - } - if ((UIControlStateDisabled & controlState) == UIControlStateDisabled) { - [string appendString:@"Disabled "]; - } - if ((UIControlStateSelected & controlState) == UIControlStateSelected) { - [string appendString:@"Selected "]; - } - return [string copy]; -} From 6f3b23dfb83d883b88aa061c05de4dc6c4d72a0c Mon Sep 17 00:00:00 2001 From: Alyssa Weiss Date: Tue, 21 Jul 2020 13:49:09 -0700 Subject: [PATCH 07/43] Adding theming extension for TabBarView PiperOrigin-RevId: 322433982 --- MaterialComponentsBeta.podspec | 19 ++ .../MDCTabBarView+MaterialTheming.h | 38 ++++ .../MDCTabBarView+MaterialTheming.m | 81 +++++++ .../MaterialTabs+TabBarViewTheming.h | 16 ++ .../TabBarView/MDCTabBarViewThemingTests.m | 200 ++++++++++++++++++ 5 files changed, 354 insertions(+) create mode 100644 components/Tabs/src/TabBarViewTheming/MDCTabBarView+MaterialTheming.h create mode 100644 components/Tabs/src/TabBarViewTheming/MDCTabBarView+MaterialTheming.m create mode 100644 components/Tabs/src/TabBarViewTheming/MaterialTabs+TabBarViewTheming.h create mode 100644 components/Tabs/tests/unit/TabBarView/MDCTabBarViewThemingTests.m diff --git a/MaterialComponentsBeta.podspec b/MaterialComponentsBeta.podspec index abdc3f5ec01..aa8b5e56b8e 100644 --- a/MaterialComponentsBeta.podspec +++ b/MaterialComponentsBeta.podspec @@ -48,6 +48,25 @@ Pod::Spec.new do |mdc| end end + mdc.subspec "Tabs+TabBarViewTheming" do |extension| + extension.ios.deployment_target = '9.0' + extension.public_header_files = "components/#{extension.base_name.split('+')[0]}/src/#{extension.base_name.split('+')[1]}/*.h" + extension.source_files = [ + "components/#{extension.base_name.split('+')[0]}/src/#{extension.base_name.split('+')[1]}/*.{h,m}", + "components/#{extension.base_name.split('+')[0]}/src/#{extension.base_name.split('+')[1]}/private/*.{h,m}" + ] + extension.dependency "MaterialComponents/#{extension.base_name.split('+')[0]}+TabBarView" + extension.dependency "MaterialComponents/schemes/Container" + + extension.test_spec 'UnitTests' do |unit_tests| + unit_tests.source_files = [ + "components/#{extension.base_name.split('+')[0]}/tests/unit/TabBarView/MDCTabBarViewThemingTests.m", + ] + unit_tests.dependency "MaterialComponents/schemes/Container" + end + end + + # Private mdc.subspec "private" do |private_spec| diff --git a/components/Tabs/src/TabBarViewTheming/MDCTabBarView+MaterialTheming.h b/components/Tabs/src/TabBarViewTheming/MDCTabBarView+MaterialTheming.h new file mode 100644 index 00000000000..11dedd791f3 --- /dev/null +++ b/components/Tabs/src/TabBarViewTheming/MDCTabBarView+MaterialTheming.h @@ -0,0 +1,38 @@ +// 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 "MaterialTabs+TabBarView.h" +#import "MaterialContainerScheme.h" + +/** + This category is used to style MDCTabBarView instances to a specific Material style which can be + found within the [Material Guidelines](https://material.io/design/components/tabs.html). + */ +@interface MDCTabBarView (MaterialTheming) + +/** + Applies the Material Primary Theme to the receiver. + + @param scheme A container scheme used for theming. + */ +- (void)applyPrimaryThemeWithScheme:(nonnull id)scheme; + +/** + Applies the Material Surface Theme to the receiver. + + @param scheme A container scheme used for theming. + */ +- (void)applySurfaceThemeWithScheme:(nonnull id)scheme; + +@end diff --git a/components/Tabs/src/TabBarViewTheming/MDCTabBarView+MaterialTheming.m b/components/Tabs/src/TabBarViewTheming/MDCTabBarView+MaterialTheming.m new file mode 100644 index 00000000000..a6a8a552630 --- /dev/null +++ b/components/Tabs/src/TabBarViewTheming/MDCTabBarView+MaterialTheming.m @@ -0,0 +1,81 @@ +// 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 "MaterialTabs+TabBarView.h" +#import "MDCTabBarView+MaterialTheming.h" + +#import +#import + +#import "MaterialColorScheme.h" +#import "MaterialContainerScheme.h" +#import "MaterialTypographyScheme+Scheming.h" + +static const CGFloat kPrimaryThemeUnselectedOpacity = 0.74f; +static const CGFloat kPrimaryThemeBottomDividerOpacity = 0.12f; +static const CGFloat kSurfaceThemeUnselectedOpacity = 0.6f; + +@implementation MDCTabBarView (MaterialTheming) + +- (void)applyPrimaryThemeWithScheme:(nonnull id)scheme { + id colorScheme = scheme.colorScheme; + [self applyPrimaryThemeWithColorScheme:colorScheme]; + + id typographyScheme = scheme.typographyScheme; + [self applyThemeWithTypographyScheme:typographyScheme]; +} + +- (void)applySurfaceThemeWithScheme:(nonnull id)scheme { + id colorScheme = scheme.colorScheme; + [self applySurfaceThemeWithColorScheme:colorScheme]; + + id typographyScheme = scheme.typographyScheme; + [self applyThemeWithTypographyScheme:typographyScheme]; +} + +- (void)applyPrimaryThemeWithColorScheme:(id)colorScheme { + self.barTintColor = colorScheme.primaryColor; + self.bottomDividerColor = + [colorScheme.onPrimaryColor colorWithAlphaComponent:kPrimaryThemeBottomDividerOpacity]; + self.selectionIndicatorStrokeColor = colorScheme.onPrimaryColor; + [self setTitleColor:colorScheme.onPrimaryColor forState:UIControlStateSelected]; + [self setImageTintColor:colorScheme.onPrimaryColor forState:UIControlStateSelected]; + UIColor *unselectedTitleColor = + [colorScheme.onPrimaryColor colorWithAlphaComponent:kPrimaryThemeUnselectedOpacity]; + UIColor *unselectedImageColor = + [colorScheme.onPrimaryColor colorWithAlphaComponent:kPrimaryThemeUnselectedOpacity]; + [self setTitleColor:unselectedTitleColor forState:UIControlStateNormal]; + [self setImageTintColor:unselectedImageColor forState:UIControlStateNormal]; +} + +- (void)applySurfaceThemeWithColorScheme:(id)colorScheme { + self.barTintColor = colorScheme.surfaceColor; + self.bottomDividerColor = colorScheme.onSurfaceColor; + self.selectionIndicatorStrokeColor = colorScheme.primaryColor; + [self setTitleColor:colorScheme.primaryColor forState:UIControlStateSelected]; + [self setImageTintColor:colorScheme.primaryColor forState:UIControlStateSelected]; + UIColor *unselectedTitleColor = + [colorScheme.onSurfaceColor colorWithAlphaComponent:kSurfaceThemeUnselectedOpacity]; + UIColor *unselectedImageColor = + [colorScheme.onSurfaceColor colorWithAlphaComponent:kSurfaceThemeUnselectedOpacity]; + [self setTitleColor:unselectedTitleColor forState:UIControlStateNormal]; + [self setImageTintColor:unselectedImageColor forState:UIControlStateNormal]; +} + +- (void)applyThemeWithTypographyScheme:(id)typographyScheme { + [self setTitleFont:typographyScheme.button forState:UIControlStateNormal]; + [self setTitleFont:typographyScheme.button forState:UIControlStateSelected]; +} + +@end diff --git a/components/Tabs/src/TabBarViewTheming/MaterialTabs+TabBarViewTheming.h b/components/Tabs/src/TabBarViewTheming/MaterialTabs+TabBarViewTheming.h new file mode 100644 index 00000000000..85d1ffa7f66 --- /dev/null +++ b/components/Tabs/src/TabBarViewTheming/MaterialTabs+TabBarViewTheming.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 "MDCTabBarView+MaterialTheming.h" diff --git a/components/Tabs/tests/unit/TabBarView/MDCTabBarViewThemingTests.m b/components/Tabs/tests/unit/TabBarView/MDCTabBarViewThemingTests.m new file mode 100644 index 00000000000..f7a9bf09b3d --- /dev/null +++ b/components/Tabs/tests/unit/TabBarView/MDCTabBarViewThemingTests.m @@ -0,0 +1,200 @@ +// 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 "MaterialTabs+TabBarViewTheming.h" + +#import + +#import "MaterialTabs+TabBarView.h" +#import "MaterialColorScheme.h" +#import "MaterialContainerScheme.h" +#import "MaterialTypographyScheme.h" + +static const CGFloat kPrimaryThemeUnselectedOpacity = 0.74f; +static const CGFloat kPrimaryThemeBottomDividerOpacity = 0.12f; +static const CGFloat kSurfaceThemeUnselectedOpacity = 0.6f; + +/** Tests to confirm @c MDCTabBarView theming extension has the correct mappings. */ +@interface MDCTabBarViewThemingTest : XCTestCase +/** The @c MDCTabBarView being tested. */ +@property(nonatomic, strong) MDCTabBarView *tabBar; +/** The @c MDCSemanticColorScheme used to color the tabBar*/ +@property(nonatomic, strong) MDCSemanticColorScheme *colorScheme; +/** The @c MDCTypographyScheme used to provide fonts to the tabBar*/ +@property(nonatomic, strong) MDCTypographyScheme *typographyScheme; +/** The @c MDCContainerScheme used to hold the colorScheme and typographyScheme*/ +@property(nonatomic, strong) MDCContainerScheme *containerScheme; +@end + +@implementation MDCTabBarViewThemingTest + +- (void)setUp { + [super setUp]; + + self.tabBar = [[MDCTabBarView alloc] init]; + self.colorScheme = + [[MDCSemanticColorScheme alloc] initWithDefaults:MDCColorSchemeDefaultsMaterial201804]; + self.typographyScheme = + [[MDCTypographyScheme alloc] initWithDefaults:MDCTypographySchemeDefaultsMaterial201804]; + self.containerScheme = [[MDCContainerScheme alloc] init]; + self.containerScheme.colorScheme = self.colorScheme; + self.containerScheme.typographyScheme = self.typographyScheme; +} + +- (void)tearDown { + self.containerScheme = nil; + self.typographyScheme = nil; + self.colorScheme = nil; + self.tabBar = nil; + + [super tearDown]; +} + +- (void)testTabBarPrimaryThemingDefault { + // When + [self.tabBar applyPrimaryThemeWithScheme:self.containerScheme]; + + // Then + [self verifyTabBarPrimaryTheming]; +} + +- (void)testTabBarPrimaryThemingCustom { + // Given + self.colorScheme = [self customColorScheme]; + self.typographyScheme = [self customTypographyScheme]; + self.containerScheme.colorScheme = self.colorScheme; + self.containerScheme.typographyScheme = self.typographyScheme; + + // When + [self.tabBar applyPrimaryThemeWithScheme:self.containerScheme]; + + // Then + [self verifyTabBarPrimaryTheming]; +} + +- (void)testTabBarSurfaceVariantThemingDefault { + // When + [self.tabBar applySurfaceThemeWithScheme:self.containerScheme]; + + // Then + [self verifyTabBarSurfaceVariantTheming]; +} + +- (void)testTabBarSurfaceVariantThemingCustom { + // Given + self.colorScheme = [self customColorScheme]; + self.typographyScheme = [self customTypographyScheme]; + self.containerScheme.colorScheme = self.colorScheme; + self.containerScheme.typographyScheme = self.typographyScheme; + + // When + [self.tabBar applySurfaceThemeWithScheme:self.containerScheme]; + + // Then + [self verifyTabBarSurfaceVariantTheming]; +} + +#pragma mark - Test helpers + +- (MDCSemanticColorScheme *)customColorScheme { + MDCSemanticColorScheme *colorScheme = + [[MDCSemanticColorScheme alloc] initWithDefaults:MDCColorSchemeDefaultsMaterial201804]; + + colorScheme.primaryColor = [UIColor colorWithWhite:(CGFloat)0.9 alpha:0]; + colorScheme.primaryColorVariant = [UIColor colorWithWhite:(CGFloat)0.8 alpha:(CGFloat)0.1]; + colorScheme.secondaryColor = [UIColor colorWithWhite:(CGFloat)0.7 alpha:(CGFloat)0.2]; + colorScheme.errorColor = [UIColor colorWithWhite:(CGFloat)0.6 alpha:(CGFloat)0.3]; + colorScheme.surfaceColor = [UIColor colorWithWhite:(CGFloat)0.5 alpha:(CGFloat)0.4]; + colorScheme.backgroundColor = [UIColor colorWithWhite:(CGFloat)0.4 alpha:(CGFloat)0.5]; + colorScheme.onPrimaryColor = [UIColor colorWithWhite:(CGFloat)0.3 alpha:(CGFloat)0.6]; + colorScheme.onSecondaryColor = [UIColor colorWithWhite:(CGFloat)0.2 alpha:(CGFloat)0.7]; + colorScheme.onSurfaceColor = [UIColor colorWithWhite:(CGFloat)0.1 alpha:(CGFloat)0.8]; + colorScheme.onBackgroundColor = [UIColor colorWithWhite:0 alpha:(CGFloat)0.9]; + + return colorScheme; +} + +- (MDCTypographyScheme *)customTypographyScheme { + MDCTypographyScheme *typographyScheme = [[MDCTypographyScheme alloc] init]; + + typographyScheme.headline1 = [UIFont systemFontOfSize:1]; + typographyScheme.headline2 = [UIFont systemFontOfSize:2]; + typographyScheme.headline3 = [UIFont systemFontOfSize:3]; + typographyScheme.headline4 = [UIFont systemFontOfSize:4]; + typographyScheme.headline5 = [UIFont systemFontOfSize:5]; + typographyScheme.headline6 = [UIFont systemFontOfSize:6]; + typographyScheme.subtitle1 = [UIFont systemFontOfSize:7]; + typographyScheme.subtitle2 = [UIFont systemFontOfSize:8]; + typographyScheme.body1 = [UIFont systemFontOfSize:9]; + typographyScheme.body2 = [UIFont systemFontOfSize:10]; + typographyScheme.caption = [UIFont systemFontOfSize:11]; + typographyScheme.button = [UIFont systemFontOfSize:12]; + typographyScheme.overline = [UIFont systemFontOfSize:13]; + + return typographyScheme; +} + +- (void)verifyTabBarPrimaryTheming { + // Color + XCTAssertEqualObjects(self.tabBar.barTintColor, self.colorScheme.primaryColor); + UIColor *bottomDividerColor = + [self.colorScheme.onPrimaryColor colorWithAlphaComponent:kPrimaryThemeBottomDividerOpacity]; + XCTAssertEqualObjects(self.tabBar.bottomDividerColor, bottomDividerColor); + XCTAssertEqualObjects(self.tabBar.selectionIndicatorStrokeColor, self.colorScheme.onPrimaryColor); + XCTAssertEqualObjects([self.tabBar titleColorForState:UIControlStateSelected], + self.colorScheme.onPrimaryColor); + XCTAssertEqualObjects([self.tabBar imageTintColorForState:UIControlStateSelected], + self.colorScheme.onPrimaryColor); + UIColor *unselectedTitleColor = + [self.colorScheme.onPrimaryColor colorWithAlphaComponent:kPrimaryThemeUnselectedOpacity]; + UIColor *unselectedImageColor = + [self.colorScheme.onPrimaryColor colorWithAlphaComponent:kPrimaryThemeUnselectedOpacity]; + XCTAssertEqualObjects([self.tabBar titleColorForState:UIControlStateNormal], + unselectedTitleColor); + XCTAssertEqualObjects([self.tabBar imageTintColorForState:UIControlStateNormal], + unselectedImageColor); + + // Typography + XCTAssertEqualObjects([self.tabBar titleFontForState:UIControlStateNormal], + self.typographyScheme.button); + XCTAssertEqualObjects([self.tabBar titleFontForState:UIControlStateSelected], + self.typographyScheme.button); +} + +- (void)verifyTabBarSurfaceVariantTheming { + // Color + XCTAssertEqualObjects(self.tabBar.barTintColor, self.colorScheme.surfaceColor); + XCTAssertEqualObjects(self.tabBar.bottomDividerColor, self.colorScheme.onSurfaceColor); + XCTAssertEqualObjects(self.tabBar.selectionIndicatorStrokeColor, self.colorScheme.primaryColor); + XCTAssertEqualObjects([self.tabBar titleColorForState:UIControlStateSelected], + self.colorScheme.primaryColor); + XCTAssertEqualObjects([self.tabBar imageTintColorForState:UIControlStateSelected], + self.colorScheme.primaryColor); + UIColor *unselectedTitleColor = + [self.colorScheme.onSurfaceColor colorWithAlphaComponent:kSurfaceThemeUnselectedOpacity]; + UIColor *unselectedImageColor = + [self.colorScheme.onSurfaceColor colorWithAlphaComponent:kSurfaceThemeUnselectedOpacity]; + XCTAssertEqualObjects([self.tabBar titleColorForState:UIControlStateNormal], + unselectedTitleColor); + XCTAssertEqualObjects([self.tabBar imageTintColorForState:UIControlStateNormal], + unselectedImageColor); + + // Typography + XCTAssertEqualObjects([self.tabBar titleFontForState:UIControlStateNormal], + self.typographyScheme.button); + XCTAssertEqualObjects([self.tabBar titleFontForState:UIControlStateSelected], + self.typographyScheme.button); +} + +@end From 180a5bf724f0fac5fa824660cca1c11a02de9721 Mon Sep 17 00:00:00 2001 From: Bryan Oltman Date: Tue, 21 Jul 2020 14:39:49 -0700 Subject: [PATCH 08/43] [FeatureHighlight] Delete deprecated FeatureHighlightAccessibilityMutator. PiperOrigin-RevId: 322445386 --- .../examples/FeatureHighlightColorExample.m | 2 - .../FeatureHighlightCustomFontsExample.m | 2 - .../FeatureHighlightShownViewExample.m | 2 - .../MDCFeatureHighlightAccessibilityMutator.h | 33 ----- .../MDCFeatureHighlightAccessibilityMutator.m | 83 ----------- ...ght+FeatureHighlightAccessibilityMutator.h | 15 -- ...hlightTitleBodyAccessibilityMutatorTests.m | 135 ------------------ 7 files changed, 272 deletions(-) delete mode 100644 components/FeatureHighlight/src/FeatureHighlightAccessibilityMutator/MDCFeatureHighlightAccessibilityMutator.h delete mode 100644 components/FeatureHighlight/src/FeatureHighlightAccessibilityMutator/MDCFeatureHighlightAccessibilityMutator.m delete mode 100644 components/FeatureHighlight/src/FeatureHighlightAccessibilityMutator/MaterialFeatureHighlight+FeatureHighlightAccessibilityMutator.h delete mode 100644 components/FeatureHighlight/tests/unit/FeatureHighlightTitleBodyAccessibilityMutatorTests.m diff --git a/components/FeatureHighlight/examples/FeatureHighlightColorExample.m b/components/FeatureHighlight/examples/FeatureHighlightColorExample.m index fa53f9471dc..acabc93f579 100644 --- a/components/FeatureHighlight/examples/FeatureHighlightColorExample.m +++ b/components/FeatureHighlight/examples/FeatureHighlightColorExample.m @@ -12,7 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -#import "MaterialFeatureHighlight+FeatureHighlightAccessibilityMutator.h" #import "MaterialFeatureHighlight.h" #import "supplemental/FeatureHighlightExampleSupplemental.h" @@ -31,7 +30,6 @@ - (void)collectionView:(UICollectionView *)collectionView highlightController.accessibilityHint = nil; highlightController.bodyText = @"What a nice color you've chosen."; highlightController.outerHighlightColor = cell.accessoryView.backgroundColor; - [MDCFeatureHighlightAccessibilityMutator mutate:highlightController]; [self presentViewController:highlightController animated:YES completion:nil]; } diff --git a/components/FeatureHighlight/examples/FeatureHighlightCustomFontsExample.m b/components/FeatureHighlight/examples/FeatureHighlightCustomFontsExample.m index f95b1e3156c..9d181b824dc 100644 --- a/components/FeatureHighlight/examples/FeatureHighlightCustomFontsExample.m +++ b/components/FeatureHighlight/examples/FeatureHighlightCustomFontsExample.m @@ -13,7 +13,6 @@ // limitations under the License. #import "MaterialButtons.h" -#import "MaterialFeatureHighlight+FeatureHighlightAccessibilityMutator.h" #import "MaterialFeatureHighlight.h" #import "MaterialTypography.h" #import "supplemental/FeatureHighlightExampleSupplemental.h" @@ -56,7 +55,6 @@ - (void)viewDidLoad { - (void)didTapButton:(id)sender { MDCFeatureHighlightViewController *vc = [[MDCFeatureHighlightViewController alloc] initWithHighlightedView:_button completion:nil]; - [MDCFeatureHighlightAccessibilityMutator mutate:vc]; vc.titleText = @"Feature Highlight can use custom fonts"; vc.bodyText = @"The title and body font can be set individually."; diff --git a/components/FeatureHighlight/examples/FeatureHighlightShownViewExample.m b/components/FeatureHighlight/examples/FeatureHighlightShownViewExample.m index b0470d43bbf..289f4b96752 100644 --- a/components/FeatureHighlight/examples/FeatureHighlightShownViewExample.m +++ b/components/FeatureHighlight/examples/FeatureHighlightShownViewExample.m @@ -15,7 +15,6 @@ #import "MaterialButtons+Theming.h" #import "MaterialButtons.h" #import "MaterialFeatureHighlight+ColorThemer.h" -#import "MaterialFeatureHighlight+FeatureHighlightAccessibilityMutator.h" #import "MaterialFeatureHighlight.h" #import "supplemental/FeatureHighlightExampleSupplemental.h" @@ -54,7 +53,6 @@ - (void)didTapButton:(id)sender { [self fabDidTap:fab]; } }]; - [MDCFeatureHighlightAccessibilityMutator mutate:vc]; [MDCFeatureHighlightColorThemer applySemanticColorScheme:self.colorScheme toFeatureHighlightViewController:vc]; vc.titleFont = self.typographyScheme.headline6; diff --git a/components/FeatureHighlight/src/FeatureHighlightAccessibilityMutator/MDCFeatureHighlightAccessibilityMutator.h b/components/FeatureHighlight/src/FeatureHighlightAccessibilityMutator/MDCFeatureHighlightAccessibilityMutator.h deleted file mode 100644 index e7281874b0f..00000000000 --- a/components/FeatureHighlight/src/FeatureHighlightAccessibilityMutator/MDCFeatureHighlightAccessibilityMutator.h +++ /dev/null @@ -1,33 +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. - -@class MDCFeatureHighlightViewController; - -#import -#import - -/** - A Mutator that will change an instance of MDCFeatureHighlightViewController to have a high enough - contrast text between its background. - Calling this mutator can overwrite UIApperance values. - */ -@interface MDCFeatureHighlightAccessibilityMutator : NSObject - -/** - This method will change the title and body color of the feature highlight to ensure a high - accessiblity contrast with its background if needed. - */ -+ (void)mutate:(MDCFeatureHighlightViewController *)featureHighlightViewController; - -@end diff --git a/components/FeatureHighlight/src/FeatureHighlightAccessibilityMutator/MDCFeatureHighlightAccessibilityMutator.m b/components/FeatureHighlight/src/FeatureHighlightAccessibilityMutator/MDCFeatureHighlightAccessibilityMutator.m deleted file mode 100644 index 22f31995de5..00000000000 --- a/components/FeatureHighlight/src/FeatureHighlightAccessibilityMutator/MDCFeatureHighlightAccessibilityMutator.m +++ /dev/null @@ -1,83 +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. - -#import "MDCFeatureHighlightAccessibilityMutator.h" - -#import "MaterialFeatureHighlight.h" -#import "MaterialTypography.h" - -@implementation MDCFeatureHighlightAccessibilityMutator - -+ (void)mutate:(MDCFeatureHighlightViewController *)featureHighlightViewController { - [MDCFeatureHighlightAccessibilityMutator mutateTitleColor:featureHighlightViewController]; - [MDCFeatureHighlightAccessibilityMutator mutateBodyColor:featureHighlightViewController]; -} - -+ (void)mutateTitleColor:(MDCFeatureHighlightViewController *)featureHighlightViewController { - MDFTextAccessibilityOptions options = MDFTextAccessibilityOptionsPreferLighter; - if ([MDFTextAccessibility isLargeForContrastRatios:featureHighlightViewController.titleFont]) { - options |= MDFTextAccessibilityOptionsLargeFont; - } - - UIColor *textColor = featureHighlightViewController.titleColor; - UIColor *backgroundColor = - [featureHighlightViewController.outerHighlightColor colorWithAlphaComponent:1]; - UIColor *titleColor = - [MDCFeatureHighlightAccessibilityMutator accessibleColorForTextColor:textColor - withBackgroundColor:backgroundColor - options:options]; - // no change needed. - if ([titleColor isEqual:textColor]) { - return; - } - - // Make title alpha the maximum it can be. - CGFloat titleAlpha = [MDFTextAccessibility minAlphaOfTextColor:titleColor - onBackgroundColor:backgroundColor - options:options]; - titleAlpha = MAX([MDCTypography titleFontOpacity], titleAlpha); - featureHighlightViewController.titleColor = [titleColor colorWithAlphaComponent:titleAlpha]; -} - -+ (void)mutateBodyColor:(MDCFeatureHighlightViewController *)featureHighlightViewController { - MDFTextAccessibilityOptions options = MDFTextAccessibilityOptionsPreferLighter; - if ([MDFTextAccessibility isLargeForContrastRatios:featureHighlightViewController.bodyFont]) { - options |= MDFTextAccessibilityOptionsLargeFont; - } - - UIColor *textColor = featureHighlightViewController.bodyColor; - UIColor *backgroundColor = - [featureHighlightViewController.outerHighlightColor colorWithAlphaComponent:1]; - featureHighlightViewController.bodyColor = - [MDCFeatureHighlightAccessibilityMutator accessibleColorForTextColor:textColor - withBackgroundColor:backgroundColor - options:options]; -} - -#pragma mark - Private - -+ (UIColor *)accessibleColorForTextColor:(UIColor *)textColor - withBackgroundColor:(UIColor *)backgroundColor - options:(MDFTextAccessibilityOptions)options { - if (textColor && [MDFTextAccessibility textColor:textColor - passesOnBackgroundColor:backgroundColor - options:options]) { - return textColor; - } - return [MDFTextAccessibility textColorOnBackgroundColor:backgroundColor - targetTextAlpha:[MDCTypography captionFontOpacity] - options:options]; -} - -@end diff --git a/components/FeatureHighlight/src/FeatureHighlightAccessibilityMutator/MaterialFeatureHighlight+FeatureHighlightAccessibilityMutator.h b/components/FeatureHighlight/src/FeatureHighlightAccessibilityMutator/MaterialFeatureHighlight+FeatureHighlightAccessibilityMutator.h deleted file mode 100644 index ddb2ab2c697..00000000000 --- a/components/FeatureHighlight/src/FeatureHighlightAccessibilityMutator/MaterialFeatureHighlight+FeatureHighlightAccessibilityMutator.h +++ /dev/null @@ -1,15 +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. - -#import "MDCFeatureHighlightAccessibilityMutator.h" diff --git a/components/FeatureHighlight/tests/unit/FeatureHighlightTitleBodyAccessibilityMutatorTests.m b/components/FeatureHighlight/tests/unit/FeatureHighlightTitleBodyAccessibilityMutatorTests.m deleted file mode 100644 index b540b44d60f..00000000000 --- a/components/FeatureHighlight/tests/unit/FeatureHighlightTitleBodyAccessibilityMutatorTests.m +++ /dev/null @@ -1,135 +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. - -#import "MaterialFeatureHighlight+FeatureHighlightAccessibilityMutator.h" -#import "MaterialFeatureHighlight.h" - -#import - -static NSArray *testColors() { - return @[ - [UIColor whiteColor], [UIColor blackColor], [UIColor redColor], [UIColor orangeColor], - [UIColor greenColor], [UIColor blueColor], [UIColor grayColor] - ]; -} - -@interface FeatureHighlightTitleBodyAccessibilityMutatorTests : XCTestCase -@property(nonatomic, strong) UIView *highlightedView; -@property(nonatomic, strong) UIView *showView; -@end - -@implementation FeatureHighlightTitleBodyAccessibilityMutatorTests - -- (void)setUp { - [super setUp]; - self.showView = [[UIView alloc] init]; - self.highlightedView = [[UIView alloc] init]; - [self.showView addSubview:self.highlightedView]; -} - -- (void)tearDown { - for (UIView *subview in self.showView.subviews) { - [subview removeFromSuperview]; - } - self.showView = nil; - self.highlightedView = nil; - [super tearDown]; -} - -- (void)testMutatorChangesTextColor { - for (UIColor *color in testColors()) { - MDCFeatureHighlightViewController *featureHighlightViewController = - [[MDCFeatureHighlightViewController alloc] initWithHighlightedView:self.highlightedView - andShowView:self.showView - completion:nil]; - - // Making the background color the same as the title/body color. - featureHighlightViewController.outerHighlightColor = color; - featureHighlightViewController.titleColor = color; - featureHighlightViewController.bodyColor = color; - [MDCFeatureHighlightAccessibilityMutator mutate:featureHighlightViewController]; - - XCTAssertNotNil(featureHighlightViewController.titleColor); - XCTAssertNotNil(featureHighlightViewController.bodyColor); - XCTAssertNotEqualObjects(featureHighlightViewController.titleColor, color); - XCTAssertNotEqualObjects(featureHighlightViewController.bodyColor, color); - } -} - -/** - * This test could fail even when our mutator is returning accessible color. In case MDF is - * returning a new accessible color - */ -- (void)testMutatorChangesTextColorToExpectedColor { - MDCFeatureHighlightViewController *featureHighlightViewController = - [[MDCFeatureHighlightViewController alloc] initWithHighlightedView:self.highlightedView - andShowView:self.showView - completion:nil]; - - // Making the background color the same as the title/body color. - featureHighlightViewController.outerHighlightColor = [UIColor whiteColor]; - featureHighlightViewController.titleColor = [UIColor whiteColor]; - featureHighlightViewController.bodyColor = [UIColor whiteColor]; - [MDCFeatureHighlightAccessibilityMutator mutate:featureHighlightViewController]; - - XCTAssertNotNil(featureHighlightViewController.titleColor); - XCTAssertNotNil(featureHighlightViewController.bodyColor); - - // Hard coded values, if this test fails it only means that we could mean that we changed the - // accessible color we are returning. - XCTAssertEqualObjects(featureHighlightViewController.titleColor, - [UIColor colorWithWhite:0 alpha:(CGFloat)0.87]); - XCTAssertEqualObjects(featureHighlightViewController.bodyColor, - [UIColor colorWithWhite:0 alpha:(CGFloat)0.54]); -} - -- (void)testMutatorKeepsAccessibleTextColor { - NSDictionary *colors = @{[UIColor redColor] : [UIColor blackColor]}; - for (UIColor *color in colors) { - MDCFeatureHighlightViewController *featureHighlightViewController = - [[MDCFeatureHighlightViewController alloc] initWithHighlightedView:self.highlightedView - andShowView:self.showView - completion:nil]; - // Making the background color accessible with title/body color. - featureHighlightViewController.outerHighlightColor = colors[color]; - featureHighlightViewController.titleColor = color; - featureHighlightViewController.bodyColor = color; - - [MDCFeatureHighlightAccessibilityMutator mutate:featureHighlightViewController]; - - XCTAssertEqualObjects(featureHighlightViewController.titleColor, color); - XCTAssertEqualObjects(featureHighlightViewController.bodyColor, color); - } -} - -- (void)testMutatorSelectsTheRightColorWhenThereIsNoColorSet { - MDCFeatureHighlightViewController *featureHighlightViewController = - [[MDCFeatureHighlightViewController alloc] initWithHighlightedView:self.highlightedView - andShowView:self.showView - completion:nil]; - - // Making the background color accessible with title/body color. - featureHighlightViewController.outerHighlightColor = [UIColor blackColor]; - - [MDCFeatureHighlightAccessibilityMutator mutate:featureHighlightViewController]; - - XCTAssertNotNil(featureHighlightViewController.titleColor); - XCTAssertNotNil(featureHighlightViewController.bodyColor); - XCTAssertNotEqualObjects(featureHighlightViewController.titleColor, [UIColor clearColor]); - XCTAssertNotEqualObjects(featureHighlightViewController.bodyColor, [UIColor clearColor]); - XCTAssertNotEqualObjects(featureHighlightViewController.titleColor, [UIColor blackColor]); - XCTAssertNotEqualObjects(featureHighlightViewController.bodyColor, [UIColor blackColor]); -} - -@end From 9039d47602220f44f5ea2fa72d1683173e35b5c8 Mon Sep 17 00:00:00 2001 From: Cody Weaver Date: Wed, 22 Jul 2020 09:02:55 -0700 Subject: [PATCH 09/43] [MDC/Button] Add rippleColor and rippleStyle APIs This commit adds the ripple color and ripple style APIs to match the inkColor and inkStyle APIs that are soon to be deprecated. This also moves those APIs to a "ToBeDeprecated" category. In this commit I set an ivar instead of having pass through properties because in a follow up I think it would be a good optimization to have the ripple be lazy until a user interacts with the button. This will lower initialization time and make the setters potentially faster. Since we don't have pass through properties I mirrored the default rippleColor from `MDCStatefulRippleView` to match here. PiperOrigin-RevId: 322585578 --- components/Buttons/src/MDCButton.h | 19 ++++++++++++++---- components/Buttons/src/MDCButton.m | 15 +++++++++++++- .../snapshot/ButtonsRippleSnapshotTests.m | 2 +- .../Buttons/tests/unit/MDCButtonRippleTests.m | 20 +++++++++---------- .../Buttons/tests/unit/MDCButtonTests.m | 11 ++++++++++ 5 files changed, 51 insertions(+), 16 deletions(-) diff --git a/components/Buttons/src/MDCButton.h b/components/Buttons/src/MDCButton.h index 60ea6133d62..7976f2a8c2e 100644 --- a/components/Buttons/src/MDCButton.h +++ b/components/Buttons/src/MDCButton.h @@ -17,6 +17,7 @@ #import "MaterialElevation.h" #import "MaterialInk.h" +#import "MaterialRipple.h" #import "MaterialShadowElevations.h" #import "MaterialShapes.h" @@ -36,11 +37,15 @@ */ @interface MDCButton : UIButton -/** The ink style of the button. */ -@property(nonatomic, assign) MDCInkStyle inkStyle UI_APPEARANCE_SELECTOR; +/** The ripple style of the button. */ +@property(nonatomic, assign) MDCRippleStyle rippleStyle; -/** The ink color of the button. */ -@property(nonatomic, strong, null_resettable) UIColor *inkColor UI_APPEARANCE_SELECTOR; +/** + The color of the ripple. + + @note Defaults to a transparent black. + */ +@property(nonatomic, strong, null_resettable) UIColor *rippleColor; /* Maximum radius of the button's ink. If the radius <= 0 then half the length of the diagonal of @@ -401,4 +406,10 @@ */ @property(nonatomic, assign) BOOL accessibilityTraitsIncludesButton; +/** The ink style of the button. */ +@property(nonatomic, assign) MDCInkStyle inkStyle UI_APPEARANCE_SELECTOR; + +/** The ink color of the button. */ +@property(nonatomic, strong, null_resettable) UIColor *inkColor UI_APPEARANCE_SELECTOR; + @end diff --git a/components/Buttons/src/MDCButton.m b/components/Buttons/src/MDCButton.m index 7866f74641d..247247088b7 100644 --- a/components/Buttons/src/MDCButton.m +++ b/components/Buttons/src/MDCButton.m @@ -33,6 +33,7 @@ static const CGFloat MDCButtonMinimumTouchTargetHeight = 48; static const CGFloat MDCButtonMinimumTouchTargetWidth = 48; static const CGFloat MDCButtonDefaultCornerRadius = 2.0; +static const CGFloat kDefaultRippleAlpha = (CGFloat)0.12; static const NSTimeInterval MDCButtonAnimationDuration = 0.2; @@ -230,7 +231,8 @@ - (void)commonMDCButtonInit { _inkView.inkColor = [UIColor colorWithWhite:1 alpha:(CGFloat)0.2]; _rippleView = [[MDCStatefulRippleView alloc] initWithFrame:self.bounds]; - _rippleView.rippleColor = [UIColor colorWithWhite:1 alpha:(CGFloat)0.12]; + _rippleColor = [UIColor colorWithWhite:0 alpha:kDefaultRippleAlpha]; + _rippleView.rippleColor = [UIColor colorWithWhite:0 alpha:(CGFloat)0.12]; // Default content insets // The default contentEdgeInsets are set here (instead of above, as they were previously) because @@ -622,6 +624,12 @@ - (void)setInkStyle:(MDCInkStyle)inkStyle { (inkStyle == MDCInkStyleUnbounded) ? MDCRippleStyleUnbounded : MDCRippleStyleBounded; } +- (void)setRippleStyle:(MDCRippleStyle)rippleStyle { + _rippleStyle = rippleStyle; + + self.rippleView.rippleStyle = rippleStyle; +} + - (UIColor *)inkColor { return _inkView.inkColor; } @@ -631,6 +639,11 @@ - (void)setInkColor:(UIColor *)inkColor { [self.rippleView setRippleColor:inkColor forState:MDCRippleStateHighlighted]; } +- (void)setRippleColor:(UIColor *)rippleColor { + _rippleColor = rippleColor ?: [UIColor colorWithWhite:0 alpha:kDefaultRippleAlpha]; + [self.rippleView setRippleColor:_rippleColor forState:MDCRippleStateHighlighted]; +} + - (void)setInkMaxRippleRadius:(CGFloat)inkMaxRippleRadius { _inkMaxRippleRadius = inkMaxRippleRadius; _inkView.maxRippleRadius = inkMaxRippleRadius; diff --git a/components/Buttons/tests/snapshot/ButtonsRippleSnapshotTests.m b/components/Buttons/tests/snapshot/ButtonsRippleSnapshotTests.m index 933acf51c7f..d1c50b0e750 100644 --- a/components/Buttons/tests/snapshot/ButtonsRippleSnapshotTests.m +++ b/components/Buttons/tests/snapshot/ButtonsRippleSnapshotTests.m @@ -43,7 +43,7 @@ - (void)setUp { UIImage *testImage = [[UIImage mdc_testImageOfSize:CGSizeMake(24, 24)] imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]; [self.button setImage:testImage forState:UIControlStateNormal]; - self.button.inkColor = [UIColor.magentaColor colorWithAlphaComponent:0.25]; + self.button.rippleColor = [UIColor.magentaColor colorWithAlphaComponent:0.25]; [self.button sizeToFit]; [self configureButton:self.button forState:UIControlStateNormal diff --git a/components/Buttons/tests/unit/MDCButtonRippleTests.m b/components/Buttons/tests/unit/MDCButtonRippleTests.m index 3e0d79f54ca..2f21d0252b3 100644 --- a/components/Buttons/tests/unit/MDCButtonRippleTests.m +++ b/components/Buttons/tests/unit/MDCButtonRippleTests.m @@ -51,7 +51,7 @@ - (void)tearDown { - (void)testDefaultButtonBehaviorWithRipple { // Then XCTAssertNotNil(self.button.rippleView); - XCTAssertEqualObjects(self.button.rippleView.rippleColor, [UIColor colorWithWhite:1 + XCTAssertEqualObjects(self.button.rippleView.rippleColor, [UIColor colorWithWhite:0 alpha:(CGFloat)0.12]); XCTAssertEqual(self.button.rippleView.rippleStyle, MDCRippleStyleBounded); XCTAssertFalse(self.button.enableRippleBehavior); @@ -89,14 +89,14 @@ - (void)testSetEnableRippleBehaviorToYesThenNoRemovesRippleViewAsSubviewOfButton } /** - Test setting @c inkColor correctly sets the @c rippleColor on @c rippleView of the button. + Test setting @c rippleColor correctly sets the @c rippleColor on @c rippleView of the button. */ -- (void)testSetCustomInkColorUpdatesRippleViewForHighlightedState { +- (void)testSetCustomRippleColorUpdatesRippleViewForHighlightedState { // Given UIColor *fakeColor = UIColor.redColor; // When - self.button.inkColor = fakeColor; + self.button.rippleColor = fakeColor; // Then XCTAssertEqualObjects([self.button.rippleView rippleColorForState:MDCRippleStateHighlighted], @@ -104,22 +104,22 @@ - (void)testSetCustomInkColorUpdatesRippleViewForHighlightedState { } /** - Test setting @c inkStyle correctly sets the @c rippleStyle on @c rippleView of the button. + Test setting @c rippleStyle correctly sets the @c rippleStyle on @c rippleView of the button. */ -- (void)testSetInkStyleUnboundedUpdatesRippleView { +- (void)testSetRippleStyleUnboundedUpdatesRippleView { // When - self.button.inkStyle = MDCInkStyleUnbounded; + self.button.rippleStyle = MDCRippleStyleUnbounded; // Then XCTAssertEqual(self.button.rippleView.rippleStyle, MDCRippleStyleUnbounded); } /** - Test setting @c inkStyle correctly sets the @c rippleStyle on @c rippleView of the button. + Test setting @c rippleStyle correctly sets the @c rippleStyle on @c rippleView of the button. */ -- (void)testSetInkStyleBoundedUpdatesRippleView { +- (void)testSetRippleStyleBoundedUpdatesRippleView { // When - self.button.inkStyle = MDCInkStyleBounded; + self.button.rippleStyle = MDCRippleStyleBounded; // Then XCTAssertEqual(self.button.rippleView.rippleStyle, MDCRippleStyleBounded); diff --git a/components/Buttons/tests/unit/MDCButtonTests.m b/components/Buttons/tests/unit/MDCButtonTests.m index dae509ed213..2beb9fed037 100644 --- a/components/Buttons/tests/unit/MDCButtonTests.m +++ b/components/Buttons/tests/unit/MDCButtonTests.m @@ -926,6 +926,17 @@ - (void)testInkColors { XCTAssertEqualObjects(self.button.inkColor, color); } +- (void)testRippleColors { + // Given + UIColor *color = randomColor(); + + // When + self.button.rippleColor = color; + + // Then + XCTAssertEqualObjects(self.button.rippleColor, color); +} + /* TODO: things to unit test (should these even be a thing?) From 66497f78375748c4d71d1a45a82420ff438373e4 Mon Sep 17 00:00:00 2001 From: Andrew Overton Date: Wed, 22 Jul 2020 09:27:50 -0700 Subject: [PATCH 10/43] [TextControls] Fix jumpiness in text areas PiperOrigin-RevId: 322590189 --- .../src/BaseTextAreas/MDCBaseTextArea.m | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/components/TextControls/src/BaseTextAreas/MDCBaseTextArea.m b/components/TextControls/src/BaseTextAreas/MDCBaseTextArea.m index 1f3174b74bb..f5620dca44f 100644 --- a/components/TextControls/src/BaseTextAreas/MDCBaseTextArea.m +++ b/components/TextControls/src/BaseTextAreas/MDCBaseTextArea.m @@ -199,7 +199,16 @@ - (void)postLayoutSubviews { if (![self validateHeight]) { [self invalidateIntrinsicContentSize]; } - [self.textView scrollRangeToVisible:self.textView.selectedRange]; + [self scrollToVisibleText]; +} + +- (void)scrollToVisibleText { + // This method was added to address b/161887902, with help from + // https://stackoverflow.com/a/49631521 + NSRange range = NSMakeRange(self.textView.text.length - 1, 1); + [self.textView scrollRangeToVisible:range]; + self.textView.scrollEnabled = NO; + self.textView.scrollEnabled = YES; } - (MDCBaseTextAreaLayout *)calculateLayoutWithSize:(CGSize)size { @@ -285,7 +294,7 @@ - (void)layoutGradientLayers { } - (CGFloat)numberOfLinesOfText { - // For more context on measurinig the lines in a UITextView see here: + // For more context on measuring the lines in a UITextView see here: // https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/TextLayout/Tasks/CountLines.html NSLayoutManager *layoutManager = self.textView.layoutManager; NSUInteger numberOfGlyphs = layoutManager.numberOfGlyphs; @@ -298,8 +307,10 @@ - (CGFloat)numberOfLinesOfText { numberOfLines += 1; } - if (self.textView.text.length > 0 && - [self.textView.text characterAtIndex:self.textView.text.length - 1] == '\n') { + BOOL textEndsInNewLine = + self.textView.text.length > 0 && + [self.textView.text characterAtIndex:self.textView.text.length - 1] == '\n'; + if (textEndsInNewLine) { numberOfLines += 1; } return (CGFloat)numberOfLines; From de482d077aa4fa1310bcb268952ce98a6d2291ba Mon Sep 17 00:00:00 2001 From: Bryan Oltman Date: Wed, 22 Jul 2020 10:07:24 -0700 Subject: [PATCH 11/43] [Slider] Remove supplemental directory from examples. PiperOrigin-RevId: 322598360 --- .../examples/SliderCollectionViewController.m | 18 +++++++++- .../SliderCollectionSupplemental.h | 25 -------------- .../SliderCollectionSupplemental.m | 33 ------------------- 3 files changed, 17 insertions(+), 59 deletions(-) delete mode 100644 components/Slider/examples/supplemental/SliderCollectionSupplemental.h delete mode 100644 components/Slider/examples/supplemental/SliderCollectionSupplemental.m diff --git a/components/Slider/examples/SliderCollectionViewController.m b/components/Slider/examples/SliderCollectionViewController.m index b894093b181..b1f6555fcbf 100644 --- a/components/Slider/examples/SliderCollectionViewController.m +++ b/components/Slider/examples/SliderCollectionViewController.m @@ -18,7 +18,6 @@ #import "MaterialSlider+ColorThemer.h" #import "MaterialSlider.h" #import "MaterialTypographyScheme.h" -#import "supplemental/SliderCollectionSupplemental.h" static NSString *const kReusableIdentifierItem = @"sliderItemCellIdentifier"; static CGFloat const kSliderHorizontalMargin = 16; @@ -208,6 +207,10 @@ - (CGFloat)minimumInteritemSpacing { @end +@interface SliderCollectionViewController : UICollectionViewController +@property(nonatomic, strong) MDCSemanticColorScheme *colorScheme; +@end + @implementation SliderCollectionViewController { NSMutableArray *_sliders; MDCTypographyScheme *_typographyScheme; @@ -316,3 +319,16 @@ - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView } @end + +@implementation SliderCollectionViewController (CatalogByConvention) + ++ (NSDictionary *)catalogMetadata { + return @{ + @"breadcrumbs" : @[ @"Slider", @"Slider" ], + @"description" : @"Sliders allow users to make selections from a range of values.", + @"primaryDemo" : @YES, + @"presentable" : @YES, + }; +} + +@end diff --git a/components/Slider/examples/supplemental/SliderCollectionSupplemental.h b/components/Slider/examples/supplemental/SliderCollectionSupplemental.h deleted file mode 100644 index dbdb1ef1d8b..00000000000 --- a/components/Slider/examples/supplemental/SliderCollectionSupplemental.h +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright 2016-present the Material Components for iOS authors. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -/* IMPORTANT: - This file contains supplemental code used to populate the demos with dummy data or instructions. - It is not necessary to import this file to use Material Components for iOS. - */ - -#import -#import "MaterialColorScheme.h" - -@interface SliderCollectionViewController : UICollectionViewController -@property(nonatomic, strong) MDCSemanticColorScheme *colorScheme; -@end diff --git a/components/Slider/examples/supplemental/SliderCollectionSupplemental.m b/components/Slider/examples/supplemental/SliderCollectionSupplemental.m deleted file mode 100644 index b295b4f3c90..00000000000 --- a/components/Slider/examples/supplemental/SliderCollectionSupplemental.m +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright 2016-present the Material Components for iOS authors. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -/* IMPORTANT: - This file contains supplemental code used to populate the demos with dummy data or instructions. - It is not necessary to import this file to use Material Components for iOS. - */ - -#import "SliderCollectionSupplemental.h" - -@implementation SliderCollectionViewController (CatalogByConvention) - -+ (NSDictionary *)catalogMetadata { - return @{ - @"breadcrumbs" : @[ @"Slider", @"Slider" ], - @"description" : @"Sliders allow users to make selections from a range of values.", - @"primaryDemo" : @YES, - @"presentable" : @YES, - }; -} - -@end From 93fb8836455d7716810afa054e09ccb9e4e02220 Mon Sep 17 00:00:00 2001 From: Andrew Overton Date: Wed, 22 Jul 2020 10:20:14 -0700 Subject: [PATCH 12/43] [TextControls] Add debug stuff to text controls examples PiperOrigin-RevId: 322601224 --- ...TextControlTextAreaContentViewController.m | 17 ++++++++++ ...extControlTextFieldContentViewController.m | 31 ++++++++++++++++--- 2 files changed, 43 insertions(+), 5 deletions(-) diff --git a/components/TextControls/examples/supplemental/MDCTextControlTextAreaContentViewController.m b/components/TextControls/examples/supplemental/MDCTextControlTextAreaContentViewController.m index 63544706acc..84ddc8d8b03 100644 --- a/components/TextControls/examples/supplemental/MDCTextControlTextAreaContentViewController.m +++ b/components/TextControls/examples/supplemental/MDCTextControlTextAreaContentViewController.m @@ -27,6 +27,7 @@ #import "MDCOutlinedTextArea+MaterialTheming.h" @interface MDCTextControlTextAreaContentViewController () +@property(nonatomic, assign) BOOL shouldAddDebugBorder; @end @implementation MDCTextControlTextAreaContentViewController @@ -39,6 +40,9 @@ - (MDCFilledTextArea *)createMaterialFilledTextArea { textArea.labelBehavior = MDCTextControlLabelBehaviorFloats; textArea.label.text = @"Phone number"; textArea.leadingAssistiveLabel.text = @"This is a string."; + if (self.shouldAddDebugBorder) { + [self addBorderToTextArea:textArea]; + } [textArea applyThemeWithScheme:self.containerScheme]; return textArea; } @@ -48,6 +52,9 @@ - (MDCOutlinedTextArea *)createMaterialOutlinedTextArea { textArea.textView.delegate = self; textArea.label.text = @"Phone number"; [textArea applyThemeWithScheme:self.containerScheme]; + if (self.shouldAddDebugBorder) { + [self addBorderToTextArea:textArea]; + } return textArea; } @@ -55,6 +62,9 @@ - (MDCBaseTextArea *)createDefaultBaseTextArea { MDCBaseTextArea *textArea = [[MDCBaseTextArea alloc] init]; textArea.textView.delegate = self; textArea.label.text = @"This is a floating label"; + if (self.shouldAddDebugBorder) { + [self addBorderToTextArea:textArea]; + } return textArea; } @@ -62,11 +72,18 @@ - (void)textViewDidChange:(UITextView *)textView { [self.view setNeedsLayout]; } +- (void)addBorderToTextArea:(MDCBaseTextArea *)textArea { + textArea.layer.borderColor = UIColor.redColor.CGColor; + textArea.layer.borderWidth = 1; +} + #pragma mark Overrides - (void)initializeScrollViewSubviewsArray { [super initializeScrollViewSubviewsArray]; + self.shouldAddDebugBorder = NO; + MDCFilledTextArea *filledTextAreaWithoutFloatingLabel = [self createMaterialFilledTextArea]; filledTextAreaWithoutFloatingLabel.labelBehavior = MDCTextControlLabelBehaviorDisappears; MDCOutlinedTextArea *outlinedTextAreaWithoutFloatingLabel = [self createMaterialOutlinedTextArea]; diff --git a/components/TextControls/examples/supplemental/MDCTextControlTextFieldContentViewController.m b/components/TextControls/examples/supplemental/MDCTextControlTextFieldContentViewController.m index 3d466c1fe11..30a395dfc81 100644 --- a/components/TextControls/examples/supplemental/MDCTextControlTextFieldContentViewController.m +++ b/components/TextControls/examples/supplemental/MDCTextControlTextFieldContentViewController.m @@ -30,7 +30,8 @@ #import "MDCUnderlinedTextField+MaterialTheming.h" @interface MDCTextControlTextFieldContentViewController () -@property(nonatomic, assign) BOOL shouldAddLeadingView; +@property(nonatomic, assign) BOOL shouldAddDebugLeadingView; +@property(nonatomic, assign) BOOL shouldAddDebugBorder; @end @implementation MDCTextControlTextFieldContentViewController @@ -44,9 +45,12 @@ - (MDCFilledTextField *)createMaterialFilledTextField { textField.label.text = @"Phone number"; textField.clearButtonMode = UITextFieldViewModeWhileEditing; textField.leadingAssistiveLabel.text = @"This is a string."; - if (self.shouldAddLeadingView) { + if (self.shouldAddDebugLeadingView) { [self addLeadingViewToTextField:textField]; } + if (self.shouldAddDebugBorder) { + [self addBorderToTextField:textField]; + } [textField applyThemeWithScheme:self.containerScheme]; return textField; } @@ -57,9 +61,12 @@ - (MDCUnderlinedTextField *)createMaterialUnderlinedTextField { textField.label.text = @"Phone number"; textField.clearButtonMode = UITextFieldViewModeWhileEditing; textField.leadingAssistiveLabel.text = @"This is a string."; - if (self.shouldAddLeadingView) { + if (self.shouldAddDebugLeadingView) { [self addLeadingViewToTextField:textField]; } + if (self.shouldAddDebugBorder) { + [self addBorderToTextField:textField]; + } [textField applyThemeWithScheme:self.containerScheme]; return textField; } @@ -69,9 +76,12 @@ - (MDCOutlinedTextField *)createMaterialOutlinedTextField { textField.placeholder = @"555-555-5555"; textField.label.text = @"Phone number"; textField.clearButtonMode = UITextFieldViewModeWhileEditing; - if (self.shouldAddLeadingView) { + if (self.shouldAddDebugLeadingView) { [self addLeadingViewToTextField:textField]; } + if (self.shouldAddDebugBorder) { + [self addBorderToTextField:textField]; + } [textField applyThemeWithScheme:self.containerScheme]; return textField; } @@ -82,12 +92,20 @@ - (MDCBaseTextField *)createDefaultBaseTextField { textField.label.text = @"This is a floating label"; textField.clearButtonMode = UITextFieldViewModeWhileEditing; textField.borderStyle = UITextBorderStyleRoundedRect; - if (self.shouldAddLeadingView) { + if (self.shouldAddDebugLeadingView) { [self addLeadingViewToTextField:textField]; } + if (self.shouldAddDebugBorder) { + [self addBorderToTextField:textField]; + } return textField; } +- (void)addBorderToTextField:(MDCBaseTextField *)textField { + textField.layer.borderColor = UIColor.redColor.CGColor; + textField.layer.borderWidth = 1; +} + - (void)addLeadingViewToTextField:(MDCBaseTextField *)textField { UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 24, 24)]; imageView.tintColor = self.containerScheme.colorScheme.primaryColor; @@ -103,6 +121,9 @@ - (void)addLeadingViewToTextField:(MDCBaseTextField *)textField { - (void)initializeScrollViewSubviewsArray { [super initializeScrollViewSubviewsArray]; + self.shouldAddDebugBorder = NO; + self.shouldAddDebugLeadingView = NO; + MDCFilledTextField *filledTextFieldWithoutFloatingLabel = [self createMaterialFilledTextField]; filledTextFieldWithoutFloatingLabel.labelBehavior = MDCTextControlLabelBehaviorDisappears; MDCOutlinedTextField *outlinedTextFieldWithoutFloatingLabel = From 3f3816f014bc4c5f0760c8838a0dc6916dd42f33 Mon Sep 17 00:00:00 2001 From: Yarden Eitan Date: Wed, 22 Jul 2020 11:22:30 -0700 Subject: [PATCH 13/43] [Shapes] Updates the shapeGenerator with a line width to inset the line rather than center it. There is a visual difference when using lineWidth instead of borderWidth due to lineWidth drawing on the path, and borderWidth insetting from the path. To resolve this, the path is made smaller by half the width of the line, thus to create a similar visual appearance as if borderWidth was set. PiperOrigin-RevId: 322615641 --- components/Shapes/src/MDCShapedShadowLayer.m | 17 ++++++++++ .../snapshot/MDCShapedViewSnapshotTests.m | 31 +++++++++++++++++++ 2 files changed, 48 insertions(+) diff --git a/components/Shapes/src/MDCShapedShadowLayer.m b/components/Shapes/src/MDCShapedShadowLayer.m index c3afcba9e04..745aa9d9e67 100644 --- a/components/Shapes/src/MDCShapedShadowLayer.m +++ b/components/Shapes/src/MDCShapedShadowLayer.m @@ -104,9 +104,25 @@ - (void)setPath:(CGPathRef)path { _colorLayer.fillColor = self.shapedBackgroundColor.CGColor; _colorLayer.strokeColor = self.shapedBorderColor.CGColor; _colorLayer.lineWidth = self.shapedBorderWidth; + [self generateColorPathGivenLineWidth]; } } +- (void)generateColorPathGivenLineWidth { + if (CGPathIsEmpty(self.path) || _colorLayer.lineWidth <= 0) { + return; + } + CGFloat halfOfBorderWidth = self.shapedBorderWidth / 2.f; + CGRect standardizedBounds = CGRectStandardize(self.bounds); + CGRect insetBounds = CGRectInset(standardizedBounds, halfOfBorderWidth, halfOfBorderWidth); + CGAffineTransform transform = + CGAffineTransformMakeTranslation(halfOfBorderWidth, halfOfBorderWidth); + transform = CGAffineTransformScale( + transform, CGRectGetWidth(insetBounds) / CGRectGetWidth(standardizedBounds), + CGRectGetHeight(insetBounds) / CGRectGetHeight(standardizedBounds)); + _colorLayer.path = CGPathCreateCopyByTransformingPath(_colorLayer.path, &transform); +} + - (CGPathRef)path { return _colorLayer.path; } @@ -155,6 +171,7 @@ - (void)setShapedBorderWidth:(CGFloat)shapedBorderWidth { } else { self.borderWidth = 0; _colorLayer.lineWidth = _shapedBorderWidth; + [self generateColorPathGivenLineWidth]; } } diff --git a/components/Shapes/tests/snapshot/MDCShapedViewSnapshotTests.m b/components/Shapes/tests/snapshot/MDCShapedViewSnapshotTests.m index dc52760c21c..2b9d358a154 100644 --- a/components/Shapes/tests/snapshot/MDCShapedViewSnapshotTests.m +++ b/components/Shapes/tests/snapshot/MDCShapedViewSnapshotTests.m @@ -52,6 +52,37 @@ - (void)generateSnapshotAndVerifyView { #pragma mark - Tests +- (void)testRectShapedViewWithCornerRadiusBySettingABorderWidthToPositiveThenZero { + // Given + MDCRectangleShapeGenerator *shapeGenerator = [[MDCRectangleShapeGenerator alloc] init]; + MDCRoundedCornerTreatment *cornerTreatment = [MDCRoundedCornerTreatment cornerWithRadius:50.f]; + [shapeGenerator setCorners:cornerTreatment]; + ((MDCShapedShadowLayer *)self.shapedView.layer).shapedBorderWidth = 10; + ((MDCShapedShadowLayer *)self.shapedView.layer).shapedBorderColor = UIColor.redColor; + + // When + self.shapedView.shapeGenerator = shapeGenerator; + ((MDCShapedShadowLayer *)self.shapedView.layer).shapedBorderWidth = 0; + + // Then + [self generateSnapshotAndVerifyView]; +} + +- (void)testRectShapedViewWithCornerRadius { + // Given + MDCRectangleShapeGenerator *shapeGenerator = [[MDCRectangleShapeGenerator alloc] init]; + MDCRoundedCornerTreatment *cornerTreatment = [MDCRoundedCornerTreatment cornerWithRadius:50.f]; + [shapeGenerator setCorners:cornerTreatment]; + ((MDCShapedShadowLayer *)self.shapedView.layer).shapedBorderWidth = 10; + ((MDCShapedShadowLayer *)self.shapedView.layer).shapedBorderColor = UIColor.redColor; + + // When + self.shapedView.shapeGenerator = shapeGenerator; + + // Then + [self generateSnapshotAndVerifyView]; +} + - (void)testCurvedRectShapedViewElevation00 { // When self.shapedView.shapeGenerator = From d6255fa0f431f3ee62b83637f0a50fb07c153292 Mon Sep 17 00:00:00 2001 From: Andrew Overton Date: Wed, 22 Jul 2020 13:51:29 -0700 Subject: [PATCH 14/43] [BottomAppBar] Material io bottom app bar This PR replaces the bottom app bar docs Closes https://github.com/material-components/material-components-ios/pull/10040 PiperOrigin-RevId: 322647459 --- components/BottomAppBar/README.md | 150 ++++++++++-------- components/BottomAppBar/docs/README.md | 45 ------ components/BottomAppBar/docs/accessibility.md | 62 -------- .../docs/assets/bottom-app-bar-anatomy.png | Bin 0 -> 49310 bytes .../docs/assets/bottom-app-bar-hero.png | Bin 0 -> 30504 bytes components/BottomAppBar/docs/color-theming.md | 4 - components/BottomAppBar/docs/typical-use.md | 4 - 7 files changed, 80 insertions(+), 185 deletions(-) delete mode 100644 components/BottomAppBar/docs/README.md delete mode 100644 components/BottomAppBar/docs/accessibility.md create mode 100644 components/BottomAppBar/docs/assets/bottom-app-bar-anatomy.png create mode 100644 components/BottomAppBar/docs/assets/bottom-app-bar-hero.png delete mode 100644 components/BottomAppBar/docs/color-theming.md delete mode 100644 components/BottomAppBar/docs/typical-use.md diff --git a/components/BottomAppBar/README.md b/components/BottomAppBar/README.md index 32921ebfa59..b53300f4696 100644 --- a/components/BottomAppBar/README.md +++ b/components/BottomAppBar/README.md @@ -2,86 +2,82 @@ title: "App bars: bottom" layout: detail section: components -excerpt: "A bottom app bar displays navigation and key actions at the bottom of the screen." +excerpt: "A bottom app bar displays navigation and key actions at the bottom of mobile screens." iconId: bottom_app_bar path: /catalog/bottomappbar/ api_doc_root: true --> - - # App bars: bottom [![Open bugs badge](https://img.shields.io/badge/dynamic/json.svg?label=open%20bugs&url=https%3A%2F%2Fapi.github.com%2Fsearch%2Fissues%3Fq%3Dis%253Aopen%2Blabel%253Atype%253ABug%2Blabel%253A%255BBottomAppBar%255D&query=%24.total_count)](https://github.com/material-components/material-components-ios/issues?q=is%3Aopen+is%3Aissue+label%3Atype%3ABug+label%3A%5BBottomAppBar%5D) -A bottom app bar displays navigation and key actions at the bottom of the screen. Bottom app bars -work like [navigation bars](../NavigationBar), but with the additional option to show a -[floating action button](../Buttons). +[Bottom app bars](https://material.io/components/app-bars-bottom/) display navigation and key actions at the bottom of the screen. -
- A screenshot of a bottom app bar. -
+![Bottom app bar hero](docs/assets/bottom-app-bar-hero.png) -## Design & API documentation +## Contents - +* [Using bottom app bars](#using-bottom-app-bars) +* [Installing bottom app bars](#installing-bottom-app-bars) +* [Making bottom app bars accessible](#making-bottom-app-bars-accessible) +* [Bottom app bar anatomy](#bottom-app-bar-anatomy) +* [Theming bottom app bars](#theming-bottom-app-bars) -## Table of contents +## Using bottom app bars -- [Overview](#overview) -- [Installation](#installation) - - [Installation with CocoaPods](#installation-with-cocoapods) - - [Importing](#importing) -- [Usage](#usage) - - [Typical use](#typical-use) -- [Extensions](#extensions) - - [Color Theming](#color-theming) -- [Accessibility](#accessibility) - - [Set `-accessibilityLabel`](#set-`-accessibilitylabel`) - - [Set `-accessibilityHint`](#set-`-accessibilityhint`) +Bottom app bars group primary and secondary actions at the bottom of the screen, where they are easily reachable by the user's thumb. -- - - +Use the `UIView` subclass `MDCBottomAppBarView` to add a bottom app bar to your app. `MDCBottomAppBarView` contains a horizontally centered [floating action button](https://material.io/components/ios/catalog/buttons/api-docs/Classes/MDCFloatingButton.html) for primary actions and a customizable [navigation bar](https://material.io/components/ios/catalog/flexible-headers/navigation-bars/) for secondary actions. The `MDCBottomAppBarView` API includes properties that allow changes in elevation, position, and visibility of the embedded floating action button. -## Overview +Instances of `UIBarButtonItem` can be added to a `MDCBottomAppBarView`'s navigation bar. Leading and trailing navigation items will be shown and hidden based on the position of the floating action button. -Bottom app bars follow a recommended Material Design interaction design pattern for providing primary and secondary actions that are easily accessible. With a bottom app bar users are more easily able to use single-handed touch interaction with an application since actions are displayed close to the bottom of the screen within easy reach of a user's thumb. +Transitions between floating action button position, elevation, and visibility states are animated by default, but can be disabled if desired. -The bottom app bar includes a floating action button that is intended to provide users with a primary action. Secondary actions are available on a navigation bar that can be customized with several buttons on the left and right sides of the navigation bar. The primary action floating action button is centered on the bottom app bar by default. +### Bottom app bar example -MDCBottomAppBarView should be attached to the bottom of the screen or used in conjunction with an expandable bottom drawer. The MDCBottomAppBarView API includes properties that allow changes to the elevation, position and visibility of the embedded floating action button. +`MDCBottomAppBarView` can be added to a view hierarchy like any `UIView`. Material Design guidelines +recommend always placing the bottom app bar at the bottom of the screen. -UIBarButtonItems can be added to the navigation bar of the MDCBottomAppBarView. Leading and trailing navigation items will be shown and hidden based on the position of the floating action button. + +#### Swift -Transitions between floating action button position, elevation and visibility states are animated by default, but can be disabled if desired. +```swift +let bottomAppBar = MDCBottomAppBarView() +addSubview(bottomAppBar) +view.leftAnchor.constraint(equalTo: bottomAppBarView.leftAnchor).isActive = true +view.rightAnchor.constraint(equalTo: bottomAppBarView.rightAnchor).isActive = true +view.bottomAnchor.constraint(equalTo: bottomAppBarView.bottomAnchor).isActive = true +``` + +#### Objective-C -## Installation +```objc +MDCBottomAppBarView *bottomAppBar = [[MDCBottomAppBarView alloc] init]; +[self addSubview:bottomAppBar]; +[self.view.leftAnchor constraintEqualToAnchor:bottomAppBarView.leftAnchor].active = YES; +[self.view.rightAnchor constraintEqualToAnchor:bottomAppBarView.rightAnchor].active = YES; +[self.view.bottomAnchor constraintEqualToAnchor:self.textField.bottomAnchor].active = YES; +``` - + -### Installation with CocoaPods +## Installing `MDCBottomAppBarView` -Add the following to your `Podfile`: +In order to use `MDCBottomAppBarView`, first add the component to your `Podfile`: ```bash -pod 'MaterialComponents/BottomAppBar' +pod MaterialComponents/BottomAppBar ``` -Then, run the following command: +Then, run the installer. ```bash pod install ``` -### Importing - -To import the component: +After that, import the component target. #### Swift @@ -90,38 +86,16 @@ import MaterialComponents.MaterialBottomAppBar ``` #### Objective-C - ```objc #import "MaterialBottomAppBar.h" ``` +From there, initialize an `MDCBottomAppBarView` like you would any `UIView`. -## Usage - - - -### Typical use - -MDCBottomAppBarView can be added to a view hierarchy like any UIView. Material Design guidelines -recommend always placing the bottom app bar at the bottom of the screen. - +## Making bottom app bars accessible -## Extensions - - - -### Color Theming - -MDCBottomAppBarView does not yet have a Material Design color system theming extension. Please -indicate interest by commenting on https://github.com/material-components/material-components-ios/issues/7172. - -## Accessibility - - - -To help ensure your bottom app bar is accessible to as many users as possible, please be sure to review the -following recommendations: +The following recommendations will ensure that the bottom app bar is accessible to as many users as possible: ### Set `-accessibilityLabel` @@ -183,3 +157,39 @@ trailingButton.accessibilityHint = @"Purchase the item"; ``` +## Bottom app bar anatomy + +A bottom app bar has a container and an optional navigation icon, anchored +floating action button (FAB), action item(s) and an overflow menu. + +![Bottom app bar anatomy diagram](docs/assets/bottom-app-bar-anatomy.png) + +1. Container +2. Navigation icon (optional) +3. Floating action button (FAB) (optional) +4. Action item(s) (optional) +5. Overflow menu (optional) + +### Container attributes + +  | Attribute | Related method(s) | Default value +------------- | -------------------- | ------------------------------------------ | ------------- +**Color** | `barTintColor` | `-setBarTintColor:`
`-barTintColor` | White +**Elevation** | `elevation` | `-setElevation:`
`-elevation` | 8 + +### Navigation icon attributes + +  | Attribute | Related method(s) | Default value +--------- | -------------------- | ------------------------------------------ | ------------- +**Icon** | `leadingBarButtonItems`
`trailingBarButtonItems` | `-setLeadingBarButtonItems:`
`-leadingBarButtonItems`
`-setTrailingBarButtonItems:`
`-trailingBarButtonItems` | `nil` + +### FAB attributes + +  | Attribute | Related method(s) | Default value +-------------------------------- | ---------------------------------- | ---------------------------------------------------------------------- | ------------- +**Alignment mode** | `floatingButtonPosition` | `-setFloatingButtonPosition:`
`-floatingButtonPosition` | `.center` +**Elevation** | `floatingButtonElevation` | `-setFloatingButtonElevation:`
`-floatingButtonElevation` | 0 + +## Theming bottom app bars + +`MDCBottomAppBarView` does not currently have a Material Design theming extension or otherwise support theming. Please indicate interest in adding theming support by commenting on [issue #7172](https://github.com/material-components/material-components-ios/issues/7172). diff --git a/components/BottomAppBar/docs/README.md b/components/BottomAppBar/docs/README.md deleted file mode 100644 index e5c27fdffb6..00000000000 --- a/components/BottomAppBar/docs/README.md +++ /dev/null @@ -1,45 +0,0 @@ -# App bars: bottom - - - -A bottom app bar displays navigation and key actions at the bottom of the screen. Bottom app bars -work like [navigation bars](../../NavigationBar), but with the additional option to show a -[floating action button](../../Buttons). - -
- A screenshot of a bottom app bar. -
- - - - - -- - - - -## Overview - -Bottom app bars follow a recommended Material Design interaction design pattern for providing primary and secondary actions that are easily accessible. With a bottom app bar users are more easily able to use single-handed touch interaction with an application since actions are displayed close to the bottom of the screen within easy reach of a user's thumb. - -The bottom app bar includes a floating action button that is intended to provide users with a primary action. Secondary actions are available on a navigation bar that can be customized with several buttons on the left and right sides of the navigation bar. The primary action floating action button is centered on the bottom app bar by default. - -MDCBottomAppBarView should be attached to the bottom of the screen or used in conjunction with an expandable bottom drawer. The MDCBottomAppBarView API includes properties that allow changes to the elevation, position and visibility of the embedded floating action button. - -UIBarButtonItems can be added to the navigation bar of the MDCBottomAppBarView. Leading and trailing navigation items will be shown and hidden based on the position of the floating action button. - -Transitions between floating action button position, elevation and visibility states are animated by default, but can be disabled if desired. - -## Installation - -- [Typical installation](../../../docs/component-installation.md) - -## Usage - -- [Typical use](typical-use.md) - -## Extensions - -- [Color Theming](color-theming.md) - -## Accessibility - -- [Accessibility](accessibility.md) diff --git a/components/BottomAppBar/docs/accessibility.md b/components/BottomAppBar/docs/accessibility.md deleted file mode 100644 index 9abf43ba794..00000000000 --- a/components/BottomAppBar/docs/accessibility.md +++ /dev/null @@ -1,62 +0,0 @@ -To help ensure your bottom app bar is accessible to as many users as possible, please be sure to review the -following recommendations: - -### Set `-accessibilityLabel` - -Set an appropriate -[`accessibilityLabel`](https://developer.apple.com/documentation/uikit/uiaccessibilityelement/1619577-accessibilitylabel) -to all buttons within the bottom app bar. - - -#### Swift - -```swift -bottomAppBar.floatingButton.accessibilityLabel = "Compose" -let trailingButton = UIBarButtonItem() -trailingButton.accessibilityLabel = "Buy" -bottomAppBar.trailingBarButtonItems = [ trailingButton ] -``` - -#### Objective-C - -```objc -bottomAppBar.floatingButton.accessibilityLabel = @"Compose"; -UIBarButtonItem *trailingButton = - [[UIBarButtonItem alloc] initWithTitle:nil - style:UIBarButtonItemStylePlain - target:self - action:@selector(didTapTrailing:)]; -trailingButton.accessibilityLabel = @"Buy"; -[bottomAppBar setTrailingBarButtonItems:@[ trailingButton ]]; -``` - - -### Set `-accessibilityHint` - -Set an appropriate -[`accessibilityHint`](https://developer.apple.com/documentation/objectivec/nsobject/1615093-accessibilityhint) -to all buttons within the bottom app bar. - - -#### Swift - -```swift -bottomAppBar.floatingButton.accessibilityHint = "Create new email" -let trailingButton = UIBarButtonItem() -trailingButton.accessibilityHint = "Purchase the item" -bottomAppBar.trailingBarButtonItems = [ trailingButton ] -``` - -#### Objective-C - -```objc -bottomAppBar.floatingButton.accessibilityHint = @"Create new email"; -UIBarButtonItem *trailingButton = - [[UIBarButtonItem alloc] initWithTitle:nil - style:UIBarButtonItemStylePlain - target:self - action:@selector(didTapTrailing:)]; -trailingButton.accessibilityHint = @"Purchase the item"; -[bottomAppBar setTrailingBarButtonItems:@[ trailingButton ]]; -``` - diff --git a/components/BottomAppBar/docs/assets/bottom-app-bar-anatomy.png b/components/BottomAppBar/docs/assets/bottom-app-bar-anatomy.png new file mode 100644 index 0000000000000000000000000000000000000000..2808d532df4e8032c51f11ff2ef4034dfa114e18 GIT binary patch literal 49310 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 literal 0 HcmV?d00001 diff --git a/components/BottomAppBar/docs/assets/bottom-app-bar-hero.png b/components/BottomAppBar/docs/assets/bottom-app-bar-hero.png new file mode 100644 index 0000000000000000000000000000000000000000..58819cfbf20675972c4c9b7caef1b1ca33cafb57 GIT binary patch literal 30504 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 G>;C|EI~H94 literal 0 HcmV?d00001 diff --git a/components/BottomAppBar/docs/color-theming.md b/components/BottomAppBar/docs/color-theming.md deleted file mode 100644 index 5dcb19ec69f..00000000000 --- a/components/BottomAppBar/docs/color-theming.md +++ /dev/null @@ -1,4 +0,0 @@ -### Color Theming - -MDCBottomAppBarView does not yet have a Material Design color system theming extension. Please -indicate interest by commenting on https://github.com/material-components/material-components-ios/issues/7172. diff --git a/components/BottomAppBar/docs/typical-use.md b/components/BottomAppBar/docs/typical-use.md deleted file mode 100644 index 8e6c5a9d61b..00000000000 --- a/components/BottomAppBar/docs/typical-use.md +++ /dev/null @@ -1,4 +0,0 @@ -### Typical use - -MDCBottomAppBarView can be added to a view hierarchy like any UIView. Material Design guidelines -recommend always placing the bottom app bar at the bottom of the screen. From 77c7bfae5f113d90971c95f20fccb82cd3fe1cd2 Mon Sep 17 00:00:00 2001 From: Bryan Oltman Date: Wed, 22 Jul 2020 14:59:45 -0700 Subject: [PATCH 15/43] [HeaderStackView] Delete deprecated MDCHeaderStackViewColorThemer PiperOrigin-RevId: 322662598 --- .../MDCHeaderStackViewColorThemer.h | 43 ------------------- .../MDCHeaderStackViewColorThemer.m | 30 ------------- .../MaterialHeaderStackView+ColorThemer.h | 15 ------- 3 files changed, 88 deletions(-) delete mode 100644 components/HeaderStackView/src/ColorThemer/MDCHeaderStackViewColorThemer.h delete mode 100644 components/HeaderStackView/src/ColorThemer/MDCHeaderStackViewColorThemer.m delete mode 100644 components/HeaderStackView/src/ColorThemer/MaterialHeaderStackView+ColorThemer.h diff --git a/components/HeaderStackView/src/ColorThemer/MDCHeaderStackViewColorThemer.h b/components/HeaderStackView/src/ColorThemer/MDCHeaderStackViewColorThemer.h deleted file mode 100644 index 0045523b58a..00000000000 --- a/components/HeaderStackView/src/ColorThemer/MDCHeaderStackViewColorThemer.h +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright 2017-present the Material Components for iOS authors. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#import "MaterialHeaderStackView.h" -#import "MaterialThemes.h" - -#import - -#pragma mark - Soon to be deprecated - -/** - A color themer for instances of MDCHeaderStackView. - */ -__deprecated_msg("No replacement exists. Please comment on" - " https://github.com/material-components/material-components-ios/issues/7172" - " in order to indicate interest in a replacement API.") - @interface MDCHeaderStackViewColorThemer : NSObject - -/** - Applies a color scheme's properties to an MDCHeaderStackView. - - @warning This class will soon be deprecated. There will be no replacement API. Consider theming - your flexible header view or app bar instead. - Learn more at docs/theming.md#migration-guide-themers-to-theming-extensions - - @param colorScheme The color scheme to apply to the component instance. - @param headerStackView A component instance to which the color scheme should be applied. - */ -+ (void)applyColorScheme:(nonnull id)colorScheme - toHeaderStackView:(nonnull MDCHeaderStackView *)headerStackView; - -@end diff --git a/components/HeaderStackView/src/ColorThemer/MDCHeaderStackViewColorThemer.m b/components/HeaderStackView/src/ColorThemer/MDCHeaderStackViewColorThemer.m deleted file mode 100644 index c51cf833076..00000000000 --- a/components/HeaderStackView/src/ColorThemer/MDCHeaderStackViewColorThemer.m +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright 2017-present the Material Components for iOS authors. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#import "MDCHeaderStackViewColorThemer.h" - -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-implementations" -@implementation MDCHeaderStackViewColorThemer -#pragma clang diagnostic pop - -+ (void)applyColorScheme:(id)colorScheme - toHeaderStackView:(MDCHeaderStackView *)headerStackView { - if ([colorScheme respondsToSelector:@selector(primaryLightColor)]) { - headerStackView.topBar.backgroundColor = colorScheme.primaryLightColor; - } - headerStackView.bottomBar.backgroundColor = colorScheme.primaryColor; -} - -@end diff --git a/components/HeaderStackView/src/ColorThemer/MaterialHeaderStackView+ColorThemer.h b/components/HeaderStackView/src/ColorThemer/MaterialHeaderStackView+ColorThemer.h deleted file mode 100644 index 5dcf9fc5b52..00000000000 --- a/components/HeaderStackView/src/ColorThemer/MaterialHeaderStackView+ColorThemer.h +++ /dev/null @@ -1,15 +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. - -#import "MDCHeaderStackViewColorThemer.h" From 0495b7987138dab2608f57772fdc5869481e59a7 Mon Sep 17 00:00:00 2001 From: Bryan Oltman Date: Wed, 22 Jul 2020 15:09:58 -0700 Subject: [PATCH 16/43] [Slider] Add EarlGrey test to expose bug where tapping on slider track opposite anchor point doesn't trigger UIControlEventValueChanged PiperOrigin-RevId: 322664882 --- .../SliderEarlGreyTestViewController.m | 74 +++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100644 components/Slider/examples/SliderEarlGreyTestViewController.m diff --git a/components/Slider/examples/SliderEarlGreyTestViewController.m b/components/Slider/examples/SliderEarlGreyTestViewController.m new file mode 100644 index 00000000000..8aad107294f --- /dev/null +++ b/components/Slider/examples/SliderEarlGreyTestViewController.m @@ -0,0 +1,74 @@ +// 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 "MaterialSlider.h" + +@interface SliderEarlGreyTestViewController : UIViewController +@property(nonatomic, strong, nullable) UILabel *valueLabel; +@property(nonatomic, strong, nullable) MDCSlider *slider; +@end + +@implementation SliderEarlGreyTestViewController + +- (void)viewDidLoad { + [super viewDidLoad]; + + self.view.backgroundColor = UIColor.whiteColor; + + self.slider = [[MDCSlider alloc] init]; + [self.slider addTarget:self + action:@selector(sliderValueChanged) + forControlEvents:UIControlEventValueChanged]; + self.slider.minimumValue = 0; + self.slider.maximumValue = 10; + self.slider.filledTrackAnchorValue = 5; + self.slider.value = 5; + self.slider.accessibilityIdentifier = @"slider"; + + self.valueLabel = [[UILabel alloc] init]; + self.valueLabel.text = @"5"; + self.valueLabel.textAlignment = NSTextAlignmentCenter; + self.valueLabel.textColor = UIColor.blackColor; + self.valueLabel.accessibilityIdentifier = @"slider_value_label"; + + UIStackView *stackView = [[UIStackView alloc] init]; + stackView.axis = UILayoutConstraintAxisVertical; + stackView.spacing = 10; + stackView.translatesAutoresizingMaskIntoConstraints = NO; + [stackView addArrangedSubview:self.valueLabel]; + [stackView addArrangedSubview:self.slider]; + + [self.view addSubview:stackView]; + [stackView.centerXAnchor constraintEqualToAnchor:self.view.centerXAnchor].active = YES; + [stackView.centerYAnchor constraintEqualToAnchor:self.view.centerYAnchor].active = YES; + [stackView.widthAnchor constraintEqualToConstant:300].active = YES; +} + +- (void)sliderValueChanged { + self.valueLabel.text = [NSString stringWithFormat:@"%.f", self.slider.value]; +} + +#pragma mark - CatalogByConvention + ++ (NSDictionary *)catalogMetadata { + return @{ + @"breadcrumbs" : @[ @"Slider", @"Earl Grey Testbed" ], + @"primaryDemo" : @NO, + @"presentable" : @NO, + }; +} + +@end From fb4f43d36c7b1887f8396dada4d88177ea32d3d7 Mon Sep 17 00:00:00 2001 From: Jeff Verkoeyen Date: Wed, 22 Jul 2020 17:20:57 -0700 Subject: [PATCH 17/43] [Shapes] Add unit tests demonstrating a divide by zero error. This error can happen when an MDCShapedShadowLayer has an empty frame and a positive shapedBorderWidth value, and a path is assigned. There is a divide by zero in the logic introduced in cl/322615641. In a subsequent change I will fix the bug and update the test accordingly. PiperOrigin-RevId: 322689400 --- .../unit/MDCShapedShadowLayerPathTests.m | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 components/Shapes/tests/unit/MDCShapedShadowLayerPathTests.m diff --git a/components/Shapes/tests/unit/MDCShapedShadowLayerPathTests.m b/components/Shapes/tests/unit/MDCShapedShadowLayerPathTests.m new file mode 100644 index 00000000000..76b7ce55573 --- /dev/null +++ b/components/Shapes/tests/unit/MDCShapedShadowLayerPathTests.m @@ -0,0 +1,49 @@ +#import "MaterialShapes.h" + +#import + +@interface MDCShapedShadowLayer (UnitTesting) + +- (void)setPath:(CGPathRef)path; + +@end + +@interface MDCShapedShadowLayerPathTests : XCTestCase +@end + +@implementation MDCShapedShadowLayerPathTests + +- (void)testEmptyFrameWithNonEmptyPathGeneratesColorLayerPathMatchingPath { + // Given + MDCShapedShadowLayer *shadowLayer = [[MDCShapedShadowLayer alloc] init]; + UIBezierPath *bezierPath = [UIBezierPath bezierPathWithRect:CGRectMake(0, 0, 100, 100)]; + + // When + shadowLayer.frame = CGRectZero; + shadowLayer.path = bezierPath.CGPath; + + // Then + XCTAssertTrue(CGPathEqualToPath(shadowLayer.colorLayer.path, bezierPath.CGPath)); +} + +- (void) + testEmptyFrameWithNonEmptyPathAndPositiveShapedBorderWidthGeneratesColorLayerPathInsetByHalfOfLineWidth { + // Given + MDCShapedShadowLayer *shadowLayer = [[MDCShapedShadowLayer alloc] init]; + UIBezierPath *bezierPath = [UIBezierPath bezierPathWithRect:CGRectMake(0, 0, 100, 100)]; + + // When + shadowLayer.frame = CGRectZero; + shadowLayer.shapedBorderWidth = 2; + shadowLayer.path = bezierPath.CGPath; + + // Then + // TODO(b/161932830): This should not return NULL. The path assignment above is causing a runtime + // failure to a divide by zero, with the following error message: + // "[Unknown process name] Error: this application, or a library it uses, has passed an invalid + // numeric value (NaN, or not-a-number) to CoreGraphics API and this value is being ignored. + // Please fix this problem." + XCTAssertTrue(shadowLayer.colorLayer.path == NULL); +} + +@end From 2db3fd1ced49aefcd3d440e91e7fbc675967053d Mon Sep 17 00:00:00 2001 From: Jeff Verkoeyen Date: Thu, 23 Jul 2020 05:35:21 -0700 Subject: [PATCH 18/43] [Shapes] Fix divide by zero bug. This bug was happening when an MDCShapedShadowLayer has an empty frame and a positive shapedBorderWidth value, and a path is assigned. The MDCShapedShadowLayer was attempting to calculate a value in relation to the bounds of the MDCShapedShadowLayer, which in turn was resulting in a divide by zero. This change adds safeguards around the divisions to ensure that a division does not occur if the width or height are sufficiently close to zero. PiperOrigin-RevId: 322768484 --- components/Shapes/src/MDCShapedShadowLayer.m | 12 +++++++++--- .../tests/unit/MDCShapedShadowLayerPathTests.m | 11 ++++------- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/components/Shapes/src/MDCShapedShadowLayer.m b/components/Shapes/src/MDCShapedShadowLayer.m index 745aa9d9e67..9d1e4ee0ddb 100644 --- a/components/Shapes/src/MDCShapedShadowLayer.m +++ b/components/Shapes/src/MDCShapedShadowLayer.m @@ -17,6 +17,9 @@ #import "MDCShapeGenerating.h" #import "MaterialColor.h" +// An epsilon for use with width/height values. +static const CGFloat kDimensionalEpsilon = 0.001; + @implementation MDCShapedShadowLayer - (instancetype)init { @@ -117,9 +120,12 @@ - (void)generateColorPathGivenLineWidth { CGRect insetBounds = CGRectInset(standardizedBounds, halfOfBorderWidth, halfOfBorderWidth); CGAffineTransform transform = CGAffineTransformMakeTranslation(halfOfBorderWidth, halfOfBorderWidth); - transform = CGAffineTransformScale( - transform, CGRectGetWidth(insetBounds) / CGRectGetWidth(standardizedBounds), - CGRectGetHeight(insetBounds) / CGRectGetHeight(standardizedBounds)); + CGFloat width = CGRectGetWidth(standardizedBounds); + CGFloat height = CGRectGetHeight(standardizedBounds); + if (width > kDimensionalEpsilon && height > kDimensionalEpsilon) { + transform = CGAffineTransformScale(transform, CGRectGetWidth(insetBounds) / width, + CGRectGetHeight(insetBounds) / height); + } _colorLayer.path = CGPathCreateCopyByTransformingPath(_colorLayer.path, &transform); } diff --git a/components/Shapes/tests/unit/MDCShapedShadowLayerPathTests.m b/components/Shapes/tests/unit/MDCShapedShadowLayerPathTests.m index 76b7ce55573..d026bc5f68d 100644 --- a/components/Shapes/tests/unit/MDCShapedShadowLayerPathTests.m +++ b/components/Shapes/tests/unit/MDCShapedShadowLayerPathTests.m @@ -27,7 +27,7 @@ - (void)testEmptyFrameWithNonEmptyPathGeneratesColorLayerPathMatchingPath { } - (void) - testEmptyFrameWithNonEmptyPathAndPositiveShapedBorderWidthGeneratesColorLayerPathInsetByHalfOfLineWidth { + testEmptyFrameWithNonEmptyPathAndPositiveShapedBorderWidthGeneratesColorLayerPathOffsetByHalfOfLineWidth { // Given MDCShapedShadowLayer *shadowLayer = [[MDCShapedShadowLayer alloc] init]; UIBezierPath *bezierPath = [UIBezierPath bezierPathWithRect:CGRectMake(0, 0, 100, 100)]; @@ -38,12 +38,9 @@ - (void)testEmptyFrameWithNonEmptyPathGeneratesColorLayerPathMatchingPath { shadowLayer.path = bezierPath.CGPath; // Then - // TODO(b/161932830): This should not return NULL. The path assignment above is causing a runtime - // failure to a divide by zero, with the following error message: - // "[Unknown process name] Error: this application, or a library it uses, has passed an invalid - // numeric value (NaN, or not-a-number) to CoreGraphics API and this value is being ignored. - // Please fix this problem." - XCTAssertTrue(shadowLayer.colorLayer.path == NULL); + // Note that the X and Y values here are shifted by half of the shaped border width. + UIBezierPath *insetPath = [UIBezierPath bezierPathWithRect:CGRectMake(1, 1, 100, 100)]; + XCTAssertTrue(CGPathEqualToPath(shadowLayer.colorLayer.path, insetPath.CGPath)); } @end From e4efea4264dd431768cc491c8e442624f4d39937 Mon Sep 17 00:00:00 2001 From: Wenyu Zhang Date: Thu, 23 Jul 2020 07:34:47 -0700 Subject: [PATCH 19/43] [private/ThumbTrack] Add snapshot tests to MDCThumbTrack. PiperOrigin-RevId: 322783494 --- .../snapshot/MDCThumbTrackSnapshotTests.m | 99 +++++++++++++++++++ 1 file changed, 99 insertions(+) create mode 100644 components/private/ThumbTrack/tests/snapshot/MDCThumbTrackSnapshotTests.m diff --git a/components/private/ThumbTrack/tests/snapshot/MDCThumbTrackSnapshotTests.m b/components/private/ThumbTrack/tests/snapshot/MDCThumbTrackSnapshotTests.m new file mode 100644 index 00000000000..2536b098dac --- /dev/null +++ b/components/private/ThumbTrack/tests/snapshot/MDCThumbTrackSnapshotTests.m @@ -0,0 +1,99 @@ +// 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 "MaterialSnapshot.h" +#import "MaterialThumbTrack.h" + +@interface MDCThumbTrackSnapshotTests : MDCSnapshotTestCase + +@property(nonatomic, strong) MDCThumbTrack *thumbTrack; + +@end + +@implementation MDCThumbTrackSnapshotTests + +- (void)setUp { + [super setUp]; + + // Uncomment below to recreate all the goldens (or add the following line to the specific + // test you wish to recreate the golden for). + // self.recordMode = YES; + + self.thumbTrack = [[MDCThumbTrack alloc] initWithFrame:CGRectMake(0, 0, 120, 30)]; + self.thumbTrack.thumbRadius = 10; + self.thumbTrack.trackHeight = 3; + self.thumbTrack.trackOnColor = UIColor.blueColor; + self.thumbTrack.trackOffColor = UIColor.redColor; + self.thumbTrack.value = 0; + self.thumbTrack.minimumValue = -10; + self.thumbTrack.maximumValue = 10; + self.thumbTrack.backgroundColor = UIColor.whiteColor; + self.thumbTrack.thumbEnabledColor = UIColor.blackColor; +} + +- (void)tearDown { + self.thumbTrack = nil; + + [super tearDown]; +} + +- (void)generateSnapshotWithBorderAndVerifyForView:(MDCThumbTrack *)view { + // TODO(b/161927075): Refactor when this functionality is available in MaterialSnapshot. + UIView *thumbTrackFrameView = [[UIView alloc] initWithFrame:view.bounds]; + [view addSubview:thumbTrackFrameView]; + thumbTrackFrameView.layer.borderColor = UIColor.yellowColor.CGColor; + thumbTrackFrameView.layer.borderWidth = 0.5f; + + UIView *thumbViewFrameView = [[UIView alloc] initWithFrame:view.thumbView.bounds]; + [view.thumbView addSubview:thumbViewFrameView]; + thumbViewFrameView.layer.borderColor = UIColor.yellowColor.CGColor; + thumbViewFrameView.layer.borderWidth = 0.5f; + + UIView *snapshotView = [view mdc_addToBackgroundView]; + [self snapshotVerifyView:snapshotView]; + + [thumbViewFrameView removeFromSuperview]; + [thumbTrackFrameView removeFromSuperview]; +} + +#pragma mark - Tests + +- (void)testThumbTrackAtMidpoint { + // When + self.thumbTrack.value = self.thumbTrack.minimumValue + + (self.thumbTrack.maximumValue - self.thumbTrack.minimumValue) / 2; + + // Then + [self generateSnapshotWithBorderAndVerifyForView:self.thumbTrack]; +} + +- (void)testThumbTrackAtMinimum { + // When + self.thumbTrack.value = self.thumbTrack.minimumValue; + + // Then + [self generateSnapshotWithBorderAndVerifyForView:self.thumbTrack]; +} + +- (void)testThumbTrackAtMaximum { + // When + self.thumbTrack.value = self.thumbTrack.maximumValue; + + // Then + [self generateSnapshotWithBorderAndVerifyForView:self.thumbTrack]; +} + +@end From 89ddea0cf1a5d6b646be808f288978318d7ce0dc Mon Sep 17 00:00:00 2001 From: Galia Kaufman Date: Thu, 23 Jul 2020 08:29:36 -0700 Subject: [PATCH 20/43] [Dialogs] Improved titleIconView example with animation. PiperOrigin-RevId: 322791317 --- ...alogsTitleImageExampleViewController.swift | 61 ++++++++++++++----- 1 file changed, 47 insertions(+), 14 deletions(-) diff --git a/components/Dialogs/examples/DialogsTitleImageExampleViewController.swift b/components/Dialogs/examples/DialogsTitleImageExampleViewController.swift index 2da49caad42..5a4370fd576 100644 --- a/components/Dialogs/examples/DialogsTitleImageExampleViewController.swift +++ b/components/Dialogs/examples/DialogsTitleImageExampleViewController.swift @@ -280,22 +280,55 @@ extension DialogsTitleImageExampleViewController { extension UIView { fileprivate func animateIn( - duration: TimeInterval = 2, options: UIView.AnimationOptions = [.curveEaseOut] + duration: TimeInterval = 4, + repeating: Bool = true, + options: UIView.AnimationOptions = [.curveEaseOut] ) { - alpha = 0.2 - self.transform = self.transform - .translatedBy(x: -self.frame.origin.x / 2, y: 0) - .scaledBy(x: 0.2, y: 0.2) - .rotated(by: CGFloat.pi) - UIView.animate( - withDuration: duration, - delay: 0, - usingSpringWithDamping: 0.3, - initialSpringVelocity: 0.3, - options: options, + self.alpha = 1 + let initialTransform = self.transform.translatedBy(x: -150, y: 0) + self.transform = initialTransform + let transform1 = + initialTransform + .concatenating(CGAffineTransform(translationX: 80, y: 0)) + .rotated(by: CGFloat.pi * 0.8) + let transform2 = + initialTransform + .concatenating(CGAffineTransform(translationX: 160, y: 0)) + .rotated(by: CGFloat.pi * 1.5) + let transform3 = + initialTransform + .concatenating(CGAffineTransform(translationX: 240, y: 0)) + let transform4 = + initialTransform + .concatenating(CGAffineTransform(translationX: 360, y: 0)) + .rotated(by: CGFloat.pi * 0.75) + + let animationOptions: UInt + if repeating { + animationOptions = + UIView.AnimationOptions.curveLinear.rawValue | UIView.AnimationOptions.repeat.rawValue + } else { + animationOptions = UIView.AnimationOptions.curveLinear.rawValue + } + + let keyFrameAnimationOptions = UIView.KeyframeAnimationOptions(rawValue: animationOptions) + + UIView.animateKeyframes( + withDuration: duration, delay: 0, + options: [keyFrameAnimationOptions, .calculationModeLinear], animations: { - self.transform = CGAffineTransform.identity - self.alpha = 1 + UIView.addKeyframe(withRelativeStartTime: 0, relativeDuration: 0.14) { // 0.375 + self.transform = transform1 + } + UIView.addKeyframe(withRelativeStartTime: 0.14, relativeDuration: 0.14) { // 0.375 + self.transform = transform2 + } + UIView.addKeyframe(withRelativeStartTime: 0.28, relativeDuration: 0.15) { //0.25) { + self.transform = transform3 + } + UIView.addKeyframe(withRelativeStartTime: 0.75, relativeDuration: 0.15) { //0.25) { + self.transform = transform4 + } }, completion: nil) } } From ae6938670e6083c94f300cbd2e9484d69ec060a4 Mon Sep 17 00:00:00 2001 From: Andrew Overton Date: Thu, 23 Jul 2020 08:32:14 -0700 Subject: [PATCH 21/43] [TextControls] Make filled positioning reference subclass NSObject PiperOrigin-RevId: 322791755 --- ...ontrolVerticalPositioningReferenceFilled.h | 3 +- ...ontrolVerticalPositioningReferenceFilled.m | 144 ++++++++++++++++-- 2 files changed, 130 insertions(+), 17 deletions(-) diff --git a/components/private/TextControlsPrivate/src/FilledStyle/MDCTextControlVerticalPositioningReferenceFilled.h b/components/private/TextControlsPrivate/src/FilledStyle/MDCTextControlVerticalPositioningReferenceFilled.h index 1e2185dda38..c456006b0ce 100644 --- a/components/private/TextControlsPrivate/src/FilledStyle/MDCTextControlVerticalPositioningReferenceFilled.h +++ b/components/private/TextControlsPrivate/src/FilledStyle/MDCTextControlVerticalPositioningReferenceFilled.h @@ -19,8 +19,7 @@ #import "MDCTextControlVerticalPositioningReferenceUnderlined.h" @interface MDCTextControlVerticalPositioningReferenceFilled - : MDCTextControlVerticalPositioningReferenceUnderlined < - MDCTextControlVerticalPositioningReference> + : NSObject - (instancetype)initWithFloatingFontLineHeight:(CGFloat)floatingLabelHeight normalFontLineHeight:(CGFloat)normalFontLineHeight diff --git a/components/private/TextControlsPrivate/src/FilledStyle/MDCTextControlVerticalPositioningReferenceFilled.m b/components/private/TextControlsPrivate/src/FilledStyle/MDCTextControlVerticalPositioningReferenceFilled.m index 91966e7c3f8..26f56972434 100644 --- a/components/private/TextControlsPrivate/src/FilledStyle/MDCTextControlVerticalPositioningReferenceFilled.m +++ b/components/private/TextControlsPrivate/src/FilledStyle/MDCTextControlVerticalPositioningReferenceFilled.m @@ -15,18 +15,36 @@ #import "MDCTextControlVerticalPositioningReferenceFilled.h" #import "MDCTextControlVerticalPositioningReference.h" +static const CGFloat kMinPaddingAroundTextWhenNoFloatingLabel = 6.0f; +static const CGFloat kMaxPaddingAroundTextWhenNoFloatingLabel = 10.0f; +static const CGFloat kMinPaddingBetweenContainerTopAndFloatingLabel = 6.0f; +static const CGFloat kMaxPaddingBetweenContainerTopAndFloatingLabel = 10.0f; +static const CGFloat kMinPaddingBetweenFloatingLabelAndEditingText = 3.0f; +static const CGFloat kMaxPaddingBetweenFloatingLabelAndEditingText = 6.0f; +static const CGFloat kMinPaddingBetweenEditingTextAndContainerBottom = 6.0f; +static const CGFloat kMaxPaddingBetweenEditingTextAndContainerBottom = 10.0f; +static const CGFloat kMinPaddingAroundAssistiveLabels = 3.0f; +static const CGFloat kMaxPaddingAroundAssistiveLabels = 6.0f; + /** For slightly more context on what this class is doing look at MDCTextControlVerticalPositioningReferenceBase. It's very similar and has some comments. Maybe at some point all the positioning references should be refactored to share a superclass, because there's currently a lot of duplicated code among the three of them. */ -@interface MDCTextControlVerticalPositioningReferenceFilled () -@end - @implementation MDCTextControlVerticalPositioningReferenceFilled +@synthesize paddingBetweenContainerTopAndFloatingLabel = + _paddingBetweenContainerTopAndFloatingLabel; @synthesize paddingBetweenContainerTopAndNormalLabel = _paddingBetweenContainerTopAndNormalLabel; +@synthesize paddingBetweenFloatingLabelAndEditingText = _paddingBetweenFloatingLabelAndEditingText; +@synthesize paddingBetweenEditingTextAndContainerBottom = + _paddingBetweenEditingTextAndContainerBottom; +@synthesize paddingAboveAssistiveLabels = _paddingAboveAssistiveLabels; +@synthesize paddingBelowAssistiveLabels = _paddingBelowAssistiveLabels; +@synthesize containerHeightWithFloatingLabel = _containerHeightWithFloatingLabel; +@synthesize containerHeightWithoutFloatingLabel = _containerHeightWithoutFloatingLabel; +@synthesize paddingAroundTextWhenNoFloatingLabel = _paddingAroundTextWhenNoFloatingLabel; - (instancetype)initWithFloatingFontLineHeight:(CGFloat)floatingLabelHeight normalFontLineHeight:(CGFloat)normalFontLineHeight @@ -34,25 +52,89 @@ - (instancetype)initWithFloatingFontLineHeight:(CGFloat)floatingLabelHeight numberOfTextRows:(CGFloat)numberOfTextRows density:(CGFloat)density preferredContainerHeight:(CGFloat)preferredContainerHeight { - self = [super initWithFloatingFontLineHeight:floatingLabelHeight - normalFontLineHeight:normalFontLineHeight - textRowHeight:textRowHeight - numberOfTextRows:numberOfTextRows - density:density - preferredContainerHeight:preferredContainerHeight]; + self = [super init]; if (self) { - BOOL isMultiline = numberOfTextRows > 1 || numberOfTextRows == 0; + BOOL isMultilineTextControl = numberOfTextRows > 1 || numberOfTextRows == 0; + CGFloat clampedDensity = MDCTextControlClampDensity(density); + + _paddingBetweenContainerTopAndFloatingLabel = MDCTextControlPaddingValueWithMinimumPadding( + kMinPaddingBetweenContainerTopAndFloatingLabel, + kMaxPaddingBetweenContainerTopAndFloatingLabel, clampedDensity); + + _paddingBetweenFloatingLabelAndEditingText = MDCTextControlPaddingValueWithMinimumPadding( + kMinPaddingBetweenFloatingLabelAndEditingText, + kMaxPaddingBetweenFloatingLabelAndEditingText, clampedDensity); + + _paddingBetweenEditingTextAndContainerBottom = MDCTextControlPaddingValueWithMinimumPadding( + kMinPaddingBetweenEditingTextAndContainerBottom, + kMaxPaddingBetweenEditingTextAndContainerBottom, clampedDensity); + + _paddingAboveAssistiveLabels = MDCTextControlPaddingValueWithMinimumPadding( + kMinPaddingAroundAssistiveLabels, kMaxPaddingAroundAssistiveLabels, clampedDensity); + _paddingBelowAssistiveLabels = _paddingAboveAssistiveLabels; + + CGFloat defaultContainerHeightForFloatingLabel = + MDCTextControlCalculateContainerHeightWithFloatingLabelHeight( + floatingLabelHeight, textRowHeight, numberOfTextRows, + _paddingBetweenContainerTopAndFloatingLabel, _paddingBetweenFloatingLabelAndEditingText, + _paddingBetweenEditingTextAndContainerBottom); + BOOL preferredContainerHeightIsValidForFloatingLabel = + preferredContainerHeight > defaultContainerHeightForFloatingLabel; + if (preferredContainerHeightIsValidForFloatingLabel) { + _containerHeightWithFloatingLabel = preferredContainerHeight; + BOOL shouldUpdatePaddingValuesToMeetMinimumHeight = !isMultilineTextControl; + if (shouldUpdatePaddingValuesToMeetMinimumHeight) { + CGFloat difference = preferredContainerHeight - defaultContainerHeightForFloatingLabel; + CGFloat sumOfPaddingValues = _paddingBetweenContainerTopAndFloatingLabel + + _paddingBetweenFloatingLabelAndEditingText + + _paddingBetweenEditingTextAndContainerBottom; + _paddingBetweenContainerTopAndFloatingLabel = + _paddingBetweenContainerTopAndFloatingLabel + + ((_paddingBetweenContainerTopAndFloatingLabel / sumOfPaddingValues) * difference); + _paddingBetweenFloatingLabelAndEditingText = + _paddingBetweenFloatingLabelAndEditingText + + ((_paddingBetweenFloatingLabelAndEditingText / sumOfPaddingValues) * difference); + _paddingBetweenEditingTextAndContainerBottom = + _paddingBetweenEditingTextAndContainerBottom + + ((_paddingBetweenEditingTextAndContainerBottom / sumOfPaddingValues) * difference); + } + } else { + _containerHeightWithFloatingLabel = defaultContainerHeightForFloatingLabel; + } + + _paddingAroundTextWhenNoFloatingLabel = MDCTextControlPaddingValueWithMinimumPadding( + kMinPaddingAroundTextWhenNoFloatingLabel, kMaxPaddingAroundTextWhenNoFloatingLabel, + clampedDensity); + + CGFloat defaultContainerHeightForNoFloatingLabel = + MDCTextControlCalculateContainerHeightWhenNoFloatingLabelWithTextRowHeight( + textRowHeight, numberOfTextRows, _paddingAroundTextWhenNoFloatingLabel); + BOOL preferredContainerHeightIsValidForNoFloatingLabel = + preferredContainerHeight > defaultContainerHeightForNoFloatingLabel; + if (preferredContainerHeightIsValidForNoFloatingLabel) { + _containerHeightWithoutFloatingLabel = preferredContainerHeight; + BOOL shouldUpdatePaddingValuesToMeetMinimumHeight = !isMultilineTextControl; + if (shouldUpdatePaddingValuesToMeetMinimumHeight) { + CGFloat difference = preferredContainerHeight - defaultContainerHeightForNoFloatingLabel; + CGFloat sumOfPaddingValues = _paddingAroundTextWhenNoFloatingLabel * 2.0f; + _paddingAroundTextWhenNoFloatingLabel = + _paddingAroundTextWhenNoFloatingLabel + + ((_paddingAroundTextWhenNoFloatingLabel / sumOfPaddingValues) * difference); + } + } else { + _containerHeightWithoutFloatingLabel = defaultContainerHeightForNoFloatingLabel; + } + CGFloat halfOfNormalFontLineHeight = (CGFloat)0.5 * normalFontLineHeight; - if (isMultiline) { + if (isMultilineTextControl) { CGFloat heightWithOneRow = MDCTextControlCalculateContainerHeightWithFloatingLabelHeight( - floatingLabelHeight, textRowHeight, 1, self.paddingBetweenContainerTopAndFloatingLabel, - self.paddingBetweenFloatingLabelAndEditingText, - self.paddingBetweenEditingTextAndContainerBottom); + floatingLabelHeight, textRowHeight, 1, _paddingBetweenContainerTopAndFloatingLabel, + _paddingBetweenFloatingLabelAndEditingText, _paddingBetweenEditingTextAndContainerBottom); CGFloat halfOfHeightWithOneRow = (CGFloat)0.5 * heightWithOneRow; _paddingBetweenContainerTopAndNormalLabel = halfOfHeightWithOneRow - halfOfNormalFontLineHeight; } else { - CGFloat halfOfContainerHeight = (CGFloat)0.5 * self.containerHeightWithFloatingLabel; + CGFloat halfOfContainerHeight = (CGFloat)0.5 * _containerHeightWithFloatingLabel; _paddingBetweenContainerTopAndNormalLabel = halfOfContainerHeight - halfOfNormalFontLineHeight; } @@ -60,8 +142,40 @@ - (instancetype)initWithFloatingFontLineHeight:(CGFloat)floatingLabelHeight return self; } +- (CGFloat)paddingBetweenContainerTopAndFloatingLabel { + return _paddingBetweenContainerTopAndFloatingLabel; +} + - (CGFloat)paddingBetweenContainerTopAndNormalLabel { return _paddingBetweenContainerTopAndNormalLabel; } +- (CGFloat)paddingBetweenFloatingLabelAndEditingText { + return _paddingBetweenFloatingLabelAndEditingText; +} + +- (CGFloat)paddingBetweenEditingTextAndContainerBottom { + return _paddingBetweenEditingTextAndContainerBottom; +} + +- (CGFloat)paddingAboveAssistiveLabels { + return _paddingAboveAssistiveLabels; +} + +- (CGFloat)paddingBelowAssistiveLabels { + return _paddingBelowAssistiveLabels; +} + +- (CGFloat)containerHeightWithFloatingLabel { + return _containerHeightWithFloatingLabel; +} + +- (CGFloat)containerHeightWithoutFloatingLabel { + return _containerHeightWithoutFloatingLabel; +} + +- (CGFloat)paddingAroundTextWhenNoFloatingLabel { + return _paddingAroundTextWhenNoFloatingLabel; +} + @end From c5dbf01d5a072596ee9447a67e6dff5090936ea7 Mon Sep 17 00:00:00 2001 From: Bryan Oltman Date: Thu, 23 Jul 2020 08:42:22 -0700 Subject: [PATCH 22/43] [Slider] Fix an issue where sliders with non-min/max anchor points do not publish a UIControlEventValueChanged event when a track tap causes the value to cross the anchor point. PiperOrigin-RevId: 322793502 --- components/private/ThumbTrack/src/MDCThumbTrack.m | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/components/private/ThumbTrack/src/MDCThumbTrack.m b/components/private/ThumbTrack/src/MDCThumbTrack.m index 14d25e50c63..1041109d69d 100644 --- a/components/private/ThumbTrack/src/MDCThumbTrack.m +++ b/components/private/ThumbTrack/src/MDCThumbTrack.m @@ -675,11 +675,19 @@ - (void)updateThumbTrackAnimated:(BOOL)animated delay:0 options:options animations:^{ - self.value = self.filledTrackAnchorValue; + // Set _value ivar instead of property to avoid conflicts with logic that + // sends UIControlEventValueChanged. + + // Setting self.value programmatically here causes _lastDispatchedValue to + // be updated to the new value before sendDiscreteChangeAction executes. + // sendDiscreteChangeAction uses _lastDispatchedValue to ensure that + // UIControlEventValueChanged actions aren't sent as a result of + // programmatic changes to the value property. + _value = self.filledTrackAnchorValue; [self updateViewsMainIsAnimated:animated withDuration:animationDurationToAnchor animationOptions:options]; - self.value = currentValue; + _value = currentValue; } completion:afterCrossingAnchorAnimation]; } else { From 49b441e84976df1d1f70b1809c2630c5073f1a44 Mon Sep 17 00:00:00 2001 From: Andrew Overton Date: Thu, 23 Jul 2020 13:41:08 -0700 Subject: [PATCH 23/43] [TextControls] Prepare to expose preferredContainerHeight on text controls PiperOrigin-RevId: 322856550 --- .../src/BaseTextAreas/MDCBaseTextArea.m | 3 +- .../src/BaseTextFields/MDCBaseTextField.m | 3 +- .../src/BaseStyle/MDCTextControlStyleBase.m | 6 +- ...tControlVerticalPositioningReferenceBase.h | 3 +- ...tControlVerticalPositioningReferenceBase.m | 111 ++++++++---------- .../FilledStyle/MDCTextControlStyleFilled.m | 6 +- ...ontrolVerticalPositioningReferenceFilled.h | 3 +- ...ontrolVerticalPositioningReferenceFilled.m | 4 +- .../MDCTextControlStyleOutlined.m | 6 +- ...trolVerticalPositioningReferenceOutlined.h | 3 +- ...trolVerticalPositioningReferenceOutlined.m | 98 +++++++--------- .../src/Shared/MDCTextControl.h | 3 +- .../MDCTextControlStyleUnderlined.m | 6 +- ...olVerticalPositioningReferenceUnderlined.h | 3 +- ...olVerticalPositioningReferenceUnderlined.m | 103 +++++++++------- 15 files changed, 183 insertions(+), 178 deletions(-) diff --git a/components/TextControls/src/BaseTextAreas/MDCBaseTextArea.m b/components/TextControls/src/BaseTextAreas/MDCBaseTextArea.m index f5620dca44f..e177fc5386f 100644 --- a/components/TextControls/src/BaseTextAreas/MDCBaseTextArea.m +++ b/components/TextControls/src/BaseTextAreas/MDCBaseTextArea.m @@ -243,7 +243,8 @@ - (MDCBaseTextAreaLayout *)calculateLayoutWithSize:(CGSize)size { self.normalFont.leading) numberOfTextRows:self.numberOfLinesOfVisibleText density:0 - preferredContainerHeight:self.preferredContainerHeight]; + preferredContainerHeight:self.preferredContainerHeight + isMultilineTextControl:YES]; } - (id)createHorizontalPositioningReference { diff --git a/components/TextControls/src/BaseTextFields/MDCBaseTextField.m b/components/TextControls/src/BaseTextFields/MDCBaseTextField.m index 9e2624f08bd..18201cb0511 100644 --- a/components/TextControls/src/BaseTextFields/MDCBaseTextField.m +++ b/components/TextControls/src/BaseTextFields/MDCBaseTextField.m @@ -258,7 +258,8 @@ - (MDCBaseTextFieldLayout *)calculateLayoutWithTextFieldSize:(CGSize)textFieldSi textRowHeight:self.normalFont.lineHeight numberOfTextRows:self.numberOfLinesOfVisibleText density:0 - preferredContainerHeight:self.preferredContainerHeight]; + preferredContainerHeight:self.preferredContainerHeight + isMultilineTextControl:NO]; } - (CGFloat)clampedCustomAssistiveLabelDrawPriority:(CGFloat)customPriority { diff --git a/components/private/TextControlsPrivate/src/BaseStyle/MDCTextControlStyleBase.m b/components/private/TextControlsPrivate/src/BaseStyle/MDCTextControlStyleBase.m index 4d42d49ebd6..c9e7ccfecbf 100644 --- a/components/private/TextControlsPrivate/src/BaseStyle/MDCTextControlStyleBase.m +++ b/components/private/TextControlsPrivate/src/BaseStyle/MDCTextControlStyleBase.m @@ -43,14 +43,16 @@ - (void)removeStyleFrom:(id)textControl { textRowHeight:(CGFloat)textRowHeight numberOfTextRows:(CGFloat)numberOfTextRows density:(CGFloat)density - preferredContainerHeight:(CGFloat)preferredContainerHeight { + preferredContainerHeight:(CGFloat)preferredContainerHeight + isMultilineTextControl:(BOOL)isMultilineTextControl { return [[MDCTextControlVerticalPositioningReferenceBase alloc] initWithFloatingFontLineHeight:floatingLabelHeight normalFontLineHeight:normalFontLineHeight textRowHeight:textRowHeight numberOfTextRows:numberOfTextRows density:density - preferredContainerHeight:preferredContainerHeight]; + preferredContainerHeight:preferredContainerHeight + isMultilineTextControl:isMultilineTextControl]; } - (MDCTextControlHorizontalPositioningReference *)horizontalPositioningReference { diff --git a/components/private/TextControlsPrivate/src/BaseStyle/MDCTextControlVerticalPositioningReferenceBase.h b/components/private/TextControlsPrivate/src/BaseStyle/MDCTextControlVerticalPositioningReferenceBase.h index a2b4b711cd0..4176095ee27 100644 --- a/components/private/TextControlsPrivate/src/BaseStyle/MDCTextControlVerticalPositioningReferenceBase.h +++ b/components/private/TextControlsPrivate/src/BaseStyle/MDCTextControlVerticalPositioningReferenceBase.h @@ -24,6 +24,7 @@ textRowHeight:(CGFloat)textRowHeight numberOfTextRows:(CGFloat)numberOfTextRows density:(CGFloat)density - preferredContainerHeight:(CGFloat)preferredContainerHeight; + preferredContainerHeight:(CGFloat)preferredContainerHeight + isMultilineTextControl:(BOOL)isMultilineTextControl; @end diff --git a/components/private/TextControlsPrivate/src/BaseStyle/MDCTextControlVerticalPositioningReferenceBase.m b/components/private/TextControlsPrivate/src/BaseStyle/MDCTextControlVerticalPositioningReferenceBase.m index 76f8af23bb1..0ddded57e98 100644 --- a/components/private/TextControlsPrivate/src/BaseStyle/MDCTextControlVerticalPositioningReferenceBase.m +++ b/components/private/TextControlsPrivate/src/BaseStyle/MDCTextControlVerticalPositioningReferenceBase.m @@ -16,8 +16,8 @@ #import "MaterialMath.h" #import "MDCTextControlVerticalPositioningReference.h" -static const CGFloat kMinPaddingAroundTextWhenNoLabel = 6.0f; -static const CGFloat kMaxPaddingAroundTextWhenNoLabel = 10.0f; +static const CGFloat kMinPaddingAroundTextWhenNoFloatingLabel = 6.0f; +static const CGFloat kMaxPaddingAroundTextWhenNoFloatingLabel = 10.0f; static const CGFloat kMinPaddingBetweenContainerTopAndFloatingLabel = (CGFloat)6.0; static const CGFloat kMaxPaddingBetweenContainerTopAndFloatingLabel = (CGFloat)10.0; static const CGFloat kMinPaddingBetweenFloatingLabelAndEditingText = (CGFloat)3.0; @@ -51,7 +51,8 @@ - (instancetype)initWithFloatingFontLineHeight:(CGFloat)floatingLabelHeight textRowHeight:(CGFloat)textRowHeight numberOfTextRows:(CGFloat)numberOfTextRows density:(CGFloat)density - preferredContainerHeight:(CGFloat)preferredContainerHeight { + preferredContainerHeight:(CGFloat)preferredContainerHeight + isMultilineTextControl:(BOOL)isMultilineTextControl { self = [super init]; if (self) { [self calculatePaddingValuesWithFoatingFontLineHeight:floatingLabelHeight @@ -59,7 +60,8 @@ - (instancetype)initWithFloatingFontLineHeight:(CGFloat)floatingLabelHeight textRowHeight:textRowHeight numberOfTextRows:numberOfTextRows density:density - preferredContainerHeight:preferredContainerHeight]; + preferredContainerHeight:preferredContainerHeight + isMultilineTextControl:isMultilineTextControl]; } return self; } @@ -69,13 +71,10 @@ - (void)calculatePaddingValuesWithFoatingFontLineHeight:(CGFloat)floatingLabelHe textRowHeight:(CGFloat)textRowHeight numberOfTextRows:(CGFloat)numberOfTextRows density:(CGFloat)density - preferredContainerHeight:(CGFloat)preferredContainerHeight { - BOOL isMultiline = numberOfTextRows > 1 || numberOfTextRows == 0; + preferredContainerHeight:(CGFloat)preferredContainerHeight + isMultilineTextControl:(CGFloat)isMultilineTextControl { CGFloat clampedDensity = MDCTextControlClampDensity(density); - _paddingAroundTextWhenNoFloatingLabel = MDCTextControlPaddingValueWithMinimumPadding( - kMinPaddingAroundTextWhenNoLabel, kMaxPaddingAroundTextWhenNoLabel, clampedDensity); - _paddingBetweenContainerTopAndFloatingLabel = MDCTextControlPaddingValueWithMinimumPadding( kMinPaddingBetweenContainerTopAndFloatingLabel, kMaxPaddingBetweenContainerTopAndFloatingLabel, clampedDensity); @@ -90,78 +89,72 @@ - (void)calculatePaddingValuesWithFoatingFontLineHeight:(CGFloat)floatingLabelHe _paddingAboveAssistiveLabels = MDCTextControlPaddingValueWithMinimumPadding( kMinPaddingAboveAssistiveLabels, kMaxPaddingAboveAssistiveLabels, clampedDensity); - _paddingBelowAssistiveLabels = MDCTextControlPaddingValueWithMinimumPadding( kMinPaddingBelowAssistiveLabels, kMaxPaddingBelowAssistiveLabels, clampedDensity); - // The container height below is the "default" container height, given the density. This height - // will be used if the client has not specified a preferredContainerHeight. - CGFloat containerHeightWithPaddingsDeterminedByDensity = + CGFloat defaultContainerHeightForFloatingLabel = MDCTextControlCalculateContainerHeightWithFloatingLabelHeight( floatingLabelHeight, textRowHeight, numberOfTextRows, _paddingBetweenContainerTopAndFloatingLabel, _paddingBetweenFloatingLabelAndEditingText, _paddingBetweenEditingTextAndContainerBottom); - BOOL clientHasSpecifiedValidPreferredContainerHeight = - preferredContainerHeight > containerHeightWithPaddingsDeterminedByDensity; - if (clientHasSpecifiedValidPreferredContainerHeight && !isMultiline) { - // modify the previously computed padding values so that they ultimately result in a container - // with the preferred container height. - CGFloat difference = preferredContainerHeight - containerHeightWithPaddingsDeterminedByDensity; - CGFloat sumOfPaddingValues = _paddingBetweenContainerTopAndFloatingLabel + - _paddingBetweenFloatingLabelAndEditingText + - _paddingBetweenEditingTextAndContainerBottom; - _paddingBetweenContainerTopAndFloatingLabel = - _paddingBetweenContainerTopAndFloatingLabel + - ((_paddingBetweenContainerTopAndFloatingLabel / sumOfPaddingValues) * difference); - _paddingBetweenFloatingLabelAndEditingText = - _paddingBetweenFloatingLabelAndEditingText + - ((_paddingBetweenFloatingLabelAndEditingText / sumOfPaddingValues) * difference); - _paddingBetweenEditingTextAndContainerBottom = - _paddingBetweenEditingTextAndContainerBottom + - ((_paddingBetweenEditingTextAndContainerBottom / sumOfPaddingValues) * difference); + BOOL preferredContainerHeightIsValidForFloatingLabel = + preferredContainerHeight > defaultContainerHeightForFloatingLabel; + if (preferredContainerHeightIsValidForFloatingLabel) { + _containerHeightWithFloatingLabel = preferredContainerHeight; + BOOL shouldUpdatePaddingValuesToMeetMinimumHeight = !isMultilineTextControl; + if (shouldUpdatePaddingValuesToMeetMinimumHeight) { + CGFloat difference = preferredContainerHeight - defaultContainerHeightForFloatingLabel; + CGFloat sumOfPaddingValues = _paddingBetweenContainerTopAndFloatingLabel + + _paddingBetweenFloatingLabelAndEditingText + + _paddingBetweenEditingTextAndContainerBottom; + _paddingBetweenContainerTopAndFloatingLabel = + _paddingBetweenContainerTopAndFloatingLabel + + ((_paddingBetweenContainerTopAndFloatingLabel / sumOfPaddingValues) * difference); + _paddingBetweenFloatingLabelAndEditingText = + _paddingBetweenFloatingLabelAndEditingText + + ((_paddingBetweenFloatingLabelAndEditingText / sumOfPaddingValues) * difference); + _paddingBetweenEditingTextAndContainerBottom = + _paddingBetweenEditingTextAndContainerBottom + + ((_paddingBetweenEditingTextAndContainerBottom / sumOfPaddingValues) * difference); + } + } else { + _containerHeightWithFloatingLabel = defaultContainerHeightForFloatingLabel; } - if (clientHasSpecifiedValidPreferredContainerHeight) { - _containerHeightWithFloatingLabel = preferredContainerHeight; + _paddingAroundTextWhenNoFloatingLabel = MDCTextControlPaddingValueWithMinimumPadding( + kMinPaddingAroundTextWhenNoFloatingLabel, kMaxPaddingAroundTextWhenNoFloatingLabel, + clampedDensity); + + CGFloat defaultContainerHeightForNoFloatingLabel = + MDCTextControlCalculateContainerHeightWhenNoFloatingLabelWithTextRowHeight( + textRowHeight, numberOfTextRows, _paddingAroundTextWhenNoFloatingLabel); + BOOL preferredContainerHeightIsValidForNoFloatingLabel = + preferredContainerHeight > defaultContainerHeightForNoFloatingLabel; + if (preferredContainerHeightIsValidForNoFloatingLabel) { + _containerHeightWithoutFloatingLabel = preferredContainerHeight; + BOOL shouldUpdatePaddingValuesToMeetMinimumHeight = !isMultilineTextControl; + if (shouldUpdatePaddingValuesToMeetMinimumHeight) { + CGFloat difference = preferredContainerHeight - defaultContainerHeightForNoFloatingLabel; + CGFloat sumOfPaddingValues = _paddingAroundTextWhenNoFloatingLabel * 2.0f; + _paddingAroundTextWhenNoFloatingLabel = + _paddingAroundTextWhenNoFloatingLabel + + ((_paddingAroundTextWhenNoFloatingLabel / sumOfPaddingValues) * difference); + } } else { - _containerHeightWithFloatingLabel = containerHeightWithPaddingsDeterminedByDensity; + _containerHeightWithoutFloatingLabel = defaultContainerHeightForNoFloatingLabel; } CGFloat halfOfNormalFontLineHeight = (CGFloat)0.5 * normalFontLineHeight; - if (isMultiline) { - // For multiline text controls the normal label (i.e. the label when it's not floating) should - // be positioned where it would be positioned if it were single-line. + if (isMultilineTextControl) { CGFloat heightWithOneRow = MDCTextControlCalculateContainerHeightWithFloatingLabelHeight( floatingLabelHeight, textRowHeight, 1, _paddingBetweenContainerTopAndFloatingLabel, _paddingBetweenFloatingLabelAndEditingText, _paddingBetweenEditingTextAndContainerBottom); CGFloat halfOfHeightWithOneRow = (CGFloat)0.5 * heightWithOneRow; _paddingBetweenContainerTopAndNormalLabel = halfOfHeightWithOneRow - halfOfNormalFontLineHeight; } else { - // For single-line text controls the normal label (i.e. the label when it's not floating) should - // be vertically centered. CGFloat halfOfContainerHeight = (CGFloat)0.5 * _containerHeightWithFloatingLabel; _paddingBetweenContainerTopAndNormalLabel = halfOfContainerHeight - halfOfNormalFontLineHeight; } - - CGFloat containerHeightWhenNoFloatingLabelWithPaddingsDeterminedByDensity = - MDCTextControlCalculateContainerHeightWhenNoFloatingLabelWithTextRowHeight( - textRowHeight, numberOfTextRows, _paddingAroundTextWhenNoFloatingLabel); - BOOL clientHasSpecifiedValidPreferredContainerHeightWhenNoFloatingLabel = - preferredContainerHeight > containerHeightWhenNoFloatingLabelWithPaddingsDeterminedByDensity; - if (clientHasSpecifiedValidPreferredContainerHeightWhenNoFloatingLabel && !isMultiline) { - CGFloat difference = preferredContainerHeight - containerHeightWithPaddingsDeterminedByDensity; - CGFloat sumOfPaddingValues = _paddingAroundTextWhenNoFloatingLabel * 2.0f; - _paddingAroundTextWhenNoFloatingLabel = - _paddingAroundTextWhenNoFloatingLabel + - ((_paddingAroundTextWhenNoFloatingLabel / sumOfPaddingValues) * difference); - } - - if (clientHasSpecifiedValidPreferredContainerHeightWhenNoFloatingLabel) { - _containerHeightWithoutFloatingLabel = preferredContainerHeight; - } else { - _containerHeightWithoutFloatingLabel = - containerHeightWhenNoFloatingLabelWithPaddingsDeterminedByDensity; - } } - (CGFloat)paddingBetweenContainerTopAndFloatingLabel { diff --git a/components/private/TextControlsPrivate/src/FilledStyle/MDCTextControlStyleFilled.m b/components/private/TextControlsPrivate/src/FilledStyle/MDCTextControlStyleFilled.m index 8902d73f678..cb0efb1fe7a 100644 --- a/components/private/TextControlsPrivate/src/FilledStyle/MDCTextControlStyleFilled.m +++ b/components/private/TextControlsPrivate/src/FilledStyle/MDCTextControlStyleFilled.m @@ -102,14 +102,16 @@ - (void)removeStyleFrom:(id)textControl { textRowHeight:(CGFloat)textRowHeight numberOfTextRows:(CGFloat)numberOfTextRows density:(CGFloat)density - preferredContainerHeight:(CGFloat)preferredContainerHeight { + preferredContainerHeight:(CGFloat)preferredContainerHeight + isMultilineTextControl:(BOOL)isMultilineTextControl { return [[MDCTextControlVerticalPositioningReferenceFilled alloc] initWithFloatingFontLineHeight:floatingLabelHeight normalFontLineHeight:normalFontLineHeight textRowHeight:textRowHeight numberOfTextRows:numberOfTextRows density:density - preferredContainerHeight:preferredContainerHeight]; + preferredContainerHeight:preferredContainerHeight + isMultilineTextControl:isMultilineTextControl]; } - (nonnull MDCTextControlHorizontalPositioningReference *)horizontalPositioningReference { diff --git a/components/private/TextControlsPrivate/src/FilledStyle/MDCTextControlVerticalPositioningReferenceFilled.h b/components/private/TextControlsPrivate/src/FilledStyle/MDCTextControlVerticalPositioningReferenceFilled.h index c456006b0ce..986d06b9a85 100644 --- a/components/private/TextControlsPrivate/src/FilledStyle/MDCTextControlVerticalPositioningReferenceFilled.h +++ b/components/private/TextControlsPrivate/src/FilledStyle/MDCTextControlVerticalPositioningReferenceFilled.h @@ -26,6 +26,7 @@ textRowHeight:(CGFloat)textRowHeight numberOfTextRows:(CGFloat)numberOfTextRows density:(CGFloat)density - preferredContainerHeight:(CGFloat)preferredContainerHeight; + preferredContainerHeight:(CGFloat)preferredContainerHeight + isMultilineTextControl:(BOOL)isMultilineTextControl; @end diff --git a/components/private/TextControlsPrivate/src/FilledStyle/MDCTextControlVerticalPositioningReferenceFilled.m b/components/private/TextControlsPrivate/src/FilledStyle/MDCTextControlVerticalPositioningReferenceFilled.m index 26f56972434..d24b311b23c 100644 --- a/components/private/TextControlsPrivate/src/FilledStyle/MDCTextControlVerticalPositioningReferenceFilled.m +++ b/components/private/TextControlsPrivate/src/FilledStyle/MDCTextControlVerticalPositioningReferenceFilled.m @@ -51,10 +51,10 @@ - (instancetype)initWithFloatingFontLineHeight:(CGFloat)floatingLabelHeight textRowHeight:(CGFloat)textRowHeight numberOfTextRows:(CGFloat)numberOfTextRows density:(CGFloat)density - preferredContainerHeight:(CGFloat)preferredContainerHeight { + preferredContainerHeight:(CGFloat)preferredContainerHeight + isMultilineTextControl:(BOOL)isMultilineTextControl { self = [super init]; if (self) { - BOOL isMultilineTextControl = numberOfTextRows > 1 || numberOfTextRows == 0; CGFloat clampedDensity = MDCTextControlClampDensity(density); _paddingBetweenContainerTopAndFloatingLabel = MDCTextControlPaddingValueWithMinimumPadding( diff --git a/components/private/TextControlsPrivate/src/OutlinedStyle/MDCTextControlStyleOutlined.m b/components/private/TextControlsPrivate/src/OutlinedStyle/MDCTextControlStyleOutlined.m index e8d488c1f2a..29bc68d37d3 100644 --- a/components/private/TextControlsPrivate/src/OutlinedStyle/MDCTextControlStyleOutlined.m +++ b/components/private/TextControlsPrivate/src/OutlinedStyle/MDCTextControlStyleOutlined.m @@ -122,14 +122,16 @@ - (void)removeStyleFrom:(id)TextControl { textRowHeight:(CGFloat)textRowHeight numberOfTextRows:(CGFloat)numberOfTextRows density:(CGFloat)density - preferredContainerHeight:(CGFloat)preferredContainerHeight { + preferredContainerHeight:(CGFloat)preferredContainerHeight + isMultilineTextControl:(BOOL)isMultilineTextControl { return [[MDCTextControlVerticalPositioningReferenceOutlined alloc] initWithFloatingFontLineHeight:floatingLabelHeight normalFontLineHeight:normalFontLineHeight textRowHeight:textRowHeight numberOfTextRows:numberOfTextRows density:density - preferredContainerHeight:preferredContainerHeight]; + preferredContainerHeight:preferredContainerHeight + isMultilineTextControl:isMultilineTextControl]; } - (MDCTextControlHorizontalPositioningReference *)horizontalPositioningReference { diff --git a/components/private/TextControlsPrivate/src/OutlinedStyle/MDCTextControlVerticalPositioningReferenceOutlined.h b/components/private/TextControlsPrivate/src/OutlinedStyle/MDCTextControlVerticalPositioningReferenceOutlined.h index cff33a4f1a2..31c6a48f371 100644 --- a/components/private/TextControlsPrivate/src/OutlinedStyle/MDCTextControlVerticalPositioningReferenceOutlined.h +++ b/components/private/TextControlsPrivate/src/OutlinedStyle/MDCTextControlVerticalPositioningReferenceOutlined.h @@ -25,6 +25,7 @@ textRowHeight:(CGFloat)textRowHeight numberOfTextRows:(CGFloat)numberOfTextRows density:(CGFloat)density - preferredContainerHeight:(CGFloat)preferredContainerHeight; + preferredContainerHeight:(CGFloat)preferredContainerHeight + isMultilineTextControl:(BOOL)isMultilineTextControl; @end diff --git a/components/private/TextControlsPrivate/src/OutlinedStyle/MDCTextControlVerticalPositioningReferenceOutlined.m b/components/private/TextControlsPrivate/src/OutlinedStyle/MDCTextControlVerticalPositioningReferenceOutlined.m index afbeb7e6c89..0504659cf11 100644 --- a/components/private/TextControlsPrivate/src/OutlinedStyle/MDCTextControlVerticalPositioningReferenceOutlined.m +++ b/components/private/TextControlsPrivate/src/OutlinedStyle/MDCTextControlVerticalPositioningReferenceOutlined.m @@ -20,8 +20,8 @@ achieve the look and feel of the textfields at https://material.io/design/components/text-fields.html. */ -static const CGFloat kMinPaddingAroundTextWhenNoLabel = 6.0f; -static const CGFloat kMaxPaddingAroundTextWhenNoLabel = 10.0f; +static const CGFloat kMinPaddingAroundTextWhenNoFloatingLabel = 6.0f; +static const CGFloat kMaxPaddingAroundTextWhenNoFloatingLabel = 10.0f; static const CGFloat kMinPaddingBetweenFloatingLabelAndEditingText = (CGFloat)8.0; static const CGFloat kMaxPaddingBetweenFloatingLabelAndEditingText = (CGFloat)12.0; static const CGFloat kMinPaddingAroundAssistiveLabels = (CGFloat)3.0; @@ -32,11 +32,11 @@ @interface MDCTextControlVerticalPositioningReferenceOutlined () @end /** - For slightly more context on what this class is doing look at - MDCTextControlVerticalPositioningReferenceBase. It's very similar and has some comments. Maybe at - some point all the positioning references should be refactored to share a superclass, because - there's currently a lot of duplicated code among the three of them. - */ + For slightly more context on what this class is doing look at + MDCTextControlVerticalPositioningReferenceBase. It's very similar and has some comments. Maybe at + some point all the positioning references should be refactored to share a superclass, because + there's currently a lot of duplicated code among the three of them. +*/ @implementation MDCTextControlVerticalPositioningReferenceOutlined @synthesize paddingBetweenContainerTopAndFloatingLabel = _paddingBetweenContainerTopAndFloatingLabel; @@ -53,7 +53,8 @@ - (instancetype)initWithFloatingFontLineHeight:(CGFloat)floatingLabelHeight textRowHeight:(CGFloat)textRowHeight numberOfTextRows:(CGFloat)numberOfTextRows density:(CGFloat)density - preferredContainerHeight:(CGFloat)preferredContainerHeight { + preferredContainerHeight:(CGFloat)preferredContainerHeight + isMultilineTextControl:(BOOL)isMultilineTextControl { self = [super init]; if (self) { [self calculatePaddingValuesWithFoatingFontLineHeight:floatingLabelHeight @@ -61,7 +62,8 @@ - (instancetype)initWithFloatingFontLineHeight:(CGFloat)floatingLabelHeight textRowHeight:textRowHeight numberOfTextRows:numberOfTextRows density:density - preferredContainerHeight:preferredContainerHeight]; + preferredContainerHeight:preferredContainerHeight + isMultilineTextControl:isMultilineTextControl]; } return self; } @@ -71,11 +73,10 @@ - (void)calculatePaddingValuesWithFoatingFontLineHeight:(CGFloat)floatingLabelHe textRowHeight:(CGFloat)textRowHeight numberOfTextRows:(CGFloat)numberOfTextRows density:(CGFloat)density - preferredContainerHeight:(CGFloat)preferredContainerHeight { - BOOL isMultiline = numberOfTextRows > 1 || numberOfTextRows == 0; + preferredContainerHeight:(CGFloat)preferredContainerHeight + isMultilineTextControl:(BOOL)isMultilineTextControl { CGFloat clampedDensity = MDCTextControlClampDensity(density); CGFloat halfOfFloatingLabelHeight = ((CGFloat)0.5 * floatingLabelHeight); - _paddingBetweenContainerTopAndFloatingLabel = (CGFloat)0 - halfOfFloatingLabelHeight; _paddingBetweenFloatingLabelAndEditingText = MDCTextControlPaddingValueWithMinimumPadding( @@ -89,37 +90,41 @@ - (void)calculatePaddingValuesWithFoatingFontLineHeight:(CGFloat)floatingLabelHe _paddingAroundAssistiveLabels = MDCTextControlPaddingValueWithMinimumPadding( kMinPaddingAroundAssistiveLabels, kMaxPaddingAroundAssistiveLabels, clampedDensity); - CGFloat containerHeightWithPaddingsDeterminedByDensity = + CGFloat defaultContainerHeightForFloatingLabel = MDCTextControlCalculateContainerHeightWithFloatingLabelHeight( floatingLabelHeight, textRowHeight, numberOfTextRows, _paddingBetweenContainerTopAndFloatingLabel, _paddingBetweenFloatingLabelAndEditingText, _paddingBetweenEditingTextAndContainerBottom); - BOOL clientHasSpecifiedValidPreferredContainerHeight = - preferredContainerHeight > containerHeightWithPaddingsDeterminedByDensity; - if (clientHasSpecifiedValidPreferredContainerHeight && !isMultiline) { - CGFloat difference = preferredContainerHeight - containerHeightWithPaddingsDeterminedByDensity; - CGFloat sumOfPaddingValues = _paddingBetweenContainerTopAndFloatingLabel + - _paddingBetweenFloatingLabelAndEditingText + - _paddingBetweenEditingTextAndContainerBottom; - _paddingBetweenContainerTopAndFloatingLabel = - _paddingBetweenContainerTopAndFloatingLabel + - ((_paddingBetweenContainerTopAndFloatingLabel / sumOfPaddingValues) * difference); - _paddingBetweenFloatingLabelAndEditingText = - _paddingBetweenFloatingLabelAndEditingText + - ((_paddingBetweenFloatingLabelAndEditingText / sumOfPaddingValues) * difference); - _paddingBetweenEditingTextAndContainerBottom = - _paddingBetweenEditingTextAndContainerBottom + - ((_paddingBetweenEditingTextAndContainerBottom / sumOfPaddingValues) * difference); - } - - if (clientHasSpecifiedValidPreferredContainerHeight) { + BOOL preferredContainerHeightIsValidForFloatingLabel = + preferredContainerHeight > defaultContainerHeightForFloatingLabel; + if (preferredContainerHeightIsValidForFloatingLabel) { _containerHeightWithFloatingLabel = preferredContainerHeight; } else { - _containerHeightWithFloatingLabel = containerHeightWithPaddingsDeterminedByDensity; + _containerHeightWithFloatingLabel = defaultContainerHeightForFloatingLabel; } + _paddingAroundTextWhenNoFloatingLabel = MDCTextControlPaddingValueWithMinimumPadding( + kMinPaddingAroundTextWhenNoFloatingLabel, kMaxPaddingAroundTextWhenNoFloatingLabel, + clampedDensity); + + CGFloat defaultContainerHeightForNoFloatingLabel = + MDCTextControlCalculateContainerHeightWhenNoFloatingLabelWithTextRowHeight( + textRowHeight, numberOfTextRows, _paddingAroundTextWhenNoFloatingLabel); + BOOL preferredContainerHeightIsValidForNoFloatingLabel = + preferredContainerHeight > defaultContainerHeightForNoFloatingLabel; CGFloat halfOfNormalFontLineHeight = (CGFloat)0.5 * normalFontLineHeight; - if (isMultiline) { + if (preferredContainerHeightIsValidForNoFloatingLabel) { + _containerHeightWithoutFloatingLabel = preferredContainerHeight; + BOOL shouldUpdatePaddingValuesToMeetMinimumHeight = !isMultilineTextControl; + if (shouldUpdatePaddingValuesToMeetMinimumHeight) { + CGFloat halfOfContainerHeight = (CGFloat)0.5 * _containerHeightWithoutFloatingLabel; + _paddingAroundTextWhenNoFloatingLabel = halfOfContainerHeight - halfOfNormalFontLineHeight; + } + } else { + _containerHeightWithoutFloatingLabel = defaultContainerHeightForNoFloatingLabel; + } + + if (isMultilineTextControl) { CGFloat heightWithOneRow = MDCTextControlCalculateContainerHeightWithFloatingLabelHeight( floatingLabelHeight, textRowHeight, 1, _paddingBetweenContainerTopAndFloatingLabel, _paddingBetweenFloatingLabelAndEditingText, _paddingBetweenEditingTextAndContainerBottom); @@ -128,29 +133,8 @@ - (void)calculatePaddingValuesWithFoatingFontLineHeight:(CGFloat)floatingLabelHe } else { CGFloat halfOfContainerHeight = (CGFloat)0.5 * _containerHeightWithFloatingLabel; _paddingBetweenContainerTopAndNormalLabel = halfOfContainerHeight - halfOfNormalFontLineHeight; - } - - _paddingAroundTextWhenNoFloatingLabel = MDCTextControlPaddingValueWithMinimumPadding( - kMinPaddingAroundTextWhenNoLabel, kMaxPaddingAroundTextWhenNoLabel, clampedDensity); - - CGFloat containerHeightWhenNoFloatingLabelWithPaddingsDeterminedByDensity = - MDCTextControlCalculateContainerHeightWhenNoFloatingLabelWithTextRowHeight( - textRowHeight, numberOfTextRows, _paddingAroundTextWhenNoFloatingLabel); - BOOL clientHasSpecifiedValidPreferredContainerHeightWhenNoFloatingLabel = - preferredContainerHeight > containerHeightWhenNoFloatingLabelWithPaddingsDeterminedByDensity; - if (clientHasSpecifiedValidPreferredContainerHeightWhenNoFloatingLabel && !isMultiline) { - CGFloat difference = preferredContainerHeight - containerHeightWithPaddingsDeterminedByDensity; - CGFloat sumOfPaddingValues = _paddingAroundTextWhenNoFloatingLabel * 2.0f; - _paddingAroundTextWhenNoFloatingLabel = - _paddingAroundTextWhenNoFloatingLabel + - ((_paddingAroundTextWhenNoFloatingLabel / sumOfPaddingValues) * difference); - } - - if (clientHasSpecifiedValidPreferredContainerHeightWhenNoFloatingLabel) { - _containerHeightWithoutFloatingLabel = preferredContainerHeight; - } else { - _containerHeightWithoutFloatingLabel = - containerHeightWhenNoFloatingLabelWithPaddingsDeterminedByDensity; + _paddingBetweenFloatingLabelAndEditingText = + _paddingBetweenContainerTopAndNormalLabel - halfOfFloatingLabelHeight; } } diff --git a/components/private/TextControlsPrivate/src/Shared/MDCTextControl.h b/components/private/TextControlsPrivate/src/Shared/MDCTextControl.h index 09d79b15517..188d812f36c 100644 --- a/components/private/TextControlsPrivate/src/Shared/MDCTextControl.h +++ b/components/private/TextControlsPrivate/src/Shared/MDCTextControl.h @@ -182,7 +182,8 @@ FOUNDATION_EXTERN const CGFloat kMDCTextControlDefaultAnimationDuration; textRowHeight:(CGFloat)textRowHeight numberOfTextRows:(CGFloat)numberOfTextRows density:(CGFloat)density - preferredContainerHeight:(CGFloat)preferredContainerHeight; + preferredContainerHeight:(CGFloat)preferredContainerHeight + isMultilineTextControl:(BOOL)isMultilineTextControl; /** This method returns an object that tells the view where to position its views diff --git a/components/private/TextControlsPrivate/src/UnderlinedStyle/MDCTextControlStyleUnderlined.m b/components/private/TextControlsPrivate/src/UnderlinedStyle/MDCTextControlStyleUnderlined.m index f5d2f18bf2b..251a8b02f83 100644 --- a/components/private/TextControlsPrivate/src/UnderlinedStyle/MDCTextControlStyleUnderlined.m +++ b/components/private/TextControlsPrivate/src/UnderlinedStyle/MDCTextControlStyleUnderlined.m @@ -112,14 +112,16 @@ - (void)removeStyleFrom:(id)textControl { textRowHeight:(CGFloat)textRowHeight numberOfTextRows:(CGFloat)numberOfTextRows density:(CGFloat)density - preferredContainerHeight:(CGFloat)preferredContainerHeight { + preferredContainerHeight:(CGFloat)preferredContainerHeight + isMultilineTextControl:(BOOL)isMultilineTextControl { return [[MDCTextControlVerticalPositioningReferenceUnderlined alloc] initWithFloatingFontLineHeight:floatingLabelHeight normalFontLineHeight:normalFontLineHeight textRowHeight:textRowHeight numberOfTextRows:numberOfTextRows density:density - preferredContainerHeight:preferredContainerHeight]; + preferredContainerHeight:preferredContainerHeight + isMultilineTextControl:isMultilineTextControl]; } - (UIFont *)floatingFontWithNormalFont:(UIFont *)font { diff --git a/components/private/TextControlsPrivate/src/UnderlinedStyle/MDCTextControlVerticalPositioningReferenceUnderlined.h b/components/private/TextControlsPrivate/src/UnderlinedStyle/MDCTextControlVerticalPositioningReferenceUnderlined.h index bb5d82b7bd6..9349cd89f1b 100644 --- a/components/private/TextControlsPrivate/src/UnderlinedStyle/MDCTextControlVerticalPositioningReferenceUnderlined.h +++ b/components/private/TextControlsPrivate/src/UnderlinedStyle/MDCTextControlVerticalPositioningReferenceUnderlined.h @@ -25,6 +25,7 @@ textRowHeight:(CGFloat)textRowHeight numberOfTextRows:(CGFloat)numberOfTextRows density:(CGFloat)density - preferredContainerHeight:(CGFloat)preferredContainerHeight; + preferredContainerHeight:(CGFloat)preferredContainerHeight + isMultilineTextControl:(BOOL)isMultilineTextControl; @end diff --git a/components/private/TextControlsPrivate/src/UnderlinedStyle/MDCTextControlVerticalPositioningReferenceUnderlined.m b/components/private/TextControlsPrivate/src/UnderlinedStyle/MDCTextControlVerticalPositioningReferenceUnderlined.m index 6f2f056a718..f735032a48c 100644 --- a/components/private/TextControlsPrivate/src/UnderlinedStyle/MDCTextControlVerticalPositioningReferenceUnderlined.m +++ b/components/private/TextControlsPrivate/src/UnderlinedStyle/MDCTextControlVerticalPositioningReferenceUnderlined.m @@ -21,8 +21,8 @@ https://material.io/design/components/text-fields.html. */ -static const CGFloat kMinPaddingAroundTextWhenNoLabel = 6.0f; -static const CGFloat kMaxPaddingAroundTextWhenNoLabel = 10.0f; +static const CGFloat kMinPaddingAroundTextWhenNoFloatingLabel = 6.0f; +static const CGFloat kMaxPaddingAroundTextWhenNoFloatingLabel = 10.0f; static const CGFloat kMinPaddingBetweenContainerTopAndFloatingLabel = 6.0f; static const CGFloat kMaxPaddingBetweenContainerTopAndFloatingLabel = 10.0f; static const CGFloat kMinPaddingBetweenFloatingLabelAndEditingText = 3.0f; @@ -59,7 +59,8 @@ - (instancetype)initWithFloatingFontLineHeight:(CGFloat)floatingLabelHeight textRowHeight:(CGFloat)textRowHeight numberOfTextRows:(CGFloat)numberOfTextRows density:(CGFloat)density - preferredContainerHeight:(CGFloat)preferredContainerHeight { + preferredContainerHeight:(CGFloat)preferredContainerHeight + isMultilineTextControl:(BOOL)isMultilineTextControl { self = [super init]; if (self) { [self calculatePaddingValuesWithFoatingFontLineHeight:floatingLabelHeight @@ -67,7 +68,8 @@ - (instancetype)initWithFloatingFontLineHeight:(CGFloat)floatingLabelHeight textRowHeight:textRowHeight numberOfTextRows:numberOfTextRows density:density - preferredContainerHeight:preferredContainerHeight]; + preferredContainerHeight:preferredContainerHeight + isMultilineTextControl:isMultilineTextControl]; } return self; } @@ -77,13 +79,10 @@ - (void)calculatePaddingValuesWithFoatingFontLineHeight:(CGFloat)floatingLabelHe textRowHeight:(CGFloat)textRowHeight numberOfTextRows:(CGFloat)numberOfTextRows density:(CGFloat)density - preferredContainerHeight:(CGFloat)preferredContainerHeight { - BOOL isMultiline = numberOfTextRows > 1 || numberOfTextRows == 0; + preferredContainerHeight:(CGFloat)preferredContainerHeight + isMultilineTextControl:(BOOL)isMultilineTextControl { CGFloat clampedDensity = MDCTextControlClampDensity(density); - _paddingAroundTextWhenNoFloatingLabel = MDCTextControlPaddingValueWithMinimumPadding( - kMinPaddingAroundTextWhenNoLabel, kMaxPaddingAroundTextWhenNoLabel, clampedDensity); - _paddingBetweenContainerTopAndFloatingLabel = MDCTextControlPaddingValueWithMinimumPadding( kMinPaddingBetweenContainerTopAndFloatingLabel, kMaxPaddingBetweenContainerTopAndFloatingLabel, clampedDensity); @@ -103,54 +102,68 @@ - (void)calculatePaddingValuesWithFoatingFontLineHeight:(CGFloat)floatingLabelHe floatingLabelHeight + _paddingBetweenFloatingLabelAndEditingText; - CGFloat containerHeightWithPaddingsDeterminedByDensity = + CGFloat defaultContainerHeightForFloatingLabel = MDCTextControlCalculateContainerHeightWithFloatingLabelHeight( floatingLabelHeight, textRowHeight, numberOfTextRows, _paddingBetweenContainerTopAndFloatingLabel, _paddingBetweenFloatingLabelAndEditingText, _paddingBetweenEditingTextAndContainerBottom); - - BOOL clientHasSpecifiedValidPreferredContainerHeight = - preferredContainerHeight > containerHeightWithPaddingsDeterminedByDensity; - if (clientHasSpecifiedValidPreferredContainerHeight && !isMultiline) { - CGFloat difference = preferredContainerHeight - containerHeightWithPaddingsDeterminedByDensity; - CGFloat sumOfPaddingValues = _paddingBetweenContainerTopAndFloatingLabel + - _paddingBetweenFloatingLabelAndEditingText + - _paddingBetweenEditingTextAndContainerBottom; - _paddingBetweenContainerTopAndFloatingLabel = - _paddingBetweenContainerTopAndFloatingLabel + - ((_paddingBetweenContainerTopAndFloatingLabel / sumOfPaddingValues) * difference); - _paddingBetweenFloatingLabelAndEditingText = - _paddingBetweenFloatingLabelAndEditingText + - ((_paddingBetweenFloatingLabelAndEditingText / sumOfPaddingValues) * difference); - _paddingBetweenEditingTextAndContainerBottom = - _paddingBetweenEditingTextAndContainerBottom + - ((_paddingBetweenEditingTextAndContainerBottom / sumOfPaddingValues) * difference); - } - - if (clientHasSpecifiedValidPreferredContainerHeight) { + BOOL preferredContainerHeightIsValidForFloatingLabel = + preferredContainerHeight > defaultContainerHeightForFloatingLabel; + if (preferredContainerHeightIsValidForFloatingLabel) { _containerHeightWithFloatingLabel = preferredContainerHeight; + BOOL shouldUpdatePaddingValuesToMeetMinimumHeight = !isMultilineTextControl; + if (shouldUpdatePaddingValuesToMeetMinimumHeight) { + CGFloat difference = preferredContainerHeight - defaultContainerHeightForFloatingLabel; + CGFloat sumOfPaddingValues = _paddingBetweenContainerTopAndFloatingLabel + + _paddingBetweenFloatingLabelAndEditingText + + _paddingBetweenEditingTextAndContainerBottom; + _paddingBetweenContainerTopAndFloatingLabel = + _paddingBetweenContainerTopAndFloatingLabel + + ((_paddingBetweenContainerTopAndFloatingLabel / sumOfPaddingValues) * difference); + _paddingBetweenFloatingLabelAndEditingText = + _paddingBetweenFloatingLabelAndEditingText + + ((_paddingBetweenFloatingLabelAndEditingText / sumOfPaddingValues) * difference); + _paddingBetweenEditingTextAndContainerBottom = + _paddingBetweenEditingTextAndContainerBottom + + ((_paddingBetweenEditingTextAndContainerBottom / sumOfPaddingValues) * difference); + _paddingBetweenContainerTopAndNormalLabel = _paddingBetweenContainerTopAndFloatingLabel + + floatingLabelHeight + + _paddingBetweenFloatingLabelAndEditingText; + } } else { - _containerHeightWithFloatingLabel = containerHeightWithPaddingsDeterminedByDensity; + _containerHeightWithFloatingLabel = defaultContainerHeightForFloatingLabel; } - CGFloat containerHeightWhenNoFloatingLabelWithPaddingsDeterminedByDensity = + _paddingAroundTextWhenNoFloatingLabel = MDCTextControlPaddingValueWithMinimumPadding( + kMinPaddingAroundTextWhenNoFloatingLabel, kMaxPaddingAroundTextWhenNoFloatingLabel, + clampedDensity); + + CGFloat defaultContainerHeightForNoFloatingLabel = MDCTextControlCalculateContainerHeightWhenNoFloatingLabelWithTextRowHeight( textRowHeight, numberOfTextRows, _paddingAroundTextWhenNoFloatingLabel); - BOOL clientHasSpecifiedValidPreferredContainerHeightWhenNoFloatingLabel = - preferredContainerHeight > containerHeightWhenNoFloatingLabelWithPaddingsDeterminedByDensity; - if (clientHasSpecifiedValidPreferredContainerHeightWhenNoFloatingLabel && !isMultiline) { - CGFloat difference = preferredContainerHeight - containerHeightWithPaddingsDeterminedByDensity; - CGFloat sumOfPaddingValues = _paddingAroundTextWhenNoFloatingLabel * 2.0f; - _paddingAroundTextWhenNoFloatingLabel = - _paddingAroundTextWhenNoFloatingLabel + - ((_paddingAroundTextWhenNoFloatingLabel / sumOfPaddingValues) * difference); - } - - if (clientHasSpecifiedValidPreferredContainerHeightWhenNoFloatingLabel) { + BOOL preferredContainerHeightIsValidForNoFloatingLabel = + preferredContainerHeight > defaultContainerHeightForNoFloatingLabel; + if (preferredContainerHeightIsValidForNoFloatingLabel) { _containerHeightWithoutFloatingLabel = preferredContainerHeight; + BOOL shouldUpdatePaddingValuesToMeetMinimumHeight = !isMultilineTextControl; + if (shouldUpdatePaddingValuesToMeetMinimumHeight) { + CGFloat difference = preferredContainerHeight - defaultContainerHeightForNoFloatingLabel; + CGFloat sumOfPaddingValues = _paddingAroundTextWhenNoFloatingLabel * 2.0f; + _paddingAroundTextWhenNoFloatingLabel = + _paddingAroundTextWhenNoFloatingLabel + + ((_paddingAroundTextWhenNoFloatingLabel / sumOfPaddingValues) * difference); + } } else { - _containerHeightWithoutFloatingLabel = - containerHeightWhenNoFloatingLabelWithPaddingsDeterminedByDensity; + _containerHeightWithoutFloatingLabel = defaultContainerHeightForNoFloatingLabel; + } + + if (isMultilineTextControl) { + CGFloat heightWithOneRow = MDCTextControlCalculateContainerHeightWithFloatingLabelHeight( + floatingLabelHeight, textRowHeight, 1, _paddingBetweenContainerTopAndFloatingLabel, + _paddingBetweenFloatingLabelAndEditingText, _paddingBetweenEditingTextAndContainerBottom); + CGFloat halfOfHeightWithOneRow = (CGFloat)0.5 * heightWithOneRow; + CGFloat halfOfNormalFontLineHeight = (CGFloat)0.5 * normalFontLineHeight; + _paddingBetweenContainerTopAndNormalLabel = halfOfHeightWithOneRow - halfOfNormalFontLineHeight; } } From a5ddb265b2967e449e18ab228f1da841e3bf4ae3 Mon Sep 17 00:00:00 2001 From: Yarden Eitan Date: Thu, 23 Jul 2020 16:15:08 -0700 Subject: [PATCH 24/43] [Shapes] Fixes for the shape shadow layer if the borderWidth is set multiple times and there isn't a prepareShadowPath pass. PiperOrigin-RevId: 322888549 --- components/Shapes/src/MDCShapedShadowLayer.m | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/components/Shapes/src/MDCShapedShadowLayer.m b/components/Shapes/src/MDCShapedShadowLayer.m index 9d1e4ee0ddb..fba26580adc 100644 --- a/components/Shapes/src/MDCShapedShadowLayer.m +++ b/components/Shapes/src/MDCShapedShadowLayer.m @@ -113,6 +113,7 @@ - (void)setPath:(CGPathRef)path { - (void)generateColorPathGivenLineWidth { if (CGPathIsEmpty(self.path) || _colorLayer.lineWidth <= 0) { + _colorLayer.path = _shapeLayer.path; return; } CGFloat halfOfBorderWidth = self.shapedBorderWidth / 2.f; @@ -126,7 +127,7 @@ - (void)generateColorPathGivenLineWidth { transform = CGAffineTransformScale(transform, CGRectGetWidth(insetBounds) / width, CGRectGetHeight(insetBounds) / height); } - _colorLayer.path = CGPathCreateCopyByTransformingPath(_colorLayer.path, &transform); + _colorLayer.path = CGPathCreateCopyByTransformingPath(_shapeLayer.path, &transform); } - (CGPathRef)path { From 719d68811944b8d20e92d83db3d95d6844dedc70 Mon Sep 17 00:00:00 2001 From: Yarden Eitan Date: Thu, 23 Jul 2020 18:28:35 -0700 Subject: [PATCH 25/43] [Shapes] Add test demonstrating issue with calling borderWidth multiple times with same value. PiperOrigin-RevId: 322911175 --- .../examples/MDCShapeWithBorderExample.m | 73 +++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 components/schemes/Shape/examples/MDCShapeWithBorderExample.m diff --git a/components/schemes/Shape/examples/MDCShapeWithBorderExample.m b/components/schemes/Shape/examples/MDCShapeWithBorderExample.m new file mode 100644 index 00000000000..0295c0fa874 --- /dev/null +++ b/components/schemes/Shape/examples/MDCShapeWithBorderExample.m @@ -0,0 +1,73 @@ +// 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 "MaterialCards.h" +#import "MaterialCards+Theming.h" +#import "MaterialShapeLibrary.h" +#import "MaterialShapes.h" + +@interface MDCShapeWithBorderExample : UIViewController +@property(strong, nonatomic) MDCCard *card; +@end + +@implementation MDCShapeWithBorderExample + +- (void)viewDidLoad { + [super viewDidLoad]; + + self.view.backgroundColor = UIColor.whiteColor; + self.card = [[MDCCard alloc] initWithFrame:CGRectMake(0, 0, 100, 100)]; + self.card.center = self.view.center; + self.card.backgroundColor = UIColor.blueColor; + + MDCRectangleShapeGenerator *rectangleShape = [[MDCRectangleShapeGenerator alloc] init]; + [rectangleShape setCorners:[MDCCornerTreatment cornerWithRadius:8]]; + self.card.shapeGenerator = rectangleShape; + [self.card setBorderColor:UIColor.blackColor forState:UIControlStateNormal]; + [self.card setBorderColor:UIColor.blackColor forState:UIControlStateHighlighted]; + [self.card setBorderWidth:1 forState:UIControlStateNormal]; + [self.card setBorderWidth:4 forState:UIControlStateHighlighted]; + + [self.view addSubview:self.card]; +} + +@end + +#pragma mark - Catalog by convention +@implementation MDCShapeWithBorderExample (CatlogByConvention) + ++ (NSDictionary *)catalogMetadata { + return @{ + @"breadcrumbs" : @[ @"Shape", @"Card with Border example" ], + @"primaryDemo" : @NO, + @"presentable" : @YES, + }; +} + +@end + +@implementation MDCShapeWithBorderExample (SnapshotTestingByConvention) + +- (void)testTappingMultipleTimesOnCard { + // When + self.card.highlighted = YES; + self.card.highlighted = YES; + self.card.highlighted = YES; + self.card.highlighted = YES; + self.card.highlighted = YES; + self.card.highlighted = YES; + self.card.highlighted = YES; +} + +@end From f4263fe70ed103b05912bec7cdf48b271d3b31ca Mon Sep 17 00:00:00 2001 From: Yarden Eitan Date: Thu, 23 Jul 2020 18:30:45 -0700 Subject: [PATCH 26/43] [Shape] Update shapeLayer to the correct path to correctly be a mask for content. PiperOrigin-RevId: 322911498 --- components/Shapes/src/MDCShapedShadowLayer.m | 21 ++++++++++++++----- .../snapshot/MDCShapedViewSnapshotTests.m | 20 ++++++++++++++++++ .../unit/MDCShapedShadowLayerPathTests.m | 17 +++++++++++++++ 3 files changed, 53 insertions(+), 5 deletions(-) diff --git a/components/Shapes/src/MDCShapedShadowLayer.m b/components/Shapes/src/MDCShapedShadowLayer.m index fba26580adc..38110ade172 100644 --- a/components/Shapes/src/MDCShapedShadowLayer.m +++ b/components/Shapes/src/MDCShapedShadowLayer.m @@ -113,21 +113,32 @@ - (void)setPath:(CGPathRef)path { - (void)generateColorPathGivenLineWidth { if (CGPathIsEmpty(self.path) || _colorLayer.lineWidth <= 0) { - _colorLayer.path = _shapeLayer.path; + _colorLayer.path = self.shadowPath; + _shapeLayer.path = self.shadowPath; return; } CGFloat halfOfBorderWidth = self.shapedBorderWidth / 2.f; + CGAffineTransform colorLayerTransform = [self generateTransformInsetByValue:halfOfBorderWidth]; + _colorLayer.path = CGPathCreateCopyByTransformingPath(self.shadowPath, &colorLayerTransform); + // The shape layer is used to provide the user a mask for their content, which means also + // show the full border. Because the border is shown half outside and half inside + // the color layer path, we must inset the shape layer by the full border width. + CGAffineTransform shapeLayerTransform = + [self generateTransformInsetByValue:self.shapedBorderWidth]; + _shapeLayer.path = CGPathCreateCopyByTransformingPath(self.shadowPath, &shapeLayerTransform); +} + +- (CGAffineTransform)generateTransformInsetByValue:(CGFloat)value { CGRect standardizedBounds = CGRectStandardize(self.bounds); - CGRect insetBounds = CGRectInset(standardizedBounds, halfOfBorderWidth, halfOfBorderWidth); - CGAffineTransform transform = - CGAffineTransformMakeTranslation(halfOfBorderWidth, halfOfBorderWidth); + CGRect insetBounds = CGRectInset(standardizedBounds, value, value); + CGAffineTransform transform = CGAffineTransformMakeTranslation(value, value); CGFloat width = CGRectGetWidth(standardizedBounds); CGFloat height = CGRectGetHeight(standardizedBounds); if (width > kDimensionalEpsilon && height > kDimensionalEpsilon) { transform = CGAffineTransformScale(transform, CGRectGetWidth(insetBounds) / width, CGRectGetHeight(insetBounds) / height); } - _colorLayer.path = CGPathCreateCopyByTransformingPath(_shapeLayer.path, &transform); + return transform; } - (CGPathRef)path { diff --git a/components/Shapes/tests/snapshot/MDCShapedViewSnapshotTests.m b/components/Shapes/tests/snapshot/MDCShapedViewSnapshotTests.m index 2b9d358a154..2129dd9562b 100644 --- a/components/Shapes/tests/snapshot/MDCShapedViewSnapshotTests.m +++ b/components/Shapes/tests/snapshot/MDCShapedViewSnapshotTests.m @@ -52,6 +52,26 @@ - (void)generateSnapshotAndVerifyView { #pragma mark - Tests +- (void)testRectShapedViewWithBorderWidthAndCorrectMaskingOfContent { + // Given + MDCShapedShadowLayer *shadowLayer = (MDCShapedShadowLayer *)self.shapedView.layer; + MDCRectangleShapeGenerator *shapeGenerator = [[MDCRectangleShapeGenerator alloc] init]; + MDCRoundedCornerTreatment *cornerTreatment = [MDCRoundedCornerTreatment cornerWithRadius:50.f]; + [shapeGenerator setCorners:cornerTreatment]; + shadowLayer.shapedBorderWidth = 10; + shadowLayer.shapedBorderColor = UIColor.redColor; + UIView *contentView = [[UIView alloc] initWithFrame:self.shapedView.bounds]; + contentView.backgroundColor = UIColor.systemPinkColor; + [self.shapedView addSubview:contentView]; + + // When + self.shapedView.shapeGenerator = shapeGenerator; + contentView.layer.mask = shadowLayer.shapeLayer; + + // Then + [self generateSnapshotAndVerifyView]; +} + - (void)testRectShapedViewWithCornerRadiusBySettingABorderWidthToPositiveThenZero { // Given MDCRectangleShapeGenerator *shapeGenerator = [[MDCRectangleShapeGenerator alloc] init]; diff --git a/components/Shapes/tests/unit/MDCShapedShadowLayerPathTests.m b/components/Shapes/tests/unit/MDCShapedShadowLayerPathTests.m index d026bc5f68d..25a770a104e 100644 --- a/components/Shapes/tests/unit/MDCShapedShadowLayerPathTests.m +++ b/components/Shapes/tests/unit/MDCShapedShadowLayerPathTests.m @@ -43,4 +43,21 @@ - (void)testEmptyFrameWithNonEmptyPathGeneratesColorLayerPathMatchingPath { XCTAssertTrue(CGPathEqualToPath(shadowLayer.colorLayer.path, insetPath.CGPath)); } +- (void)testGeneratedColorLayerAndShapeLayerPathsGivenABorderAndShadowLayer { + // Given + MDCShapedShadowLayer *shadowLayer = [[MDCShapedShadowLayer alloc] init]; + UIBezierPath *bezierPath = [UIBezierPath bezierPathWithRect:CGRectMake(0, 0, 100, 100)]; + + // When + shadowLayer.frame = CGRectMake(0, 0, 100, 100); + shadowLayer.shapedBorderWidth = 6; + shadowLayer.path = bezierPath.CGPath; + + // Then + UIBezierPath *halfInsetPath = [UIBezierPath bezierPathWithRect:CGRectMake(3, 3, 94, 94)]; + XCTAssertTrue(CGPathEqualToPath(shadowLayer.colorLayer.path, halfInsetPath.CGPath)); + UIBezierPath *fullInsetPath = [UIBezierPath bezierPathWithRect:CGRectMake(6, 6, 88, 88)]; + XCTAssertTrue(CGPathEqualToPath(shadowLayer.shapeLayer.path, fullInsetPath.CGPath)); +} + @end From e360f3c1c4e26bfad204876072241de753fc261e Mon Sep 17 00:00:00 2001 From: Andrew Overton Date: Fri, 24 Jul 2020 08:20:25 -0700 Subject: [PATCH 27/43] [TextControls] Expose preferredContainerHeight on MDCBaseTextField PiperOrigin-RevId: 322999254 --- .../src/BaseTextFields/MDCBaseTextField.h | 10 +++++++ .../src/BaseTextFields/MDCBaseTextField.m | 1 - .../snapshot/MDCBaseTextFieldSnapshotTests.m | 24 +++++++++++++++ .../MDCFilledTextFieldSnapshotTests.m | 29 +++++++++++++++++-- .../MDCOutlinedTextFieldSnapshotTests.m | 29 +++++++++++++++++-- .../MDCUnderlinedTextFieldSnapshotTests.m | 26 +++++++++++++++++ ...MDCBaseTextFieldTestsSnapshotTestHelpers.h | 4 +++ ...MDCBaseTextFieldTestsSnapshotTestHelpers.m | 20 +++++++++++-- 8 files changed, 136 insertions(+), 7 deletions(-) diff --git a/components/TextControls/src/BaseTextFields/MDCBaseTextField.h b/components/TextControls/src/BaseTextFields/MDCBaseTextField.h index 796ac9375d0..99533f4fb74 100644 --- a/components/TextControls/src/BaseTextFields/MDCBaseTextField.h +++ b/components/TextControls/src/BaseTextFields/MDCBaseTextField.h @@ -165,6 +165,16 @@ */ @property(nullable, nonatomic, strong) NSNumber *trailingEdgePaddingOverride; +/** + This property allows the user to override the default height of the container. The container is the + region above the the assistive labels within the text field. If there is no assistive label text, + the container's frame will be equal to the frame of the text field itself. + + If this property is set to a value that's smaller than the + default height of the container it will be ignored. + */ +@property(nonatomic, assign) CGFloat preferredContainerHeight; + @end @interface MDCBaseTextField (UIAccessibility) diff --git a/components/TextControls/src/BaseTextFields/MDCBaseTextField.m b/components/TextControls/src/BaseTextFields/MDCBaseTextField.m index 18201cb0511..9d9ea3bc859 100644 --- a/components/TextControls/src/BaseTextFields/MDCBaseTextField.m +++ b/components/TextControls/src/BaseTextFields/MDCBaseTextField.m @@ -47,7 +47,6 @@ @implementation MDCBaseTextField @synthesize containerStyle = _containerStyle; @synthesize assistiveLabelDrawPriority = _assistiveLabelDrawPriority; @synthesize customAssistiveLabelDrawPriority = _customAssistiveLabelDrawPriority; -@synthesize preferredContainerHeight = _preferredContainerHeight; #pragma mark Object Lifecycle diff --git a/components/TextControls/tests/snapshot/MDCBaseTextFieldSnapshotTests.m b/components/TextControls/tests/snapshot/MDCBaseTextFieldSnapshotTests.m index fc4ccb23a23..db067102fdc 100644 --- a/components/TextControls/tests/snapshot/MDCBaseTextFieldSnapshotTests.m +++ b/components/TextControls/tests/snapshot/MDCBaseTextFieldSnapshotTests.m @@ -210,4 +210,28 @@ - (void)testTextFieldWithScaledFontsAndXXXLargeContentSize { [self validateTextField:textField]; } +- (void)testTextFieldWithTextAndLabelTextAndPreferredContainerHeightWhileEditing { + // Given + MDCBaseTextField *textField = self.textField; + + // When + [MDCBaseTextFieldTestsSnapshotTestHelpers + configureTextFieldWithTextAndLabelTextAndPreferredContainerHeightWhileEditing:textField]; + + // Then + [self validateTextField:textField]; +} + +- (void)testTextFieldWithTextAndNoLabelTextAndPreferredContainerHeightWhileEditing { + // Given + MDCBaseTextField *textField = self.textField; + + // When + [MDCBaseTextFieldTestsSnapshotTestHelpers + configureTextFieldWithTextAndNoLabelTextAndPreferredContainerHeightWhileEditing:textField]; + + // Then + [self validateTextField:textField]; +} + @end diff --git a/components/TextControls/tests/snapshot/MDCFilledTextFieldSnapshotTests.m b/components/TextControls/tests/snapshot/MDCFilledTextFieldSnapshotTests.m index 625c09e329a..0f9661210a9 100644 --- a/components/TextControls/tests/snapshot/MDCFilledTextFieldSnapshotTests.m +++ b/components/TextControls/tests/snapshot/MDCFilledTextFieldSnapshotTests.m @@ -16,10 +16,11 @@ #import -#import "MaterialTextControls+FilledTextFieldsTheming.h" -#import "MaterialTextControls+OutlinedTextFieldsTheming.h" #import "supplemental/MDCBaseTextFieldTestsSnapshotTestHelpers.h" #import "supplemental/MDCTextControlSnapshotTestHelpers.h" +#import "MaterialTextControls+FilledTextFields.h" +#import "MaterialTextControls+FilledTextFieldsTheming.h" +#import "MaterialTextControls+OutlinedTextFieldsTheming.h" @interface MDCFilledTextFieldTestsSnapshotTests : MDCSnapshotTestCase @property(strong, nonatomic) MDCFilledTextField *textField; @@ -210,4 +211,28 @@ - (void)testTextFieldWithScaledFontsAndXXXLargeContentSize { [self validateTextField:textField]; } +- (void)testTextFieldWithTextAndLabelTextAndPreferredContainerHeightWhileEditing { + // Given + MDCFilledTextField *textField = self.textField; + + // When + [MDCBaseTextFieldTestsSnapshotTestHelpers + configureTextFieldWithTextAndLabelTextAndPreferredContainerHeightWhileEditing:textField]; + + // Then + [self validateTextField:textField]; +} + +- (void)testTextFieldWithTextAndNoLabelTextAndPreferredContainerHeightWhileEditing { + // Given + MDCFilledTextField *textField = self.textField; + + // When + [MDCBaseTextFieldTestsSnapshotTestHelpers + configureTextFieldWithTextAndNoLabelTextAndPreferredContainerHeightWhileEditing:textField]; + + // Then + [self validateTextField:textField]; +} + @end diff --git a/components/TextControls/tests/snapshot/MDCOutlinedTextFieldSnapshotTests.m b/components/TextControls/tests/snapshot/MDCOutlinedTextFieldSnapshotTests.m index a938bcd2388..6814c09aa3d 100644 --- a/components/TextControls/tests/snapshot/MDCOutlinedTextFieldSnapshotTests.m +++ b/components/TextControls/tests/snapshot/MDCOutlinedTextFieldSnapshotTests.m @@ -16,10 +16,11 @@ #import -#import "MaterialTextControls+FilledTextFieldsTheming.h" -#import "MaterialTextControls+OutlinedTextFieldsTheming.h" #import "supplemental/MDCBaseTextFieldTestsSnapshotTestHelpers.h" #import "supplemental/MDCTextControlSnapshotTestHelpers.h" +#import "MaterialTextControls+FilledTextFieldsTheming.h" +#import "MaterialTextControls+OutlinedTextFields.h" +#import "MaterialTextControls+OutlinedTextFieldsTheming.h" @interface MDCOutlinedTextFieldTestsSnapshotTests : MDCSnapshotTestCase @property(strong, nonatomic) MDCOutlinedTextField *textField; @@ -210,4 +211,28 @@ - (void)testTextFieldWithScaledFontsAndXXXLargeContentSize { [self validateTextField:textField]; } +- (void)testTextFieldWithTextAndLabelTextAndPreferredContainerHeightWhileEditing { + // Given + MDCOutlinedTextField *textField = self.textField; + + // When + [MDCBaseTextFieldTestsSnapshotTestHelpers + configureTextFieldWithTextAndLabelTextAndPreferredContainerHeightWhileEditing:textField]; + + // Then + [self validateTextField:textField]; +} + +- (void)testTextFieldWithTextAndNoLabelTextAndPreferredContainerHeightWhileEditing { + // Given + MDCOutlinedTextField *textField = self.textField; + + // When + [MDCBaseTextFieldTestsSnapshotTestHelpers + configureTextFieldWithTextAndNoLabelTextAndPreferredContainerHeightWhileEditing:textField]; + + // Then + [self validateTextField:textField]; +} + @end diff --git a/components/TextControls/tests/snapshot/MDCUnderlinedTextFieldSnapshotTests.m b/components/TextControls/tests/snapshot/MDCUnderlinedTextFieldSnapshotTests.m index fa0d82c4c77..5ec9e62d0b6 100644 --- a/components/TextControls/tests/snapshot/MDCUnderlinedTextFieldSnapshotTests.m +++ b/components/TextControls/tests/snapshot/MDCUnderlinedTextFieldSnapshotTests.m @@ -18,7 +18,9 @@ #import "supplemental/MDCBaseTextFieldTestsSnapshotTestHelpers.h" #import "supplemental/MDCTextControlSnapshotTestHelpers.h" + #import "MaterialTextControls+FilledTextFieldsTheming.h" +#import "MaterialTextControls+UnderlinedTextFields.h" #import "MaterialTextControls+UnderlinedTextFieldsTheming.h" @interface MDCUnderlinedTextFieldTestsSnapshotTests : MDCSnapshotTestCase @@ -210,4 +212,28 @@ - (void)testTextFieldWithScaledFontsAndXXXLargeContentSize { [self validateTextField:textField]; } +- (void)testTextFieldWithTextAndLabelTextAndPreferredContainerHeightWhileEditing { + // Given + MDCUnderlinedTextField *textField = self.textField; + + // When + [MDCBaseTextFieldTestsSnapshotTestHelpers + configureTextFieldWithTextAndLabelTextAndPreferredContainerHeightWhileEditing:textField]; + + // Then + [self validateTextField:textField]; +} + +- (void)testTextFieldWithTextAndNoLabelTextAndPreferredContainerHeightWhileEditing { + // Given + MDCUnderlinedTextField *textField = self.textField; + + // When + [MDCBaseTextFieldTestsSnapshotTestHelpers + configureTextFieldWithTextAndNoLabelTextAndPreferredContainerHeightWhileEditing:textField]; + + // Then + [self validateTextField:textField]; +} + @end diff --git a/components/TextControls/tests/snapshot/supplemental/MDCBaseTextFieldTestsSnapshotTestHelpers.h b/components/TextControls/tests/snapshot/supplemental/MDCBaseTextFieldTestsSnapshotTestHelpers.h index 6a74fbca727..3cc5fed41ba 100644 --- a/components/TextControls/tests/snapshot/supplemental/MDCBaseTextFieldTestsSnapshotTestHelpers.h +++ b/components/TextControls/tests/snapshot/supplemental/MDCBaseTextFieldTestsSnapshotTestHelpers.h @@ -41,5 +41,9 @@ + (void)configureTextFieldWithColoredAssistiveLabelTextWhileEditing:(MDCBaseTextField *)textField; + (void)configureTextFieldWithColoredAssistiveLabelTextWhileDisabled:(MDCBaseTextField *)textField; + (void)configureTextFieldWithScaledFontsAndAXXXLargeContentSize:(MDCBaseTextField *)textField; ++ (void)configureTextFieldWithTextAndLabelTextAndPreferredContainerHeightWhileEditing: + (MDCBaseTextField *)textField; ++ (void)configureTextFieldWithTextAndNoLabelTextAndPreferredContainerHeightWhileEditing: + (MDCBaseTextField *)textField; @end diff --git a/components/TextControls/tests/snapshot/supplemental/MDCBaseTextFieldTestsSnapshotTestHelpers.m b/components/TextControls/tests/snapshot/supplemental/MDCBaseTextFieldTestsSnapshotTestHelpers.m index 96fb122a055..5e68e5c85b3 100644 --- a/components/TextControls/tests/snapshot/supplemental/MDCBaseTextFieldTestsSnapshotTestHelpers.m +++ b/components/TextControls/tests/snapshot/supplemental/MDCBaseTextFieldTestsSnapshotTestHelpers.m @@ -12,12 +12,13 @@ // See the License for the specific language governing permissions and // limitations under the License. -#import "MaterialSnapshot.h" +#import "MDCBaseTextFieldTestsSnapshotTestHelpers.h" #import -#import "MDCBaseTextFieldTestsSnapshotTestHelpers.h" +#import "MaterialTextControls+BaseTextFields.h" #import "MDCTextControlSnapshotTestHelpers.h" +#import "MaterialSnapshot.h" @interface MDCBaseTextField (AnimationDuration) @property(nonatomic, assign) NSTimeInterval animationDuration; @@ -192,6 +193,21 @@ + (void)configureTextFieldWithScaledFontsAndAXXXLargeContentSize:(MDCBaseTextFie [textField becomeFirstResponder]; } ++ (void)configureTextFieldWithTextAndLabelTextAndPreferredContainerHeightWhileEditing: + (MDCBaseTextField *)textField { + textField.text = @"Some text"; + textField.label.text = @"Label text"; + textField.preferredContainerHeight = 100; + [textField becomeFirstResponder]; +} + ++ (void)configureTextFieldWithTextAndNoLabelTextAndPreferredContainerHeightWhileEditing: + (MDCBaseTextField *)textField { + textField.text = @"Some text"; + textField.preferredContainerHeight = 100; + [textField becomeFirstResponder]; +} + #pragma mark Helpers + (UIView *)createBlueSideView { From 0950ece8f3d980d9fd13cd2718dc32e67e4ed18c Mon Sep 17 00:00:00 2001 From: Andrew Overton Date: Fri, 24 Jul 2020 08:40:27 -0700 Subject: [PATCH 28/43] [TextControls] Allow setting underline thickness regardless of state PiperOrigin-RevId: 323002391 --- .../MDCUnderlinedTextField.h | 24 ++ .../MDCUnderlinedTextField.m | 28 +- .../MDCTextControlStyleUnderlined.h | 24 ++ .../MDCTextControlStyleUnderlined.m | 347 +++++++++++------- 4 files changed, 280 insertions(+), 143 deletions(-) diff --git a/components/TextControls/src/UnderlinedTextFields/MDCUnderlinedTextField.h b/components/TextControls/src/UnderlinedTextFields/MDCUnderlinedTextField.h index 5423dd0bf12..ed88c7fdea1 100644 --- a/components/TextControls/src/UnderlinedTextFields/MDCUnderlinedTextField.h +++ b/components/TextControls/src/UnderlinedTextFields/MDCUnderlinedTextField.h @@ -26,6 +26,30 @@ __attribute__((objc_subclassing_restricted)) @interface MDCUnderlinedTextField : */ @property(nonatomic, assign) UITextBorderStyle borderStyle NS_UNAVAILABLE; +/** +The thickness of the underline that shows in the normal and disabled states. The default is 1. +*/ +@property(nonatomic, assign) CGFloat normalUnderlineThickness; + +/** +The thickness of the underline that shows in the editing state. The default is 2. +*/ +@property(nonatomic, assign) CGFloat editingUnderlineThickness; + +/** +Sets the normal underline thickness. +@param thickness The thickness of the underline. +@param animated Determines whether or not the change is animated. +*/ +- (void)setNormalUnderlineThickness:(CGFloat)thickness animated:(BOOL)animated; + +/** +Sets the editing underline thickness. +@param thickness The thickness of the underline. +@param animated Determines whether or not the change is animated. +*/ +- (void)setEditingUnderlineThickness:(CGFloat)thickness animated:(BOOL)animated; + /** Sets the underline color for a given state. @param underlineColor The UIColor for the given state. diff --git a/components/TextControls/src/UnderlinedTextFields/MDCUnderlinedTextField.m b/components/TextControls/src/UnderlinedTextFields/MDCUnderlinedTextField.m index c3a3e034410..a5566a05d31 100644 --- a/components/TextControls/src/UnderlinedTextFields/MDCUnderlinedTextField.m +++ b/components/TextControls/src/UnderlinedTextFields/MDCUnderlinedTextField.m @@ -48,11 +48,13 @@ - (void)commonMDCUnderlinedTextFieldInit { self.containerStyle = [[MDCTextControlStyleUnderlined alloc] init]; } +#pragma mark MDCTextControlTextField methods + - (MDCTextControlTextFieldSideViewAlignment)sideViewAlignment { return MDCTextControlTextFieldSideViewAlignmentAlignedWithText; } -#pragma mark Stateful Color APIs +#pragma mark Accessors - (void)setUnderlineColor:(UIColor *)underlineColor forState:(MDCTextControlState)state { [self.underlinedStyle setUnderlineColor:underlineColor forState:state]; @@ -63,8 +65,28 @@ - (UIColor *)underlineColorForState:(MDCTextControlState)state { return [self.underlinedStyle underlineColorForState:state]; } -- (void)layoutSubviews { - [super layoutSubviews]; +- (void)setNormalUnderlineThickness:(CGFloat)normalUnderlineThickness { + self.underlinedStyle.normalUnderlineThickness = normalUnderlineThickness; +} + +- (void)setEditingUnderlineThickness:(CGFloat)editingUnderlineThickness { + self.underlinedStyle.editingUnderlineThickness = editingUnderlineThickness; +} + +- (CGFloat)normalUnderlineThickness { + return self.underlinedStyle.normalUnderlineThickness; +} + +- (CGFloat)editingUnderlineThickness { + return self.underlinedStyle.editingUnderlineThickness; +} + +- (void)setNormalUnderlineThickness:(CGFloat)thickness animated:(BOOL)animated { + [self.underlinedStyle setNormalUnderlineThickness:thickness animated:animated]; +} + +- (void)setEditingUnderlineThickness:(CGFloat)thickness animated:(BOOL)animated { + [self.underlinedStyle setEditingUnderlineThickness:thickness animated:animated]; } #pragma mark Private Helpers diff --git a/components/private/TextControlsPrivate/src/UnderlinedStyle/MDCTextControlStyleUnderlined.h b/components/private/TextControlsPrivate/src/UnderlinedStyle/MDCTextControlStyleUnderlined.h index a6da0fd9705..c21b5266654 100644 --- a/components/private/TextControlsPrivate/src/UnderlinedStyle/MDCTextControlStyleUnderlined.h +++ b/components/private/TextControlsPrivate/src/UnderlinedStyle/MDCTextControlStyleUnderlined.h @@ -21,6 +21,30 @@ This style object is used by MDCTextControls adopting the Material Underlined st */ @interface MDCTextControlStyleUnderlined : NSObject +/** +The thickness of the underline that shows in the normal and disabled states. +*/ +@property(nonatomic, assign) CGFloat normalUnderlineThickness; + +/** +The thickness of the underline that shows in the editing state. +*/ +@property(nonatomic, assign) CGFloat editingUnderlineThickness; + +/** +Sets the normal underline thickness. +@param thickness The thickness of the underline. +@param animated Determines whether or not the change is animated. +*/ +- (void)setNormalUnderlineThickness:(CGFloat)thickness animated:(BOOL)animated; + +/** +Sets the editing underline thickness. +@param thickness The thickness of the underline. +@param animated Determines whether or not the change is animated. +*/ +- (void)setEditingUnderlineThickness:(CGFloat)thickness animated:(BOOL)animated; + /** Sets the underline color color for a given state. @param underlineColor The UIColor for the given state. diff --git a/components/private/TextControlsPrivate/src/UnderlinedStyle/MDCTextControlStyleUnderlined.m b/components/private/TextControlsPrivate/src/UnderlinedStyle/MDCTextControlStyleUnderlined.m index 251a8b02f83..fdaea78bfc4 100644 --- a/components/private/TextControlsPrivate/src/UnderlinedStyle/MDCTextControlStyleUnderlined.m +++ b/components/private/TextControlsPrivate/src/UnderlinedStyle/MDCTextControlStyleUnderlined.m @@ -17,29 +17,36 @@ #import #include "MaterialAvailability.h" +#import "MDCTextControlState.h" #import "MDCTextControl.h" #import "UIBezierPath+MDCTextControlStyle.h" #import "MDCTextControlVerticalPositioningReferenceUnderlined.h" -static const CGFloat kUnderlinedContainerStyleUnderlineWidthThin = 1.0f; -static const CGFloat kUnderlinedContainerStyleUnderlineWidthThick = 2.0f; +static const CGFloat kUnderlinedContainerStyleUnderlineThicknessNormal = 1.0f; +static const CGFloat kUnderlinedContainerStyleUnderlineThicknessEditing = 2.0f; static const CGFloat kUnderlinedFloatingLabelScaleFactor = 0.75f; static const CGFloat kUnderlinedHorizontalEdgePaddingDefault = 2; @interface MDCTextControlStyleUnderlined () -@property(strong, nonatomic) CAShapeLayer *thinUnderlineLayer; -@property(strong, nonatomic) CAShapeLayer *thickUnderlineLayer; +@property(strong, nonatomic) CAShapeLayer *normalUnderlineLayer; +@property(strong, nonatomic) CAShapeLayer *editingUnderlineLayer; -@property(strong, nonatomic, readonly, class) NSString *thickUnderlineGrowKey; -@property(strong, nonatomic, readonly, class) NSString *thickUnderlineShrinkKey; -@property(strong, nonatomic, readonly, class) NSString *thinUnderlineGrowKey; -@property(strong, nonatomic, readonly, class) NSString *thinUnderlineShrinkKey; +@property(strong, nonatomic, readonly, class) NSString *editingUnderlineGrowKey; +@property(strong, nonatomic, readonly, class) NSString *editingUnderlineShrinkKey; +@property(strong, nonatomic, readonly, class) NSString *normalUnderlineGrowKey; +@property(strong, nonatomic, readonly, class) NSString *normalUnderlineShrinkKey; + +@property(strong, nonatomic, readonly, class) NSString *editingUnderlineThicknessKey; +@property(strong, nonatomic, readonly, class) NSString *normalUnderlineThicknessKey; @property(strong, nonatomic) NSMutableDictionary *underlineColors; @property(nonatomic, assign) CGRect mostRecentBounds; +@property(nonatomic, assign) BOOL isEditing; +@property(nonatomic, assign) CGFloat containerHeight; +@property(nonatomic, assign) NSTimeInterval animationDuration; @end @@ -58,9 +65,11 @@ - (instancetype)init { #pragma mark Setup - (void)commonMDCTextControlStyleUnderlinedInit { + self.mostRecentBounds = CGRectZero; + self.normalUnderlineThickness = kUnderlinedContainerStyleUnderlineThicknessNormal; + self.editingUnderlineThickness = kUnderlinedContainerStyleUnderlineThicknessEditing; [self setUpUnderlineColors]; [self setUpUnderlineSublayers]; - self.mostRecentBounds = CGRectZero; } - (void)setUpUnderlineColors { @@ -77,8 +86,8 @@ - (void)setUpUnderlineColors { } - (void)setUpUnderlineSublayers { - self.thinUnderlineLayer = [[CAShapeLayer alloc] init]; - self.thickUnderlineLayer = [[CAShapeLayer alloc] init]; + self.normalUnderlineLayer = [[CAShapeLayer alloc] init]; + self.editingUnderlineLayer = [[CAShapeLayer alloc] init]; } #pragma mark Accessors @@ -91,6 +100,46 @@ - (void)setUnderlineColor:(nonnull UIColor *)underlineColor forState:(MDCTextCon self.underlineColors[@(state)] = underlineColor; } +- (void)setEditingUnderlineThickness:(CGFloat)editingUnderlineThickness { + [self setEditingUnderlineThickness:editingUnderlineThickness animated:NO]; +} + +- (void)setNormalUnderlineThickness:(CGFloat)normalUnderlineThickness { + [self setNormalUnderlineThickness:normalUnderlineThickness animated:NO]; +} + +- (void)setEditingUnderlineThickness:(CGFloat)thickness animated:(BOOL)animated { + _editingUnderlineThickness = thickness; + + UIBezierPath *targetUnderlineBezier = [self targetEditingUnderlineBezier]; + [self.editingUnderlineLayer removeAllAnimations]; + if (animated) { + [CATransaction begin]; + [CATransaction setAnimationDuration:self.animationDuration]; + [self.editingUnderlineLayer addAnimation:[self pathAnimationTo:targetUnderlineBezier] + forKey:self.class.editingUnderlineThicknessKey]; + [CATransaction commit]; + } else { + self.editingUnderlineLayer.path = targetUnderlineBezier.CGPath; + } +} + +- (void)setNormalUnderlineThickness:(CGFloat)thickness animated:(BOOL)animated { + _normalUnderlineThickness = thickness; + + UIBezierPath *targetUnderlineBezier = [self targetNormalUnderlineBezier]; + [self.normalUnderlineLayer removeAllAnimations]; + if (animated) { + [CATransaction begin]; + [CATransaction setAnimationDuration:self.animationDuration]; + [self.normalUnderlineLayer addAnimation:[self pathAnimationTo:targetUnderlineBezier] + forKey:self.class.normalUnderlineThicknessKey]; + [CATransaction commit]; + } else { + self.normalUnderlineLayer.path = targetUnderlineBezier.CGPath; + } +} + #pragma mark MDCTextControl - (void)applyStyleToTextControl:(UIView *)textControl @@ -102,8 +151,8 @@ - (void)applyStyleToTextControl:(UIView *)textControl } - (void)removeStyleFrom:(id)textControl { - [self.thinUnderlineLayer removeFromSuperlayer]; - [self.thickUnderlineLayer removeFromSuperlayer]; + [self.normalUnderlineLayer removeFromSuperlayer]; + [self.editingUnderlineLayer removeFromSuperlayer]; } - (id) @@ -150,158 +199,133 @@ - (void)applyUnderlineStyleToView:(UIView *)view self.mostRecentBounds = view.bounds; } - self.thinUnderlineLayer.fillColor = [self.underlineColors[@(state)] CGColor]; - self.thickUnderlineLayer.fillColor = [self.underlineColors[@(state)] CGColor]; + self.containerHeight = CGRectGetMaxY(containerFrame); + self.isEditing = state == MDCTextControlStateEditing; + self.animationDuration = animationDuration; - CGFloat containerHeight = CGRectGetMaxY(containerFrame); + self.normalUnderlineLayer.fillColor = [self.underlineColors[@(state)] CGColor]; + self.editingUnderlineLayer.fillColor = [self.underlineColors[@(state)] CGColor]; BOOL styleIsBeingAppliedToView = NO; - if (self.thickUnderlineLayer.superlayer != view.layer) { - [view.layer insertSublayer:self.thickUnderlineLayer atIndex:0]; + if (self.editingUnderlineLayer.superlayer != view.layer) { + [view.layer insertSublayer:self.editingUnderlineLayer atIndex:0]; styleIsBeingAppliedToView = YES; } - if (self.thinUnderlineLayer.superlayer != view.layer) { - [view.layer insertSublayer:self.thinUnderlineLayer atIndex:0]; + if (self.normalUnderlineLayer.superlayer != view.layer) { + [view.layer insertSublayer:self.normalUnderlineLayer atIndex:0]; styleIsBeingAppliedToView = YES; } - BOOL shouldShowThickUnderline = [self shouldShowThickUnderlineWithState:state]; - CGFloat viewWidth = CGRectGetWidth(view.bounds); - CGFloat thickUnderlineThickness = shouldShowThickUnderline ? viewWidth : 0; - UIBezierPath *targetThickUnderlineBezier = - [self filledSublayerUnderlinePathWithViewBounds:view.bounds - containerHeight:containerHeight - underlineThickness:kUnderlinedContainerStyleUnderlineWidthThick - underlineWidth:thickUnderlineThickness]; - CGFloat thinUnderlineThickness = - shouldShowThickUnderline ? 0 : kUnderlinedContainerStyleUnderlineWidthThin; - UIBezierPath *targetThinUnderlineBezier = - [self filledSublayerUnderlinePathWithViewBounds:view.bounds - containerHeight:containerHeight - underlineThickness:thinUnderlineThickness - underlineWidth:viewWidth]; - + UIBezierPath *targetEditingUnderlineBezier = [self targetEditingUnderlineBezier]; + UIBezierPath *targetNormalUnderlineBezier = [self targetNormalUnderlineBezier]; if (animationDuration <= 0 || styleIsBeingAppliedToView || didChangeBounds) { - [self.thinUnderlineLayer removeAllAnimations]; - [self.thickUnderlineLayer removeAllAnimations]; - self.thinUnderlineLayer.path = targetThinUnderlineBezier.CGPath; - self.thickUnderlineLayer.path = targetThickUnderlineBezier.CGPath; + [self.normalUnderlineLayer removeAllAnimations]; + [self.editingUnderlineLayer removeAllAnimations]; + self.normalUnderlineLayer.path = targetNormalUnderlineBezier.CGPath; + self.editingUnderlineLayer.path = targetEditingUnderlineBezier.CGPath; return; } - CABasicAnimation *preexistingThickUnderlineShrinkAnimation = - (CABasicAnimation *)[self.thickUnderlineLayer - animationForKey:self.class.thickUnderlineShrinkKey]; - CABasicAnimation *preexistingThickUnderlineGrowAnimation = - (CABasicAnimation *)[self.thickUnderlineLayer - animationForKey:self.class.thickUnderlineGrowKey]; + CABasicAnimation *preexistingEditingUnderlineShrinkAnimation = + (CABasicAnimation *)[self.editingUnderlineLayer + animationForKey:self.class.editingUnderlineShrinkKey]; + CABasicAnimation *preexistingEditingUnderlineGrowAnimation = + (CABasicAnimation *)[self.editingUnderlineLayer + animationForKey:self.class.editingUnderlineGrowKey]; - CABasicAnimation *preexistingThinUnderlineGrowAnimation = - (CABasicAnimation *)[self.thinUnderlineLayer animationForKey:self.class.thinUnderlineGrowKey]; - CABasicAnimation *preexistingThinUnderlineShrinkAnimation = - (CABasicAnimation *)[self.thinUnderlineLayer - animationForKey:self.class.thinUnderlineShrinkKey]; + CABasicAnimation *preexistingNormalUnderlineGrowAnimation = + (CABasicAnimation *)[self.normalUnderlineLayer + animationForKey:self.class.normalUnderlineGrowKey]; + CABasicAnimation *preexistingNormalUnderlineShrinkAnimation = + (CABasicAnimation *)[self.normalUnderlineLayer + animationForKey:self.class.normalUnderlineShrinkKey]; [CATransaction begin]; { [CATransaction setAnimationDuration:animationDuration]; - if (shouldShowThickUnderline) { - if (preexistingThickUnderlineShrinkAnimation) { - [self.thickUnderlineLayer removeAnimationForKey:self.class.thickUnderlineShrinkKey]; + if (self.isEditing) { + if (preexistingEditingUnderlineShrinkAnimation) { + [self.editingUnderlineLayer removeAnimationForKey:self.class.editingUnderlineShrinkKey]; } - BOOL needsThickUnderlineGrowAnimation = NO; - if (preexistingThickUnderlineGrowAnimation) { - CGPathRef toValue = (__bridge CGPathRef)preexistingThickUnderlineGrowAnimation.toValue; - if (!CGPathEqualToPath(toValue, targetThickUnderlineBezier.CGPath)) { - [self.thickUnderlineLayer removeAnimationForKey:self.class.thickUnderlineGrowKey]; - needsThickUnderlineGrowAnimation = YES; - self.thickUnderlineLayer.path = targetThickUnderlineBezier.CGPath; + BOOL needsEditingUnderlineGrowAnimation = NO; + if (preexistingEditingUnderlineGrowAnimation) { + CGPathRef toValue = (__bridge CGPathRef)preexistingEditingUnderlineGrowAnimation.toValue; + if (!CGPathEqualToPath(toValue, targetEditingUnderlineBezier.CGPath)) { + [self.editingUnderlineLayer removeAnimationForKey:self.class.editingUnderlineGrowKey]; + needsEditingUnderlineGrowAnimation = YES; + self.editingUnderlineLayer.path = targetEditingUnderlineBezier.CGPath; } } else { - needsThickUnderlineGrowAnimation = YES; + needsEditingUnderlineGrowAnimation = YES; } - if (needsThickUnderlineGrowAnimation) { - [self.thickUnderlineLayer addAnimation:[self pathAnimationTo:targetThickUnderlineBezier] - forKey:self.class.thickUnderlineGrowKey]; + if (needsEditingUnderlineGrowAnimation) { + [self.editingUnderlineLayer addAnimation:[self pathAnimationTo:targetEditingUnderlineBezier] + forKey:self.class.editingUnderlineGrowKey]; } - if (preexistingThinUnderlineGrowAnimation) { - [self.thinUnderlineLayer removeAnimationForKey:self.class.thinUnderlineGrowKey]; + if (preexistingNormalUnderlineGrowAnimation) { + [self.normalUnderlineLayer removeAnimationForKey:self.class.normalUnderlineGrowKey]; } - BOOL needsThinUnderlineShrinkAnimation = NO; - if (preexistingThinUnderlineShrinkAnimation) { - CGPathRef toValue = (__bridge CGPathRef)preexistingThinUnderlineShrinkAnimation.toValue; - if (!CGPathEqualToPath(toValue, targetThinUnderlineBezier.CGPath)) { - [self.thinUnderlineLayer removeAnimationForKey:self.class.thinUnderlineShrinkKey]; - needsThinUnderlineShrinkAnimation = YES; - self.thinUnderlineLayer.path = targetThinUnderlineBezier.CGPath; + BOOL needsNormalUnderlineShrinkAnimation = NO; + if (preexistingNormalUnderlineShrinkAnimation) { + CGPathRef toValue = (__bridge CGPathRef)preexistingNormalUnderlineShrinkAnimation.toValue; + if (!CGPathEqualToPath(toValue, targetNormalUnderlineBezier.CGPath)) { + [self.normalUnderlineLayer removeAnimationForKey:self.class.normalUnderlineShrinkKey]; + needsNormalUnderlineShrinkAnimation = YES; + self.normalUnderlineLayer.path = targetNormalUnderlineBezier.CGPath; } } else { - needsThinUnderlineShrinkAnimation = YES; + needsNormalUnderlineShrinkAnimation = YES; } - if (needsThinUnderlineShrinkAnimation) { - [self.thinUnderlineLayer addAnimation:[self pathAnimationTo:targetThinUnderlineBezier] - forKey:self.class.thinUnderlineShrinkKey]; + if (needsNormalUnderlineShrinkAnimation) { + [self.normalUnderlineLayer addAnimation:[self pathAnimationTo:targetNormalUnderlineBezier] + forKey:self.class.normalUnderlineShrinkKey]; } } else { - if (preexistingThickUnderlineGrowAnimation) { - [self.thickUnderlineLayer removeAnimationForKey:self.class.thickUnderlineGrowKey]; + if (preexistingEditingUnderlineGrowAnimation) { + [self.editingUnderlineLayer removeAnimationForKey:self.class.editingUnderlineGrowKey]; } - BOOL needsThickUnderlineShrinkAnimation = NO; - if (preexistingThickUnderlineShrinkAnimation) { - CGPathRef toValue = (__bridge CGPathRef)preexistingThickUnderlineShrinkAnimation.toValue; - if (!CGPathEqualToPath(toValue, targetThickUnderlineBezier.CGPath)) { - [self.thickUnderlineLayer removeAnimationForKey:self.class.thickUnderlineShrinkKey]; - needsThickUnderlineShrinkAnimation = YES; - self.thickUnderlineLayer.path = targetThickUnderlineBezier.CGPath; + BOOL needsEditingUnderlineShrinkAnimation = NO; + if (preexistingEditingUnderlineShrinkAnimation) { + CGPathRef toValue = (__bridge CGPathRef)preexistingEditingUnderlineShrinkAnimation.toValue; + if (!CGPathEqualToPath(toValue, targetEditingUnderlineBezier.CGPath)) { + [self.editingUnderlineLayer removeAnimationForKey:self.class.editingUnderlineShrinkKey]; + needsEditingUnderlineShrinkAnimation = YES; + self.editingUnderlineLayer.path = targetEditingUnderlineBezier.CGPath; } } else { - needsThickUnderlineShrinkAnimation = YES; + needsEditingUnderlineShrinkAnimation = YES; } - if (needsThickUnderlineShrinkAnimation) { - [self.thickUnderlineLayer addAnimation:[self pathAnimationTo:targetThickUnderlineBezier] - forKey:self.class.thickUnderlineShrinkKey]; + if (needsEditingUnderlineShrinkAnimation) { + [self.editingUnderlineLayer addAnimation:[self pathAnimationTo:targetEditingUnderlineBezier] + forKey:self.class.editingUnderlineShrinkKey]; } - if (preexistingThinUnderlineShrinkAnimation) { - [self.thinUnderlineLayer removeAnimationForKey:self.class.thinUnderlineShrinkKey]; + if (preexistingNormalUnderlineShrinkAnimation) { + [self.normalUnderlineLayer removeAnimationForKey:self.class.normalUnderlineShrinkKey]; } - BOOL needsThickUnderlineGrowAnimation = NO; - if (preexistingThinUnderlineGrowAnimation) { - CGPathRef toValue = (__bridge CGPathRef)preexistingThinUnderlineGrowAnimation.toValue; - if (!CGPathEqualToPath(toValue, targetThinUnderlineBezier.CGPath)) { - [self.thinUnderlineLayer removeAnimationForKey:self.class.thinUnderlineGrowKey]; - needsThickUnderlineGrowAnimation = YES; - self.thinUnderlineLayer.path = targetThinUnderlineBezier.CGPath; + BOOL needsEditingUnderlineGrowAnimation = NO; + if (preexistingNormalUnderlineGrowAnimation) { + CGPathRef toValue = (__bridge CGPathRef)preexistingNormalUnderlineGrowAnimation.toValue; + if (!CGPathEqualToPath(toValue, targetNormalUnderlineBezier.CGPath)) { + [self.normalUnderlineLayer removeAnimationForKey:self.class.normalUnderlineGrowKey]; + needsEditingUnderlineGrowAnimation = YES; + self.normalUnderlineLayer.path = targetNormalUnderlineBezier.CGPath; } } else { - needsThickUnderlineGrowAnimation = YES; + needsEditingUnderlineGrowAnimation = YES; } - if (needsThickUnderlineGrowAnimation) { - [self.thinUnderlineLayer addAnimation:[self pathAnimationTo:targetThinUnderlineBezier] - forKey:self.class.thinUnderlineGrowKey]; + if (needsEditingUnderlineGrowAnimation) { + [self.normalUnderlineLayer addAnimation:[self pathAnimationTo:targetNormalUnderlineBezier] + forKey:self.class.normalUnderlineGrowKey]; } } } [CATransaction commit]; } -- (BOOL)shouldShowThickUnderlineWithState:(MDCTextControlState)state { - BOOL shouldShow = NO; - switch (state) { - case MDCTextControlStateEditing: - shouldShow = YES; - break; - case MDCTextControlStateNormal: - case MDCTextControlStateDisabled: - default: - break; - } - return shouldShow; -} - #pragma mark Animation - (CABasicAnimation *)pathAnimationTo:(UIBezierPath *)path { @@ -332,44 +356,87 @@ - (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag { CABasicAnimation *animation = (CABasicAnimation *)anim; CGPathRef toValue = (__bridge CGPathRef)animation.toValue; - CABasicAnimation *thickGrowAnimation = (CABasicAnimation *)[self.thickUnderlineLayer - animationForKey:self.class.thickUnderlineGrowKey]; - CABasicAnimation *thickShrinkAnimation = (CABasicAnimation *)[self.thickUnderlineLayer - animationForKey:self.class.thickUnderlineShrinkKey]; - CABasicAnimation *thinGrowAnimation = - (CABasicAnimation *)[self.thinUnderlineLayer animationForKey:self.class.thinUnderlineGrowKey]; - CABasicAnimation *thinShrinkAnimation = (CABasicAnimation *)[self.thinUnderlineLayer - animationForKey:self.class.thinUnderlineShrinkKey]; + CABasicAnimation *thickGrowAnimation = (CABasicAnimation *)[self.editingUnderlineLayer + animationForKey:self.class.editingUnderlineGrowKey]; + CABasicAnimation *thickShrinkAnimation = (CABasicAnimation *)[self.editingUnderlineLayer + animationForKey:self.class.editingUnderlineShrinkKey]; + CABasicAnimation *thinGrowAnimation = (CABasicAnimation *)[self.normalUnderlineLayer + animationForKey:self.class.normalUnderlineGrowKey]; + CABasicAnimation *thinShrinkAnimation = (CABasicAnimation *)[self.normalUnderlineLayer + animationForKey:self.class.normalUnderlineShrinkKey]; + + CABasicAnimation *editingUnderlineAnimation = (CABasicAnimation *)[self.editingUnderlineLayer + animationForKey:self.class.editingUnderlineThicknessKey]; + CABasicAnimation *normalUnderlineAnimation = (CABasicAnimation *)[self.normalUnderlineLayer + animationForKey:self.class.normalUnderlineThicknessKey]; if (flag) { if ((animation == thickGrowAnimation) || (animation == thickShrinkAnimation)) { - self.thickUnderlineLayer.path = toValue; + self.editingUnderlineLayer.path = toValue; } if ((animation == thinGrowAnimation) || (animation == thinShrinkAnimation)) { - self.thinUnderlineLayer.path = toValue; + self.normalUnderlineLayer.path = toValue; + } + if (animation == editingUnderlineAnimation) { + self.editingUnderlineLayer.path = toValue; + } + if (animation == normalUnderlineAnimation) { + self.normalUnderlineLayer.path = toValue; } } } -+ (NSString *)thinUnderlineShrinkKey { - return @"thinUnderlineShrinkKey"; ++ (NSString *)normalUnderlineShrinkKey { + return @"normalUnderlineShrinkKey"; } -+ (NSString *)thinUnderlineGrowKey { - return @"thinUnderlineGrowKey"; + ++ (NSString *)normalUnderlineGrowKey { + return @"normalUnderlineGrowKey"; } -+ (NSString *)thickUnderlineShrinkKey { - return @"thickUnderlineShrinkKey"; + ++ (NSString *)editingUnderlineShrinkKey { + return @"editingUnderlineShrinkKey"; } -+ (NSString *)thickUnderlineGrowKey { - return @"thickUnderlineGrowKey"; + ++ (NSString *)editingUnderlineGrowKey { + return @"editingUnderlineGrowKey"; +} + ++ (NSString *)normalUnderlineThicknessKey { + return @"normalUnderlineThicknessKey"; +} + ++ (NSString *)editingUnderlineThicknessKey { + return @"editingUnderlineThicknessKey"; } #pragma mark Path Drawing -- (UIBezierPath *)filledSublayerUnderlinePathWithViewBounds:(CGRect)viewBounds - containerHeight:(CGFloat)containerHeight - underlineThickness:(CGFloat)underlineThickness - underlineWidth:(CGFloat)underlineWidth { +- (UIBezierPath *)targetEditingUnderlineBezier { + CGFloat editingUnderlineWidth = self.isEditing ? CGRectGetWidth(self.mostRecentBounds) : 0; + CGFloat editingUnderlineThickness = self.isEditing ? self.editingUnderlineThickness : 0; + UIBezierPath *targetEditingUnderlineBezier = + [self underlinePathWithViewBounds:self.mostRecentBounds + containerHeight:self.containerHeight + underlineThickness:editingUnderlineThickness + underlineWidth:editingUnderlineWidth]; + return targetEditingUnderlineBezier; +} + +- (UIBezierPath *)targetNormalUnderlineBezier { + CGFloat normalUnderlineThickness = self.isEditing ? 0 : self.normalUnderlineThickness; + UIBezierPath *targetNormalUnderlineBezier = + [self underlinePathWithViewBounds:self.mostRecentBounds + containerHeight:self.containerHeight + underlineThickness:normalUnderlineThickness + underlineWidth:CGRectGetWidth(self.mostRecentBounds)]; + return targetNormalUnderlineBezier; +} + +- (UIBezierPath *)underlinePathWithViewBounds:(CGRect)viewBounds + containerHeight:(CGFloat)containerHeight + underlineThickness:(CGFloat)underlineThickness + underlineWidth:(CGFloat)underlineWidth { UIBezierPath *path = [[UIBezierPath alloc] init]; CGFloat viewWidth = CGRectGetWidth(viewBounds); CGFloat halfViewWidth = 0.5f * viewWidth; From 2a5996b407f3ba821672bf35cee5cace2bebdbd8 Mon Sep 17 00:00:00 2001 From: Cody Weaver Date: Fri, 24 Jul 2020 09:32:34 -0700 Subject: [PATCH 29/43] [MDC/Button] Add rippleMaximumRadius API. This commit adds the ripple maximum radius API to match the inkMaxRippleRadius API that are soon to be deprecated. This also moves those APIs to a "ToBeDeprecated" category. In this commit I set an ivar instead of having pass through properties because in a follow up I think it would be a good optimization to have the ripple be lazy until a user interacts with the button. This will lower initialization time and make the setters potentially faster. Since we don't have pass through properties I defaulted it to 0 to match the legacy API from `MDCStatefulRippleView` to match here. PiperOrigin-RevId: 323011293 --- components/Buttons/src/MDCButton.h | 17 +++++++++++++---- components/Buttons/src/MDCButton.m | 11 +++++++++++ .../Buttons/tests/unit/MDCButtonRippleTests.m | 14 ++++++++++++++ 3 files changed, 38 insertions(+), 4 deletions(-) diff --git a/components/Buttons/src/MDCButton.h b/components/Buttons/src/MDCButton.h index 7976f2a8c2e..e4c143a0bcf 100644 --- a/components/Buttons/src/MDCButton.h +++ b/components/Buttons/src/MDCButton.h @@ -47,11 +47,14 @@ */ @property(nonatomic, strong, null_resettable) UIColor *rippleColor; -/* - Maximum radius of the button's ink. If the radius <= 0 then half the length of the diagonal of - self.bounds is used. This value is ignored if button's @c inkStyle is set to |MDCInkStyleBounded|. +/** + The maximum radius the ripple can expand to. + + @note This property is ignored if @c rippleStyle is set to @c MDCRippleStyleBounded. + + @note Defaults to 0. */ -@property(nonatomic, assign) CGFloat inkMaxRippleRadius UI_APPEARANCE_SELECTOR; +@property(nonatomic, assign) CGFloat rippleMaximumRadius; /** This property determines if an @c MDCButton should use the @c MDCInkView behavior or not. @@ -412,4 +415,10 @@ /** The ink color of the button. */ @property(nonatomic, strong, null_resettable) UIColor *inkColor UI_APPEARANCE_SELECTOR; +/* + Maximum radius of the button's ink. If the radius <= 0 then half the length of the diagonal of + self.bounds is used. This value is ignored if button's @c inkStyle is set to |MDCInkStyleBounded|. + */ +@property(nonatomic, assign) CGFloat inkMaxRippleRadius UI_APPEARANCE_SELECTOR; + @end diff --git a/components/Buttons/src/MDCButton.m b/components/Buttons/src/MDCButton.m index 247247088b7..65d8f745fa5 100644 --- a/components/Buttons/src/MDCButton.m +++ b/components/Buttons/src/MDCButton.m @@ -112,6 +112,7 @@ @interface MDCButton () { BOOL _mdc_adjustsFontForContentSizeCategory; BOOL _cornerRadiusObserverAdded; + CGFloat _inkMaxRippleRadius; } @property(nonatomic, strong, readonly, nonnull) MDCStatefulRippleView *rippleView; @property(nonatomic, strong) MDCInkView *inkView; @@ -644,12 +645,22 @@ - (void)setRippleColor:(UIColor *)rippleColor { [self.rippleView setRippleColor:_rippleColor forState:MDCRippleStateHighlighted]; } +- (CGFloat)inkMaxRippleRadius { + return _inkMaxRippleRadius; +} + - (void)setInkMaxRippleRadius:(CGFloat)inkMaxRippleRadius { _inkMaxRippleRadius = inkMaxRippleRadius; _inkView.maxRippleRadius = inkMaxRippleRadius; self.rippleView.maximumRadius = inkMaxRippleRadius; } +- (void)setRippleMaximumRadius:(CGFloat)rippleMaximumRadius { + _rippleMaximumRadius = rippleMaximumRadius; + + self.rippleView.maximumRadius = rippleMaximumRadius; +} + - (void)setInkViewOffset:(CGSize)inkViewOffset { _inkViewOffset = inkViewOffset; [self setNeedsLayout]; diff --git a/components/Buttons/tests/unit/MDCButtonRippleTests.m b/components/Buttons/tests/unit/MDCButtonRippleTests.m index 2f21d0252b3..d6b7c642e34 100644 --- a/components/Buttons/tests/unit/MDCButtonRippleTests.m +++ b/components/Buttons/tests/unit/MDCButtonRippleTests.m @@ -201,4 +201,18 @@ - (void)testSettingContentEdgeInsetsUpdatesRipple { NSStringFromCGRect(self.button.rippleView.frame)); } +- (void)testDefaultRippleMaximumRadius { + // Then + XCTAssertEqualWithAccuracy(self.button.rippleMaximumRadius, 0, 0.001); +} + +- (void)testSettingRippleMaximumRadiusUpdatesTheRippleView { + // When + self.button.rippleMaximumRadius = 5; + + // Then + XCTAssertEqualWithAccuracy(self.button.rippleMaximumRadius, 5, 0.001); + XCTAssertEqualWithAccuracy(self.button.rippleView.maximumRadius, 5, 0.001); +} + @end From d6498592978d0ef3e22f11a7c49fc9f1aad25547 Mon Sep 17 00:00:00 2001 From: Alyssa Weiss Date: Fri, 24 Jul 2020 16:04:31 -0700 Subject: [PATCH 30/43] [Snackbar] Use different margins for multi-line snackbars PiperOrigin-RevId: 323090476 --- .../Snackbar/src/MDCSnackbarMessageView.m | 78 +++++++++++++------ 1 file changed, 55 insertions(+), 23 deletions(-) diff --git a/components/Snackbar/src/MDCSnackbarMessageView.m b/components/Snackbar/src/MDCSnackbarMessageView.m index 1d6d932a362..5184856a449 100644 --- a/components/Snackbar/src/MDCSnackbarMessageView.m +++ b/components/Snackbar/src/MDCSnackbarMessageView.m @@ -66,8 +66,9 @@ static BOOL UIViewHasFocusedAccessibilityElement(UIView *view) { /** Padding between the edges of the Snackbar and any content. */ -static UIEdgeInsets kContentMargin = (UIEdgeInsets){16.0, 16.0, 16.0, 8.0}; -static UIEdgeInsets kLegacyContentMargin = (UIEdgeInsets){18.0, 24.0, 18.0, 24.0}; +static const UIEdgeInsets kContentMarginSingleLineText = (UIEdgeInsets){6.0, 16.0, 6.0, 8.0}; +static const UIEdgeInsets kContentMarginMutliLineText = (UIEdgeInsets){16.0, 16.0, 16.0, 8.0}; +static const UIEdgeInsets kLegacyContentMargin = (UIEdgeInsets){18.0, 24.0, 18.0, 24.0}; /** Padding between the image and the main title. @@ -98,6 +99,11 @@ static BOOL UIViewHasFocusedAccessibilityElement(UIView *view) { */ static const CGFloat kMinimumHeight = 48; +/** + The minimum height of a multiline Snackbar. + */ +static const CGFloat kMinimumHeightMultiline = 68; + /** Each button will have a tag indexed starting from this value. */ @@ -207,6 +213,8 @@ @implementation MDCSnackbarMessageView { BOOL _mdc_adjustsFontForContentSizeCategory; BOOL _shouldDismissOnOverlayTap; + + BOOL _isMultilineText; } @synthesize mdc_overrideBaseElevation = _mdc_overrideBaseElevation; @@ -674,21 +682,30 @@ - (BOOL)shouldWaitForDismissalDuringVoiceover { #pragma mark - Constraints and layout +- (NSInteger)numberOfLines { + CGSize maxLabelSize = self.label.intrinsicContentSize; + CGFloat lineHeight = self.label.font.lineHeight; + return (NSInteger)MDCRound(maxLabelSize.height / lineHeight); +} + +- (void)resetConstraints { + [self removeConstraints:self.viewConstraints]; + self.viewConstraints = nil; + [self setNeedsUpdateConstraints]; +} + - (void)setAnchoredToScreenBottom:(BOOL)anchoredToScreenBottom { _anchoredToScreenBottom = anchoredToScreenBottom; [self invalidateIntrinsicContentSize]; if (self.viewConstraints) { - [self removeConstraints:self.viewConstraints]; - self.viewConstraints = nil; - [self updateConstraints]; + [self resetConstraints]; } } - (void)updateConstraints { - [super updateConstraints]; - if (self.viewConstraints) { + [super updateConstraints]; return; } @@ -700,6 +717,8 @@ - (void)updateConstraints { [self addConstraints:constraints]; self.viewConstraints = constraints; + + [super updateConstraints]; } /** @@ -707,13 +726,14 @@ - (void)updateConstraints { @c kBorderWidth. Also positions the content view and button view inside of the container view. */ - (NSArray *)containerViewConstraints { + UIEdgeInsets safeContentMargin = self.safeContentMargin; NSDictionary *metrics = @{ @"kBorderMargin" : @(kBorderWidth), - @"kBottomMargin" : @(self.safeContentMargin.bottom), - @"kLeftMargin" : @(self.safeContentMargin.left), - @"kRightMargin" : @(self.safeContentMargin.right), + @"kBottomMargin" : @(safeContentMargin.bottom), + @"kLeftMargin" : @(safeContentMargin.left), + @"kRightMargin" : @(safeContentMargin.right), @"kTitleImagePadding" : @(kTitleImagePadding), - @"kTopMargin" : @(self.safeContentMargin.top), + @"kTopMargin" : @(safeContentMargin.top), @"kTitleButtonPadding" : @(kTitleButtonPadding), @"kContentSafeBottomInset" : @(kBorderWidth + self.contentSafeBottomInset), }; @@ -835,12 +855,13 @@ - (NSArray *)containerViewConstraints { Provides constraints for the image view and label within the content view. */ - (NSArray *)contentViewConstraints { + UIEdgeInsets safeContentMargin = self.safeContentMargin; NSDictionary *metrics = @{ - @"kBottomMargin" : @(self.safeContentMargin.bottom), - @"kLeftMargin" : @(self.safeContentMargin.left), - @"kRightMargin" : @(self.safeContentMargin.right), + @"kBottomMargin" : @(safeContentMargin.bottom), + @"kLeftMargin" : @(safeContentMargin.left), + @"kRightMargin" : @(safeContentMargin.right), @"kTitleImagePadding" : @(kTitleImagePadding), - @"kTopMargin" : @(self.safeContentMargin.top), + @"kTopMargin" : @(safeContentMargin.top), }; NSMutableDictionary *views = [NSMutableDictionary dictionary]; @@ -906,12 +927,12 @@ - (NSArray *)contentViewConstraints { */ - (NSArray *)horizontalButtonLayoutConstraints { NSMutableArray *constraints = [NSMutableArray array]; - + UIEdgeInsets safeContentMargin = self.safeContentMargin; NSDictionary *metrics = @{ - @"kLeftMargin" : @(self.safeContentMargin.left), - @"kRightMargin" : @(self.safeContentMargin.right), - @"kTopMargin" : @(self.safeContentMargin.top), - @"kBottomMargin" : @(self.safeContentMargin.bottom), + @"kLeftMargin" : @(safeContentMargin.left), + @"kRightMargin" : @(safeContentMargin.right), + @"kTopMargin" : @(safeContentMargin.top), + @"kBottomMargin" : @(safeContentMargin.bottom), @"kTitleImagePadding" : @(kTitleImagePadding), @"kBorderMargin" : @(kBorderWidth), @"kTitleButtonPadding" : @(kTitleButtonPadding), @@ -989,6 +1010,12 @@ - (NSArray *)horizontalButtonLayoutConstraints { - (void)layoutSubviews { [super layoutSubviews]; + BOOL isMultilineText = [self numberOfLines] > 1; + if (_isMultilineText != isMultilineText) { + _isMultilineText = isMultilineText; + [self resetConstraints]; + } + // As our layout changes, make sure that the shadow path is kept up-to-date. UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:self.bounds @@ -1012,7 +1039,8 @@ - (CGSize)intrinsicContentSize { height += self.safeContentMargin.top + self.safeContentMargin.bottom; // Make sure that the height of the image and text is larger than the minimum height; - height = MAX(kMinimumHeight, height) + self.contentSafeBottomInset; + height = MAX(_isMultilineText ? kMinimumHeightMultiline : kMinimumHeight, height) + + self.contentSafeBottomInset; return CGSizeMake(UIViewNoIntrinsicMetric, height); } @@ -1031,8 +1059,12 @@ - (CGFloat)contentSafeBottomInset { } - (UIEdgeInsets)safeContentMargin { - UIEdgeInsets contentMargin = - MDCSnackbarMessage.usesLegacySnackbar ? kLegacyContentMargin : kContentMargin; + UIEdgeInsets contentMargin = UIEdgeInsetsZero; + if (MDCSnackbarMessage.usesLegacySnackbar) { + contentMargin = kLegacyContentMargin; + } else { + contentMargin = _isMultilineText ? kContentMarginMutliLineText : kContentMarginSingleLineText; + } UIEdgeInsets safeAreaInsets = UIEdgeInsetsZero; if (@available(iOS 11.0, *)) { From 3481620f18b9c3e62d1f9a5858f362843269099b Mon Sep 17 00:00:00 2001 From: Alyssa Weiss Date: Sat, 25 Jul 2020 09:24:21 -0700 Subject: [PATCH 31/43] When large content view is enabled, on long presses it will correctly switch to the last selected item PiperOrigin-RevId: 323167120 --- components/BottomNavigation/src/MDCBottomNavigationBar.m | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/components/BottomNavigation/src/MDCBottomNavigationBar.m b/components/BottomNavigation/src/MDCBottomNavigationBar.m index bfa65f1c978..d4e26a41dd7 100644 --- a/components/BottomNavigation/src/MDCBottomNavigationBar.m +++ b/components/BottomNavigation/src/MDCBottomNavigationBar.m @@ -916,6 +916,15 @@ - (CGFloat)mdc_currentElevation { - (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++) { + MDCBottomNavigationItemView *itemView = self.itemViews[i]; + if (item == itemView) { + [self didTouchUpInsideButton:itemView.button]; + } + } + } + self.lastLargeContentViewerItem = nil; } #endif // MDC_AVAILABLE_SDK_IOS(13_0) From 8a509d558ffdf3225b56823140c632fb2aa39ca1 Mon Sep 17 00:00:00 2001 From: Cody Weaver Date: Mon, 27 Jul 2020 08:25:29 -0700 Subject: [PATCH 32/43] [MDC/Chips] Add rippleForState APIs. This change adds rippleForState APIs for MDCChipView to allow easier migration for the ink component. PiperOrigin-RevId: 323364613 --- components/Chips/src/MDCChipView.h | 21 ++++++++ components/Chips/src/MDCChipView.m | 19 +++++++ .../Chips/tests/unit/ChipViewRippleTests.m | 50 +++++++++++++++++++ 3 files changed, 90 insertions(+) diff --git a/components/Chips/src/MDCChipView.h b/components/Chips/src/MDCChipView.h index bc2daa7d902..538e46527fc 100644 --- a/components/Chips/src/MDCChipView.h +++ b/components/Chips/src/MDCChipView.h @@ -284,6 +284,27 @@ - (void)setInkColor:(nullable UIColor *)inkColor forState:(UIControlState)state UI_APPEARANCE_SELECTOR; +/* + Returns the ripple color associated with the specified state. + + The ripple color for the specified state. If no ripple color has been set for the specific state, + this method returns the title associated with the @c UIControlStateNormal state. + + @note Defaults to @c nil. When @c nil transparent black is used. + + @param state The state that uses the ripple color. + @return The ripple color for the requested state. + */ +- (nullable UIColor *)rippleColorForState:(UIControlState)state; + +/* + Sets the ripple color for a particular control state. + + @param rippleColor The ripple color to use for the specified state. + @param state The state that uses the specified ripple color. + */ +- (void)setRippleColor:(nullable UIColor *)rippleColor forState:(UIControlState)state; + /* Returns the shadow color for a particular control state. diff --git a/components/Chips/src/MDCChipView.m b/components/Chips/src/MDCChipView.m index aff152f1cc9..eb90b2c38e0 100644 --- a/components/Chips/src/MDCChipView.m +++ b/components/Chips/src/MDCChipView.m @@ -114,6 +114,7 @@ @interface MDCChipView () @property(nonatomic, assign) BOOL shouldFullyRoundCorner; @property(nonatomic, strong) MDCInkView *inkView; @property(nonatomic, strong) MDCStatefulRippleView *rippleView; +@property(nonatomic, strong, nonnull) NSMutableDictionary *rippleColors; @property(nonatomic, readonly) CGFloat pixelScale; @property(nonatomic, assign) BOOL enableRippleBehavior; @property(nonatomic, assign) BOOL adjustsFontForContentSizeCategoryWhenScaledFontIsUnavailable; @@ -196,6 +197,7 @@ - (instancetype)initWithFrame:(CGRect)frame { [self addSubview:_inkView]; _rippleView = [[MDCStatefulRippleView alloc] initWithFrame:self.bounds]; + _rippleColors = [NSMutableDictionary dictionary]; _imageView = [[UIImageView alloc] init]; [self addSubview:_imageView]; @@ -522,6 +524,23 @@ - (void)updateInkColor { self.inkView.inkColor = inkColor ?: self.inkView.defaultInkColor; } +- (UIColor *)rippleColorForState:(UIControlState)state { + UIColor *rippleColor = self.rippleColors[@(state)]; + if (!rippleColor && state != UIControlStateNormal) { + rippleColor = self.rippleColors[@(UIControlStateNormal)]; + } + return rippleColor; +} + +- (void)setRippleColor:(UIColor *)rippleColor forState:(UIControlState)state { + self.rippleColors[@(state)] = rippleColor; + NSNumber *rippleState = [self rippleStateForControlState:state]; + if (rippleState) { + [self.rippleView setRippleColor:rippleColor forState:rippleState.integerValue]; + } + [self updateRippleColor]; +} + - (void)updateRippleColor { UIColor *rippleColor = [self inkColorForState:self.state]; // MDCStatefulRippleView sets the ripple color internally when its state changes. diff --git a/components/Chips/tests/unit/ChipViewRippleTests.m b/components/Chips/tests/unit/ChipViewRippleTests.m index cc44c2c4117..b75a2dd3b6d 100644 --- a/components/Chips/tests/unit/ChipViewRippleTests.m +++ b/components/Chips/tests/unit/ChipViewRippleTests.m @@ -18,6 +18,29 @@ #import "MaterialInk.h" #import "MaterialRipple.h" +static UIColor *RandomColor() { + switch (arc4random_uniform(5)) { + case 0: + return [UIColor colorWithRed:1 green:1 blue:1 alpha:1]; + break; + case 1: + return [UIColor colorWithRed:0 green:0 blue:0 alpha:1]; + break; + case 2: + return [UIColor redColor]; + break; + case 3: + return [UIColor orangeColor]; + break; + case 4: + return [UIColor greenColor]; + break; + default: + return [UIColor blueColor]; + break; + } +} + @interface MDCChipView (Testing) @property(nonatomic, strong) MDCStatefulRippleView *rippleView; @property(nonatomic, strong) MDCInkView *inkView; @@ -175,4 +198,31 @@ - (void)testChipViewNotHighlightedSetsRippleHighlightedToNO { XCTAssertFalse(self.chipView.rippleView.isRippleHighlighted); } +- (void)testSetRippleColorForStateReturnsTheCorrectValue { + UIControlState maxState = UIControlStateNormal | UIControlStateHighlighted | + UIControlStateDisabled | UIControlStateSelected; + for (NSUInteger state = 0; state <= maxState; ++state) { + // Given + UIColor *color = RandomColor(); + + // When + [self.chipView setRippleColor:color forState:state]; + + // Then + XCTAssertEqualObjects([self.chipView rippleColorForState:state], color); + } +} + +- (void)testSetRippleColorToNilReturnsTheCorrectValueForNormal { + // Given + UIColor *color = UIColor.orangeColor; + [self.chipView setRippleColor:color forState:UIControlStateNormal]; + + // When + [self.chipView setRippleColor:nil forState:UIControlStateSelected]; + + // Then + XCTAssertEqualObjects([self.chipView rippleColorForState:UIControlStateSelected], color); +} + @end From 07c46757cc73d995eddc6205455d8c480f3270b5 Mon Sep 17 00:00:00 2001 From: Andrew Overton Date: Mon, 27 Jul 2020 13:06:25 -0700 Subject: [PATCH 33/43] =?UTF-8?q?Replace=20material.io=20API=20doc=20links?= =?UTF-8?q?=20with=20links=20to=20the=20relevant=20header=20f=E2=80=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This PR replaces API links containing "/api-docs/" with links to suitable header files in GitHub because the site previously linked to is being taken down and replaced with something that won't handle API docs. Closes https://github.com/material-components/material-components-ios/pull/10045 COPYBARA_INTEGRATE_REVIEW=https://github.com/material-components/material-components-ios/pull/10045 from andrewoverton:replace-mio-api-docs-with-github-links 0d6e56cc11d0c147366a4f6cde829d8b51567ecc PiperOrigin-RevId: 323424362 --- components/ActionSheet/README.md | 6 +- components/ActivityIndicator/README.md | 9 +- components/BottomAppBar/README.md | 2 +- components/BottomNavigation/README.md | 1 - components/Buttons/docs/buttons.md | 3 +- components/Buttons/docs/fabs.md | 3 +- components/Cards/README.md | 2 - components/Chips/README.md | 13 +- components/CollectionCells/README.md | 4 +- .../CollectionLayoutAttributes/README.md | 2 +- components/Collections/README.md | 10 +- components/FeatureHighlight/README.md | 4 +- components/FlexibleHeader/README.md | 21 +- components/HeaderStackView/README.md | 2 +- components/Ink/README.md | 13 +- components/NavigationBar/README.md | 11 +- components/NavigationDrawer/README.md | 15 +- components/PageControl/README.md | 2 +- components/Palettes/README.md | 2 +- components/ProgressView/README.md | 5 +- components/Ripple/README.md | 15 +- components/ShadowElevations/README.md | 2 +- components/ShadowLayer/README.md | 4 +- components/Slider/README.md | 7 +- components/Snackbar/README.md | 15 +- components/Tabs/README.md | 25 +- components/TextFields/README.md | 49 ++-- components/Typography/README.md | 4 +- components/Typography/docs/README.md | 4 +- contributing/site_content_update.md | 91 ------- contributing/site_development.md | 243 ------------------ 31 files changed, 119 insertions(+), 470 deletions(-) delete mode 100644 contributing/site_content_update.md delete mode 100644 contributing/site_development.md diff --git a/components/ActionSheet/README.md b/components/ActionSheet/README.md index 64f86dc0358..055331412d8 100644 --- a/components/ActionSheet/README.md +++ b/components/ActionSheet/README.md @@ -22,9 +22,9 @@ the screen and displays actions a user can take. ## Design & API documentation ## Table of contents diff --git a/components/ActivityIndicator/README.md b/components/ActivityIndicator/README.md index 8ecbefa863b..bfe673707a4 100644 --- a/components/ActivityIndicator/README.md +++ b/components/ActivityIndicator/README.md @@ -26,11 +26,10 @@ This component only provides the circular implementation. See ## Related components diff --git a/components/BottomAppBar/README.md b/components/BottomAppBar/README.md index b53300f4696..14e2130a6a3 100644 --- a/components/BottomAppBar/README.md +++ b/components/BottomAppBar/README.md @@ -28,7 +28,7 @@ api_doc_root: true Bottom app bars group primary and secondary actions at the bottom of the screen, where they are easily reachable by the user's thumb. -Use the `UIView` subclass `MDCBottomAppBarView` to add a bottom app bar to your app. `MDCBottomAppBarView` contains a horizontally centered [floating action button](https://material.io/components/ios/catalog/buttons/api-docs/Classes/MDCFloatingButton.html) for primary actions and a customizable [navigation bar](https://material.io/components/ios/catalog/flexible-headers/navigation-bars/) for secondary actions. The `MDCBottomAppBarView` API includes properties that allow changes in elevation, position, and visibility of the embedded floating action button. +Use the `UIView` subclass `MDCBottomAppBarView` to add a bottom app bar to your app. `MDCBottomAppBarView` contains a horizontally centered [floating action button](https://github.com/material-components/material-components-ios/blob/stable/components/Buttons/src/MDCFloatingButton.h) for primary actions and a customizable [navigation bar](https://material.io/components/ios/catalog/flexible-headers/navigation-bars/) for secondary actions. The `MDCBottomAppBarView` API includes properties that allow changes in elevation, position, and visibility of the embedded floating action button. Instances of `UIBarButtonItem` can be added to a `MDCBottomAppBarView`'s navigation bar. Leading and trailing navigation items will be shown and hidden based on the position of the floating action button. diff --git a/components/BottomNavigation/README.md b/components/BottomNavigation/README.md index 8118698d49b..1b6a072170e 100644 --- a/components/BottomNavigation/README.md +++ b/components/BottomNavigation/README.md @@ -206,7 +206,6 @@ bottomNavBar.frame = bottomNavBarFrame API and source code: * `MDCBottomNavigationBar` - * [Class reference](https://material.io/components/ios/catalog/bottomnavigation/api-docs/Classes/MDCBottomNavigationBar.html) * [Class source](https://github.com/material-components/material-components-ios/blob/develop/components/BottomNavigation/src/MDCBottomNavigationBar.h) To achieve something like the example image above, add the following code to your view controller: diff --git a/components/Buttons/docs/buttons.md b/components/Buttons/docs/buttons.md index d3a143c6042..b6717063d96 100644 --- a/components/Buttons/docs/buttons.md +++ b/components/Buttons/docs/buttons.md @@ -25,7 +25,6 @@ There are four types of buttons: ## Using buttons All Material buttons are implemented by `MDCButton`, a subclass of [UIButton](https://developer.apple.com/documentation/uikit/uibutton). -* [API documentation](https://material.io/develop/ios/components/buttons/api-docs/Classes/MDCButton.html) * [GitHub source](https://github.com/material-components/material-components-ios/blob/develop/components/Buttons/src/MDCButton.h) ### Install `MDCButton` @@ -295,7 +294,7 @@ An outlined button has a text label, a container, and an optional icon. ### Contained button example -Contained buttons are implemented by [MDCButton](https://material.io/develop/ios/components/buttons/api-docs/Classes/MDCButton.html). To achieve a contained button use the contained button theming method on the MDCButton theming extension. To access the theming extension see the [Theming section](#theming). +Contained buttons are implemented by [MDCButton](https://github.com/material-components/material-components-ios/blob/stable/components/Buttons/src/MDCButton.h). To achieve a contained button use the contained button theming method on the MDCButton theming extension. To access the theming extension see the [Theming section](#theming). #### Objective-C diff --git a/components/Buttons/docs/fabs.md b/components/Buttons/docs/fabs.md index 39337664068..54246a3345c 100644 --- a/components/Buttons/docs/fabs.md +++ b/components/Buttons/docs/fabs.md @@ -24,8 +24,7 @@ There are three types of FABs: A FAB performs the primary, or most common, action on a screen. It appears in front of all screen content, typically as a circular shape with an icon in its center. -FABs are implemented by `MDCFloatingButton`, a subclass of [MDCButton](https://material.io/develop/ios/components/buttons/api-docs/Classes/MDCButton.html). -* [API documentation](https://material.io/develop/ios/components/buttons/api-docs/Classes/MDCFloatingButton.html) +FABs are implemented by `MDCFloatingButton`, a subclass of [MDCButton](https://github.com/material-components/material-components-ios/blob/stable/components/Buttons/src/MDCButton.h). * [GitHub source](https://github.com/material-components/material-components-ios/blob/develop/components/Buttons/src/MDCFloatingButton.h) Only use a FAB if it is the most suitable way to present a screen’s primary action. diff --git a/components/Cards/README.md b/components/Cards/README.md index 6832b433291..1895367a027 100644 --- a/components/Cards/README.md +++ b/components/Cards/README.md @@ -193,11 +193,9 @@ Cards can be used to build custom UIs, like the one shown above, from [CardWithI MDCCard and MDCCardCollectionCell inherit from UIControl and UICollectionViewCell, respectively. * MDCCard - * [API documentation](https://material.io/develop/ios/components/cards/api-docs/Classes/MDCCard.html) * [GitHub source](https://github.com/material-components/material-components-ios/blob/develop/components/Cards/src/MDCCard.h) * MDCCardCollectionCell - * [API documentation](https://material.io/develop/ios/components/cards/api-docs/Classes/MDCCardCollectionCell.html) * [GitHub source](https://github.com/material-components/material-components-ios/blob/develop/components/Cards/src/MDCCardCollectionCell.h) ### Card examples using MDCCard diff --git a/components/Chips/README.md b/components/Chips/README.md index 2ef0594ff68..0ba1be59a8e 100644 --- a/components/Chips/README.md +++ b/components/Chips/README.md @@ -20,13 +20,12 @@ Chips are compact elements that represent an input, attribute, or action. ## Table of contents diff --git a/components/CollectionCells/README.md b/components/CollectionCells/README.md index af5aafbb314..a344abf51f0 100644 --- a/components/CollectionCells/README.md +++ b/components/CollectionCells/README.md @@ -17,8 +17,8 @@ Collection view cell classes that adhere to Material Design layout and styling. - - - diff --git a/components/CollectionLayoutAttributes/README.md b/components/CollectionLayoutAttributes/README.md index 916fae6c72a..f89fa18f972 100644 --- a/components/CollectionLayoutAttributes/README.md +++ b/components/CollectionLayoutAttributes/README.md @@ -12,7 +12,7 @@ Allows passing layout attributes to the cells and supplementary views. ## Design & API Documentation - - - diff --git a/components/Collections/README.md b/components/Collections/README.md index 6c23d0ef869..15cf0e9f7f7 100644 --- a/components/Collections/README.md +++ b/components/Collections/README.md @@ -17,11 +17,11 @@ Collection view classes that adhere to Material Design layout and styling. - - - diff --git a/components/FeatureHighlight/README.md b/components/FeatureHighlight/README.md index cd315160e1a..98ef04768bf 100644 --- a/components/FeatureHighlight/README.md +++ b/components/FeatureHighlight/README.md @@ -23,8 +23,8 @@ The Feature Highlight component is a way to visually highlight a part of the scr ## Design & API documentation ## Table of contents diff --git a/components/FlexibleHeader/README.md b/components/FlexibleHeader/README.md index ea574b748d9..df2fe42fc2a 100644 --- a/components/FlexibleHeader/README.md +++ b/components/FlexibleHeader/README.md @@ -25,17 +25,16 @@ UIScrollViewDelegate events. ## Related components diff --git a/components/HeaderStackView/README.md b/components/HeaderStackView/README.md index d9d33bdf471..d8a4ca3c2be 100644 --- a/components/HeaderStackView/README.md +++ b/components/HeaderStackView/README.md @@ -21,7 +21,7 @@ bar views. - - - diff --git a/components/Ink/README.md b/components/Ink/README.md index f5c2ec33136..086ea08e155 100644 --- a/components/Ink/README.md +++ b/components/Ink/README.md @@ -22,13 +22,12 @@ the user's touch. ## Design & API documentation ## Table of contents diff --git a/components/NavigationBar/README.md b/components/NavigationBar/README.md index 5e656d852e3..39a63d4c369 100644 --- a/components/NavigationBar/README.md +++ b/components/NavigationBar/README.md @@ -25,12 +25,11 @@ custom title view. ## Related components diff --git a/components/NavigationDrawer/README.md b/components/NavigationDrawer/README.md index 209480e858e..d18d1015a5e 100644 --- a/components/NavigationDrawer/README.md +++ b/components/NavigationDrawer/README.md @@ -25,14 +25,13 @@ Navigation drawers are recommended for: ## Table of contents diff --git a/components/PageControl/README.md b/components/PageControl/README.md index 63716dad5d7..97f5248bd03 100644 --- a/components/PageControl/README.md +++ b/components/PageControl/README.md @@ -26,7 +26,7 @@ desired animation of the control. ## Design & API documentation ## Table of contents diff --git a/components/Palettes/README.md b/components/Palettes/README.md index d13512ed44c..8aa8c281171 100644 --- a/components/Palettes/README.md +++ b/components/Palettes/README.md @@ -16,7 +16,7 @@ The Palettes component provides Material colors organized into similar palettes. - - - diff --git a/components/ProgressView/README.md b/components/ProgressView/README.md index 1911b6a37a9..8b66f57ca4d 100644 --- a/components/ProgressView/README.md +++ b/components/ProgressView/README.md @@ -24,9 +24,8 @@ Progress view is a linear progress indicator that implements Material Design ani ## Related components diff --git a/components/Ripple/README.md b/components/Ripple/README.md index 31166bef650..8354b38a1da 100644 --- a/components/Ripple/README.md +++ b/components/Ripple/README.md @@ -23,14 +23,13 @@ Ripple is a visual form of feedback for touch events providing users a clear sig ## Design & API documentation ## Table of contents diff --git a/components/ShadowElevations/README.md b/components/ShadowElevations/README.md index 96f3d73bb67..1ccc4ba070a 100644 --- a/components/ShadowElevations/README.md +++ b/components/ShadowElevations/README.md @@ -24,7 +24,7 @@ used Material Design elevations for components. - - - diff --git a/components/ShadowLayer/README.md b/components/ShadowLayer/README.md index 7c15e13946c..1cb91371edc 100644 --- a/components/ShadowLayer/README.md +++ b/components/ShadowLayer/README.md @@ -24,8 +24,8 @@ elevation. ### MDCShadowLayer diff --git a/components/Slider/README.md b/components/Slider/README.md index 19926ce7671..18cb64befa6 100644 --- a/components/Slider/README.md +++ b/components/Slider/README.md @@ -25,10 +25,9 @@ or discrete set of values. ## Table of contents diff --git a/components/Snackbar/README.md b/components/Snackbar/README.md index 2ee521c35d7..d19dd80d74d 100644 --- a/components/Snackbar/README.md +++ b/components/Snackbar/README.md @@ -26,14 +26,13 @@ contain a text action, but no icons. ## Related components diff --git a/components/Tabs/README.md b/components/Tabs/README.md index f55f7a6d040..88fe39175e4 100644 --- a/components/Tabs/README.md +++ b/components/Tabs/README.md @@ -24,19 +24,18 @@ Tabs are bars of buttons used to navigate between groups of content. ## Table of contents diff --git a/components/TextFields/README.md b/components/TextFields/README.md index 52e35a8fb4f..7e6169f1000 100644 --- a/components/TextFields/README.md +++ b/components/TextFields/README.md @@ -18,31 +18,30 @@ For more information on text field styles, and animated images of each style in ## Table of contents diff --git a/components/Typography/README.md b/components/Typography/README.md index a5a24c51c72..ddd94d06cee 100644 --- a/components/Typography/README.md +++ b/components/Typography/README.md @@ -19,8 +19,8 @@ from the Material Design specifications. ## Installation diff --git a/components/Typography/docs/README.md b/components/Typography/docs/README.md index 12f7b0f8b68..17d355de294 100644 --- a/components/Typography/docs/README.md +++ b/components/Typography/docs/README.md @@ -17,8 +17,8 @@ from the Material Design specifications. ## Installation diff --git a/contributing/site_content_update.md b/contributing/site_content_update.md deleted file mode 100644 index fcc2a83e9f1..00000000000 --- a/contributing/site_content_update.md +++ /dev/null @@ -1,91 +0,0 @@ -# Site Content Update - -## Overview -Material Components for iOS site consists of 2 parts: -[document site](https://material.io/components/ios/) and API reference site of each -components (e.g, -[AppBar API](https://material.io/components/ios/catalog/app-bars/api-docs/Classes/MDCAppBarContainerViewController.html), etc...) - -This document will walk you through the process for updating the contents on -document site and API reference, or adding new sections to the document site. -You only need to edit markdown files in most cases, however, if you wish to make -further changes to the templates, please read to [Site -Development](./site_development.md). - - -## Updating Content - -### Document Site and GitHub README.md - -#### Syntax - -The Material Components site uses [Jekyll](https://jekyllrb.com/) to help -transform Markdown files into static HTML. This means although it is consistent -with GitHub Flavored Markdown for most cases, we do have some style classes and -special javascript to handle complicate rendering for the website. Please refer -to [Writing READMES](./writing_readmes.md) for the syntax we use. - -#### Structure - -The document site and GitHub README.md have the exact 1:1 mapping structure, except for the homepage. - -- homepage -> site-index.md -- docs -> docs/README.md -- docs/[tutorial_name] -> docs/[tutorial_name]/README.md -- components -> components/README.md -- components/[component_name] -> components/[component_name]/README.md -- contributing -> contributing/README.md - -#### Add new sections - -If you are going to add new sections to the website, you need to modify the configuration file, so that the build system will be aware of the modified structure during build process. - -To set it up, run - -``` -scripts/build_site.sh --setup - -# Modify the navigation data file -vim site-source/jekyll-site-src/_data/navigation.yaml -``` - -In this case, you should change your working directory into `site-source` and run any git commands in that folder. - -``` -cd site-source -# and do git commit & push here... -``` - -This relates to how the site is organized. If you are curious about that, you may read the appendix at the end of [Site Development](./site_development.md). - -### API Reference - -Material Components for iOS uses [jazzy](https://github.com/realm/jazzy) to transform the inline comments in the header files and make it into API reference document. See [their syntax](https://github.com/realm/jazzy#supported-keywords) for updating inline comments in components header files. - -## Build and Preview locally - -Run the following command and follow the instructions on the command line. - - scripts/build_site.sh - -The site should be served at [127.0.0.1:4000](http://127.0.0.1:4000) after build by default. - -## Deploy to production - -You need to be one of the Material Components for iOS core members in order to deploy the site for the moment. However, we will incorporate the changes to the site for every weekly cut release as well. - -If you are able to deploy the site, run - -```bash -# Run these to install gsutil for the first time -curl https://sdk.cloud.google.com | bash -exec -l $SHELL -#Set up the gsutil authentication information, it doesn't matter which app engine project you choose. -gcloud init - -# Deploy it! -scripts/build_site.sh --deploy production -``` - -Open the [Material Components for iOS](https://material.io/components/ios/) site -and make sure your modification is there. diff --git a/contributing/site_development.md b/contributing/site_development.md deleted file mode 100644 index b5b97435b32..00000000000 --- a/contributing/site_development.md +++ /dev/null @@ -1,243 +0,0 @@ -# Site Development - -**This document talks about Material Components for iOS site development. If you are trying to -updating the contents of site, please refer to [Site Content Update](./site_content_update.md) -instead.** - - -## Overview - -Material Components for iOS site consists of 2 parts: [document -site](https://material.io/components/ios/) and API reference site of each components -(e.g, [AppBar -API](https://material.io/components/ios/catalog/app-bars/api-docs/Classes/MDCAppBarContainerViewController.html), -etc...) - -This document will walk you through how you get the sources, modify and deploy the site. If you are -curious about how the sources are organized, you may also read the [in depth -explanation](#appendix-how) at the last of this document. - - -## Get the sources of the site - -**If you have ever built the site (no matter run it locally or deploy to the production), you may -skip this step.** - -Make sure you are under **develop** branch and have the latest code pulled. Then run the following -script. - -```scripts/build_site.sh --setup``` - -The sources of the site will be pulled under a folder called *site-source* under your root folder of -the repository. - - -## Develop the site - -### Develop the document site - -Material component for ios uses [jekyll](https://jekyllrb.com/) to help transform the markdowns into -static HTML. - -The sources *site-source/jekyll-site-src* respect the [directory structure -requirements](https://jekyllrb.com/docs/structure/) of jekyll with a few exceptions. Here is an -overview of the directory structure for mdc: - -- _includes & _layout: the template used by the document site with [liquid - tag](https://github.com/Shopify/liquid/wiki) - -- _sass: the style of the document site - - - base.scss: the basic style of the default html tags. Most of the styles respect [material - design guideline](https://material.io/guidelines/) - - - _globals.scss: the variables and responsive grid definitions. - - - _layout: the style defined for document site. You will modify this file for most of the cases. - - - _layout-api: the style defined for API reference site. This is a bit confusing but API - reference is actually built in as part of the document site and we want to use the syle we -have for the document site. - - - _icons & _step-sequence & _codemirror-syntax-highlighting: These are the utility class for all - icons, step by step guidance and code renderer for example code. - -- css: the css that specify which above styles should be included - -- _data: the definition of the navigation side bar - -- other assets: images, js, thirdparty - -Attention should be paid that *components*, *contributing*, *docs* are all copied files and will be -override at the time when document site is built. So if you are trying to modify the content of -these files, please read [Site Content Update](/site_content_update.md). - -Since the document site uses the feature of jekyll like [Front -Matter](https://jekyllrb.com/docs/frontmatter/), -[Configure](https://jekyllrb.com/docs/configuration/), we suggest you to read jekyll's document if -you are going to develop the site. - - -### Develop the API reference site - -Material component for ios uses [jazzy](https://github.com/realm/jazzy) to transform the inline -comments in the header files and make it into API reference document. - -The sources *site-source/apidocs-site-src* contains the theme and assets. When you build the API -reference, the API reference will be generated and copied to each components folder under -*jekyll-site-src/components* and later be build as part of the document site. Because of that, if -you are trying to modify the styling of the site, we suggest you to modify -*site-source/jekyll-site-src/_scss/_layout-api.scss* instead of css in *theme/assets/css* folder. - - -## Deploy the site - -### Serve the site locally - -Run the following command and follow the hint in the command line. - - scripts/build_site.sh - -The site should be served at [127.0.0.1:4000](http://127.0.0.1:4000) after build by default. - - -### Commit the changes - -You should change your working directory into *site-source* and run git command in that folder - -``` -cd site-source -# and do git commit here... -``` - - -We recommend you to read appendix at the last of this document if you want to know how the sources -are organized. - - -### Deploy the site to production - -You need to be one of the Material component core members in order to deploy the site for the -moment. However, we will incorporate the changes to the site for every weekly cut release as well. - -If you are able to deploy the site, run - -``` -# Run these to install gsutil for the first time -curl https://sdk.cloud.google.com | bash -exec -l $SHELL -# Set up the gsutil authentication information, it doesn't matter which app engine project you -# choose. -gcloud init - -# Deploy it! -scripts/build_site.sh --deploy production -``` - -Open [Material Component site](https://material.io/components/ios/) and make -sure your modification is there. - - -## TODO: Modify the site build script - -## Appendix - How are the sources organized? - -### Overview & Architecture Graph - -Material component ios repository is the place where all components and their documents live. Just -like most of other open source projects, core developers and community members will be actively -improving the components on master/develop branch. Meanwhile, we also want our website get those -updates as soon as the changes are pushed and merged, but leave the process as simple as pull out a -rabbit from the magic hat, without you switching between branches. - -The secret sauce we use is "locally nested structure", where contents and templates are separated in -2 branches (master/develop and site-source) but locally nested one under the other. It is easily to -understand with the following graph: - -![mdc local folder and branch structure](./mdc-local-folder-and-branch-structure.png) - -It is pretty clear that the site should be thought as 2 parts: the contents and the templates. In -our case, the contents are documents of the components. - -As shown in the graph, the document lives together with the component it is documenting, so that -whoever update the component will be able to quickly find the document needed to update and commit -together with the code. On the other hand, the templates live on another branch called "site-source" -and they describe what the website should be look like. Since we separate contents and templates -into branches, it decouples site and components development. - -But then how can we let the developers preview their updates to make sure they are not breaking the -site? The answer lies in the local folder structure. In the repository folder, we cloned another -repository, put it in a folder called site-source and switch the branch in that folder to -site-source. Let's actually look at the build process to see what is happening there. But before we -dive into that, we should also re-emphasize that this folder is gitignored so git won't ever notice -nor ever commit you local build and previews. - -### Build Process - -The build process has 2 steps: - -- Build the api reference -- Build the document site - -Since the api reference is part of the contents of the document site, so step 1 need to be carried -out before step 2. - -In step 1, we build the api reference from the inline comments of the components using -[Jazzy](./site_content_update.md#api-reference). The api reference are output to the jekyll folder -in preparation for step 2. - -In step 2, the documents sepcified in ```site-source/jekyll-site-src/_data/navigation.yaml``` are -copied to the corresponding place and rename to index.md for servering purpose. Jekyll processes -these files and generates static html into ```site-source/site-build```. - -After build, local server might run servering the static html for developer to preview or it will be -deployed to the production server. We would like to add a git hook, so that when update get merged -into master branch, the site will be rebuild and pushed at the same time secretly from the -developer. - - -### Intent behind this - -Although the structure seems hard to understand at first glance, it have several benefits once you -get the gists. - -- Benefits of decoupling - - The components developers and site developers' work should not ever interfere with each other. We -need to ensure in the worst scenario that if one of them need to roll back several commits, the -other team should not even notice that happened, which obviously cannot be achieved if the contents -and templates lives in the same branch. - -- Single source of truth and clear responsibility - - There is only a single source of the truth for both the contents and templates. While the site -developer has the authority for styling the site as they like, they probably won't have insights of -the detailed contents that should be put on the website, component developers vise versa. We barely -defined the syntax as the protocol for communication, so each party can work on its own thing and -hold their absolute authority on that. - -- Preserve document on github - - Since we only have one set of truth for content and all the other are clearly copies of them, we -preserve the README.md in the place that github can still find them, so you can read them directly -on github and even if you are not linked to the internet, you will be able to see all document in -your local repository - -- Benefit of non-blocking - - The component developers should never be blocked by the site developers when updating the content, -nor should bother the site developers to stop what they are doing and help them with merely an -content update. In this case, we are not only separating the authority but also make it possible for -each party to update on their own pace. - -- Intuitively find what you need to update - - The site has exactly 1:1 map onto the source that generate the page. For example, if you are -updating the components/Button/ page, you can find the document right at -components/Button/README.md, which is also right besides the Button.h and Button.m file - -- Simply works - - You actually don't need to understand all the structure if you are not curious. You can write the -components, update the documents and preview, or you can update the style and see without switch -branches. You don't need to understand all the nitty-gritty about all these, it simply works. From 634db350f56c83c4337bbe7041be86beb347734f Mon Sep 17 00:00:00 2001 From: Wenyu Zhang Date: Mon, 27 Jul 2020 15:06:49 -0700 Subject: [PATCH 34/43] [ThumbView] Use shapeGenerator to render the backing layer. PiperOrigin-RevId: 323449769 --- .../private/ThumbTrack/src/MDCThumbTrack.m | 12 ++-- .../private/ThumbTrack/src/MDCThumbView.h | 3 + .../private/ThumbTrack/src/MDCThumbView.m | 62 +++++++++++++------ 3 files changed, 53 insertions(+), 24 deletions(-) diff --git a/components/private/ThumbTrack/src/MDCThumbTrack.m b/components/private/ThumbTrack/src/MDCThumbTrack.m index 1041109d69d..16f5e60b672 100644 --- a/components/private/ThumbTrack/src/MDCThumbTrack.m +++ b/components/private/ThumbTrack/src/MDCThumbTrack.m @@ -751,11 +751,11 @@ - (void)updateViewsNoAnimation { [self updateTrackMask]; _thumbView.backgroundColor = _thumbEnabledColor; - _thumbView.layer.borderColor = _thumbEnabledColor.CGColor; + _thumbView.borderColor = _thumbEnabledColor; } } else { _thumbView.backgroundColor = _thumbDisabledColor; - _thumbView.layer.borderColor = _clearColor.CGColor; + _thumbView.borderColor = _clearColor; if (_thumbIsSmallerWhenDisabled) { [self setDisplayThumbRadius:_thumbRadius - _trackHeight]; @@ -897,7 +897,7 @@ - (void)updateViewsForThumbAfterMoveIsAnimated:(BOOL)animated [self updateTrackMask]; _thumbView.backgroundColor = _clearColor; - _thumbView.layer.borderColor = _trackOffColor.CGColor; + _thumbView.borderColor = _trackOffColor; } CGFloat radius; @@ -912,7 +912,7 @@ - (void)updateViewsForThumbAfterMoveIsAnimated:(BOOL)animated radius = _thumbRadius; } - if (radius == _thumbView.layer.cornerRadius || !_thumbGrowsWhenDragging) { + if (radius == _thumbView.cornerRadius || !_thumbGrowsWhenDragging) { // No need to change anything return; } @@ -921,7 +921,7 @@ - (void)updateViewsForThumbAfterMoveIsAnimated:(BOOL)animated CABasicAnimation *anim = [CABasicAnimation animationWithKeyPath:@"cornerRadius"]; anim.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]; - anim.fromValue = [NSNumber numberWithDouble:_thumbView.layer.cornerRadius]; + anim.fromValue = [NSNumber numberWithDouble:_thumbView.cornerRadius]; anim.toValue = [NSNumber numberWithDouble:radius]; anim.duration = duration; anim.delegate = self; @@ -958,7 +958,7 @@ - (void)updateTrackMask { CGMutablePathRef path = CGPathCreateMutable(); CGPathAddRect(path, NULL, maskFrame); - CGFloat radius = _thumbView.layer.cornerRadius; + CGFloat radius = _thumbView.cornerRadius; if (_thumbView.layer.presentationLayer != NULL) { // If we're animating (growing or shrinking) lean on the side of the smaller radius, to prevent // a gap from appearing between the thumb and the track in the intermediate frames. diff --git a/components/private/ThumbTrack/src/MDCThumbView.h b/components/private/ThumbTrack/src/MDCThumbView.h index 7fa574a6937..c6094903028 100644 --- a/components/private/ThumbTrack/src/MDCThumbView.h +++ b/components/private/ThumbTrack/src/MDCThumbView.h @@ -28,6 +28,9 @@ /** The border width of the thumbview layer. */ @property(nonatomic, assign) CGFloat borderWidth; +/** The border color of the thumbview layer. */ +@property(nullable, nonatomic) UIColor *borderColor; + /** The corner radius of the thumbview layer. */ @property(nonatomic, assign) CGFloat cornerRadius; diff --git a/components/private/ThumbTrack/src/MDCThumbView.m b/components/private/ThumbTrack/src/MDCThumbView.m index 5f5cf581625..9c208bdd2fb 100644 --- a/components/private/ThumbTrack/src/MDCThumbView.m +++ b/components/private/ThumbTrack/src/MDCThumbView.m @@ -15,18 +15,25 @@ #import "MDCThumbView.h" #import "MaterialShadowElevations.h" -#import "MaterialShadowLayer.h" +#import "MaterialShapeLibrary.h" +#import "MaterialShapes.h" +#import "MaterialMath.h" @interface MDCThumbView () @property(nonatomic, strong) UIImageView *iconView; +// The shape generator used to define the shape of the thumb view. +@property(nullable, nonatomic, strong) MDCRectangleShapeGenerator *shapeGenerator; +@property(nonatomic, readonly, strong) MDCShapedShadowLayer *layer; @end @implementation MDCThumbView +@dynamic layer; + + (Class)layerClass { - return [MDCShadowLayer class]; + return [MDCShapedShadowLayer class]; } - (instancetype)initWithFrame:(CGRect)frame { @@ -35,39 +42,50 @@ - (instancetype)initWithFrame:(CGRect)frame { // TODO: Remove once MDCShadowLayer is rasterized by default. self.layer.shouldRasterize = YES; self.layer.rasterizationScale = [UIScreen mainScreen].scale; + _shadowColor = UIColor.blackColor; + _shapeGenerator = [[MDCRectangleShapeGenerator alloc] init]; + self.layer.shadowColor = _shadowColor.CGColor; + self.layer.shapeGenerator = _shapeGenerator; } return self; } - (void)setBorderWidth:(CGFloat)borderWidth { - _borderWidth = borderWidth; - self.layer.borderWidth = borderWidth; + self.layer.shapedBorderWidth = borderWidth; +} + +- (CGFloat)borderWidth { + return self.layer.shapedBorderWidth; +} + +- (void)setBorderColor:(UIColor *)borderColor { + self.layer.shapedBorderColor = borderColor; +} + +- (UIColor *)borderColor { + return self.layer.shapedBorderColor; } - (void)setCornerRadius:(CGFloat)cornerRadius { + if (cornerRadius == _cornerRadius) { + return; + } _cornerRadius = cornerRadius; - self.layer.cornerRadius = cornerRadius; + + MDCCornerTreatment *cornerTreatment = + [[MDCRoundedCornerTreatment alloc] initWithRadius:cornerRadius]; + [self.shapeGenerator setCorners:cornerTreatment]; + [self setNeedsLayout]; } - (MDCShadowElevation)elevation { - return [self shadowLayer].elevation; + return self.layer.elevation; } - (void)setElevation:(MDCShadowElevation)elevation { - [self shadowLayer].elevation = elevation; -} - -- (MDCShadowLayer *)shadowLayer { - return (MDCShadowLayer *)self.layer; -} - -- (void)layoutSubviews { - [super layoutSubviews]; - self.layer.shadowPath = - [UIBezierPath bezierPathWithRoundedRect:self.bounds cornerRadius:_cornerRadius].CGPath; - self.layer.shadowColor = self.shadowColor.CGColor; + self.layer.elevation = elevation; } - (void)setIcon:(nullable UIImage *)icon { @@ -93,4 +111,12 @@ - (void)setShadowColor:(UIColor *)shadowColor { self.layer.shadowColor = shadowColor.CGColor; } +- (void)setBackgroundColor:(UIColor *)backgroundColor { + self.layer.shapedBackgroundColor = backgroundColor; +} + +- (UIColor *)backgroundColor { + return self.layer.shapedBackgroundColor; +} + @end From 90e6bc45892f072e73a85c44bc196e9de92e426b Mon Sep 17 00:00:00 2001 From: Wenyu Zhang Date: Tue, 28 Jul 2020 07:02:21 -0700 Subject: [PATCH 35/43] [TabBarView] Point TabBarViewTheming dependency in podspec to MaterialComponentsBeta. PiperOrigin-RevId: 323564707 --- MaterialComponentsBeta.podspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MaterialComponentsBeta.podspec b/MaterialComponentsBeta.podspec index aa8b5e56b8e..e06741787ff 100644 --- a/MaterialComponentsBeta.podspec +++ b/MaterialComponentsBeta.podspec @@ -55,7 +55,7 @@ Pod::Spec.new do |mdc| "components/#{extension.base_name.split('+')[0]}/src/#{extension.base_name.split('+')[1]}/*.{h,m}", "components/#{extension.base_name.split('+')[0]}/src/#{extension.base_name.split('+')[1]}/private/*.{h,m}" ] - extension.dependency "MaterialComponents/#{extension.base_name.split('+')[0]}+TabBarView" + extension.dependency "MaterialComponentsBeta/#{extension.base_name.split('+')[0]}+TabBarView" extension.dependency "MaterialComponents/schemes/Container" extension.test_spec 'UnitTests' do |unit_tests| From 8410fe26310d619a73d3ca54d2a143b33d4f9ffb Mon Sep 17 00:00:00 2001 From: Wenyu Zhang Date: Tue, 28 Jul 2020 07:42:19 -0700 Subject: [PATCH 36/43] Internal change. PiperOrigin-RevId: 323569857 --- components/private/Snapshot/src/MaterialSnapshot.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/components/private/Snapshot/src/MaterialSnapshot.h b/components/private/Snapshot/src/MaterialSnapshot.h index 0fb81c98d11..ba5f95a6749 100644 --- a/components/private/Snapshot/src/MaterialSnapshot.h +++ b/components/private/Snapshot/src/MaterialSnapshot.h @@ -12,6 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -#import "SnapshotTestCase/MDCSnapshotTestCase.h" -#import "SnapshotUtilities/UIImage+MDCSnapshot.h" -#import "SnapshotUtilities/UIView+MDCSnapshot.h" +#import "SnapshotTestCase/MDCSnapshotTestCase.h" // IWYU pragma: export +#import "SnapshotUtilities/UIImage+MDCSnapshot.h" // IWYU pragma: export +#import "SnapshotUtilities/UIView+MDCSnapshot.h" // IWYU pragma: export From a142aae3e7be48a732d1730c1858f9c15125abc3 Mon Sep 17 00:00:00 2001 From: Wenyu Zhang Date: Tue, 28 Jul 2020 08:02:11 -0700 Subject: [PATCH 37/43] [private/ThumbView] Add centerVisibleArea support with snapshot tests. PiperOrigin-RevId: 323572514 --- .../private/ThumbTrack/src/MDCThumbView.h | 10 +++ .../private/ThumbTrack/src/MDCThumbView.m | 41 +++++++++- .../snapshot/MDCThumbViewSnapshotTests.m | 77 +++++++++++++++++++ 3 files changed, 127 insertions(+), 1 deletion(-) create mode 100644 components/private/ThumbTrack/tests/snapshot/MDCThumbViewSnapshotTests.m diff --git a/components/private/ThumbTrack/src/MDCThumbView.h b/components/private/ThumbTrack/src/MDCThumbView.h index c6094903028..d3c5cee690f 100644 --- a/components/private/ThumbTrack/src/MDCThumbView.h +++ b/components/private/ThumbTrack/src/MDCThumbView.h @@ -34,6 +34,16 @@ /** The corner radius of the thumbview layer. */ @property(nonatomic, assign) CGFloat cornerRadius; +/** + A Boolean value that determines whether the visible area is centered in the bounds of the view. + + If set to YES, the visible area is centered in the bounds of the view, which is often used to + configure invisible tappable area. If set to NO, the visible area fills its bounds. + + The default value is @c NO. +*/ +@property(nonatomic, assign) BOOL centerVisibleArea; + /** Set the @c icon shown on the thumb. */ - (void)setIcon:(nullable UIImage *)icon; diff --git a/components/private/ThumbTrack/src/MDCThumbView.m b/components/private/ThumbTrack/src/MDCThumbView.m index 9c208bdd2fb..73b0baf1cbd 100644 --- a/components/private/ThumbTrack/src/MDCThumbView.m +++ b/components/private/ThumbTrack/src/MDCThumbView.m @@ -73,11 +73,50 @@ - (void)setCornerRadius:(CGFloat)cornerRadius { } _cornerRadius = cornerRadius; + [self configureShapeGeneratorWithCornerRadius:cornerRadius + centerVisibleArea:self.centerVisibleArea]; + + [self setNeedsLayout]; +} + +- (void)setCenterVisibleArea:(BOOL)centerVisibleArea { + if (centerVisibleArea == _centerVisibleArea) { + return; + } + _centerVisibleArea = centerVisibleArea; + + [self configureShapeGeneratorWithCornerRadius:self.cornerRadius + centerVisibleArea:centerVisibleArea]; + + [self setNeedsLayout]; +} + +- (void)configureShapeGeneratorWithCornerRadius:(CGFloat)cornerRadius + centerVisibleArea:(BOOL)centerVisibleArea { MDCCornerTreatment *cornerTreatment = [[MDCRoundedCornerTreatment alloc] initWithRadius:cornerRadius]; [self.shapeGenerator setCorners:cornerTreatment]; - [self setNeedsLayout]; + if (centerVisibleArea) { + UIEdgeInsets visibleAreaInsets = UIEdgeInsetsZero; + CGSize visibleAreaSize = CGSizeMake(cornerRadius * 2, cornerRadius * 2); + CGFloat additionalRequiredHeight = + MAX(0, CGRectGetHeight(self.bounds) - visibleAreaSize.height); + CGFloat additionalRequiredWidth = MAX(0, CGRectGetWidth(self.bounds) - visibleAreaSize.width); + visibleAreaInsets.top = MDCCeil(additionalRequiredHeight * 0.5f); + visibleAreaInsets.bottom = additionalRequiredHeight - visibleAreaInsets.top; + visibleAreaInsets.left = MDCCeil(additionalRequiredWidth * 0.5f); + visibleAreaInsets.right = additionalRequiredWidth - visibleAreaInsets.left; + + self.shapeGenerator.topLeftCornerOffset = + CGPointMake(visibleAreaInsets.left, visibleAreaInsets.top); + self.shapeGenerator.topRightCornerOffset = + CGPointMake(-visibleAreaInsets.right, visibleAreaInsets.top); + self.shapeGenerator.bottomLeftCornerOffset = + CGPointMake(visibleAreaInsets.left, -visibleAreaInsets.bottom); + self.shapeGenerator.bottomRightCornerOffset = + CGPointMake(-visibleAreaInsets.right, -visibleAreaInsets.bottom); + } } - (MDCShadowElevation)elevation { diff --git a/components/private/ThumbTrack/tests/snapshot/MDCThumbViewSnapshotTests.m b/components/private/ThumbTrack/tests/snapshot/MDCThumbViewSnapshotTests.m new file mode 100644 index 00000000000..4c80692fb8c --- /dev/null +++ b/components/private/ThumbTrack/tests/snapshot/MDCThumbViewSnapshotTests.m @@ -0,0 +1,77 @@ +// 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 "MaterialSnapshot.h" +#import "MaterialThumbTrack.h" + +@interface MDCThumbViewSnapshotTests : MDCSnapshotTestCase + +@property(nonatomic, strong) MDCThumbView *thumbView; + +@end + +@implementation MDCThumbViewSnapshotTests + +- (void)setUp { + [super setUp]; + + // Uncomment below to recreate all the goldens (or add the following line to the specific + // test you wish to recreate the golden for). + // self.recordMode = YES; + + self.thumbView = [[MDCThumbView alloc] initWithFrame:CGRectMake(0, 0, 44, 44)]; + self.thumbView.backgroundColor = UIColor.blueColor; +} + +- (void)tearDown { + self.thumbView = nil; + + [super tearDown]; +} + +- (void)generateSnapshotWithBorderAndVerifyForView:(MDCThumbView *)view { + // TODO(b/161927075): Refactor when this functionality is available in MaterialSnapshot. + UIView *thumbViewFrameView = [[UIView alloc] initWithFrame:view.bounds]; + [view addSubview:thumbViewFrameView]; + thumbViewFrameView.layer.borderColor = UIColor.yellowColor.CGColor; + thumbViewFrameView.layer.borderWidth = 0.5f; + + UIView *snapshotView = [view mdc_addToBackgroundView]; + [self snapshotVerifyView:snapshotView]; + + [thumbViewFrameView removeFromSuperview]; +} + +#pragma mark - Tests + +- (void)testThumbViewFullyRoundedDefaults { + // When + self.thumbView.cornerRadius = CGRectGetWidth(self.thumbView.bounds) / 2; + + // Then + [self generateSnapshotWithBorderAndVerifyForView:self.thumbView]; +} + +- (void)testThumbViewWhenCenterVisibleAreaIsTrue { + // When + self.thumbView.centerVisibleArea = YES; + self.thumbView.cornerRadius = 10; + + // Then + [self generateSnapshotWithBorderAndVerifyForView:self.thumbView]; +} + +@end From 9b23c3474ad643d9ae63e6b163cd989e0c36d135 Mon Sep 17 00:00:00 2001 From: Bryan Oltman Date: Tue, 28 Jul 2020 12:43:27 -0700 Subject: [PATCH 38/43] [Snackbar] Delete deprecated MDCSnackbarTypographyThemer. PiperOrigin-RevId: 323632046 --- components/Snackbar/README.md | 40 ---------------- components/Snackbar/docs/README.md | 1 - .../Snackbar/docs/typography-theming.md | 36 -------------- .../SnackbarExampleSupplemental.m | 2 - .../MDCSnackbarTypographyThemer.h | 41 ---------------- .../MDCSnackbarTypographyThemer.m | 24 ---------- .../MaterialSnackbar+TypographyThemer.h | 15 ------ .../MDCSnackbarTypographyThemerTests.swift | 47 ------------------- 8 files changed, 206 deletions(-) delete mode 100644 components/Snackbar/docs/typography-theming.md delete mode 100644 components/Snackbar/src/TypographyThemer/MDCSnackbarTypographyThemer.h delete mode 100644 components/Snackbar/src/TypographyThemer/MDCSnackbarTypographyThemer.m delete mode 100644 components/Snackbar/src/TypographyThemer/MaterialSnackbar+TypographyThemer.h delete mode 100644 components/Snackbar/tests/unit/MDCSnackbarTypographyThemerTests.swift diff --git a/components/Snackbar/README.md b/components/Snackbar/README.md index d19dd80d74d..88f9d38d4bf 100644 --- a/components/Snackbar/README.md +++ b/components/Snackbar/README.md @@ -54,7 +54,6 @@ contain a text action, but no icons. - [Typical use: display a message with an action](#typical-use-display-a-message-with-an-action) - [Extensions](#extensions) - [Color Theming](#color-theming) - - [Typography Theming](#typography-theming) - - - @@ -183,42 +182,3 @@ message.action = action; There is currently no theing extension for MDCSnackbar. - - -### Typography Theming - -You can theme an snackbar with your app's typography scheme using the TypographyThemer extension. - -You must first add the Typography Themer extension to your project: - -```bash -pod 'MaterialComponents/Snackbar+TypographyThemer' -``` - - -#### Swift -```swift -// Step 1: Import the TypographyThemer extension -import MaterialComponents.MaterialSnackbar_TypographyThemer - -// Step 2: Create or get a typography scheme -let typographyScheme = MDCTypographyScheme() - -// Step 3: Apply the typography scheme to your component -MDCSnackbarTypographyThemer.applyTypographyScheme(typographyScheme) -``` - -#### Objective-C - -```objc -// Step 1: Import the TypographyThemer extension -#import "MaterialSnackbar+TypographyThemer.h" - -// Step 2: Create or get a typography scheme -id typographyScheme = [[MDCTypographyScheme alloc] init]; - -// Step 3: Apply the typography scheme to your component -[MDCSnackbarTypographyThemer applyTypographyScheme:colorScheme]; -``` - - diff --git a/components/Snackbar/docs/README.md b/components/Snackbar/docs/README.md index c44cbe172bc..305921a093f 100644 --- a/components/Snackbar/docs/README.md +++ b/components/Snackbar/docs/README.md @@ -53,4 +53,3 @@ visible to the user. ## Extensions - [Color Theming](color-theming.md) -- [Typography Theming](typography-theming.md) diff --git a/components/Snackbar/docs/typography-theming.md b/components/Snackbar/docs/typography-theming.md deleted file mode 100644 index af381a1b721..00000000000 --- a/components/Snackbar/docs/typography-theming.md +++ /dev/null @@ -1,36 +0,0 @@ -### Typography Theming - -You can theme an snackbar with your app's typography scheme using the TypographyThemer extension. - -You must first add the Typography Themer extension to your project: - -```bash -pod 'MaterialComponents/Snackbar+TypographyThemer' -``` - - -#### Swift -```swift -// Step 1: Import the TypographyThemer extension -import MaterialComponents.MaterialSnackbar_TypographyThemer - -// Step 2: Create or get a typography scheme -let typographyScheme = MDCTypographyScheme() - -// Step 3: Apply the typography scheme to your component -MDCSnackbarTypographyThemer.applyTypographyScheme(typographyScheme) -``` - -#### Objective-C - -```objc -// Step 1: Import the TypographyThemer extension -#import "MaterialSnackbar+TypographyThemer.h" - -// Step 2: Create or get a typography scheme -id typographyScheme = [[MDCTypographyScheme alloc] init]; - -// Step 3: Apply the typography scheme to your component -[MDCSnackbarTypographyThemer applyTypographyScheme:colorScheme]; -``` - diff --git a/components/Snackbar/examples/supplemental/SnackbarExampleSupplemental.m b/components/Snackbar/examples/supplemental/SnackbarExampleSupplemental.m index fd8daa04780..6d26b7f78ab 100644 --- a/components/Snackbar/examples/supplemental/SnackbarExampleSupplemental.m +++ b/components/Snackbar/examples/supplemental/SnackbarExampleSupplemental.m @@ -13,7 +13,6 @@ // limitations under the License. #import "SnackbarExampleSupplemental.h" -#import "MaterialSnackbar+TypographyThemer.h" static NSString *const kCellIdentifier = @"Cell"; @@ -24,7 +23,6 @@ - (void)setupExampleViews:(NSArray *)choices { self.view.backgroundColor = [UIColor whiteColor]; [self.collectionView registerClass:[MDCCollectionViewTextCell class] forCellWithReuseIdentifier:kCellIdentifier]; - [MDCSnackbarTypographyThemer applyTypographyScheme:self.typographyScheme]; } #pragma mark - UICollectionView diff --git a/components/Snackbar/src/TypographyThemer/MDCSnackbarTypographyThemer.h b/components/Snackbar/src/TypographyThemer/MDCSnackbarTypographyThemer.h deleted file mode 100644 index 5b2b2da758d..00000000000 --- a/components/Snackbar/src/TypographyThemer/MDCSnackbarTypographyThemer.h +++ /dev/null @@ -1,41 +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. - -#import "MaterialSnackbar.h" -#import "MaterialTypographyScheme.h" - -/** - The Material Design typography system's themer for all snackbar messages. - - @warning This API will eventually be deprecated. See the individual method documentation for - details on replacement APIs. - Learn more at docs/theming.md#migration-guide-themers-to-theming-extensions - */ -@interface MDCSnackbarTypographyThemer : NSObject -@end - -@interface MDCSnackbarTypographyThemer (Deprecated) - -/** - Applies a typography scheme's properties to all snackbar messages. - - @param typographyScheme The typography scheme to apply to all snackbar messages. - - @warning This API will eventually be deprecated. There is no replacement yet. - Track progress here: https://github.com/material-components/material-components-ios/issues/7172 - Learn more at docs/theming.md#migration-guide-themers-to-theming-extensions - */ -+ (void)applyTypographyScheme:(nonnull id)typographyScheme; - -@end diff --git a/components/Snackbar/src/TypographyThemer/MDCSnackbarTypographyThemer.m b/components/Snackbar/src/TypographyThemer/MDCSnackbarTypographyThemer.m deleted file mode 100644 index 28af0da1565..00000000000 --- a/components/Snackbar/src/TypographyThemer/MDCSnackbarTypographyThemer.m +++ /dev/null @@ -1,24 +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. - -#import "MDCSnackbarTypographyThemer.h" - -@implementation MDCSnackbarTypographyThemer - -+ (void)applyTypographyScheme:(nonnull id)typographyScheme { - MDCSnackbarManager.buttonFont = typographyScheme.button; - MDCSnackbarManager.messageFont = typographyScheme.body2; -} - -@end diff --git a/components/Snackbar/src/TypographyThemer/MaterialSnackbar+TypographyThemer.h b/components/Snackbar/src/TypographyThemer/MaterialSnackbar+TypographyThemer.h deleted file mode 100644 index 084d717079f..00000000000 --- a/components/Snackbar/src/TypographyThemer/MaterialSnackbar+TypographyThemer.h +++ /dev/null @@ -1,15 +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. - -#import "MDCSnackbarTypographyThemer.h" diff --git a/components/Snackbar/tests/unit/MDCSnackbarTypographyThemerTests.swift b/components/Snackbar/tests/unit/MDCSnackbarTypographyThemerTests.swift deleted file mode 100644 index dfb53a1ad98..00000000000 --- a/components/Snackbar/tests/unit/MDCSnackbarTypographyThemerTests.swift +++ /dev/null @@ -1,47 +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. - -import XCTest -import MaterialComponents.MaterialSnackbar -import MaterialComponents.MaterialSnackbar_TypographyThemer - -class MDCSnackbarTypographyThemerTests: XCTestCase { - - override func tearDown() { - MDCSnackbarManager.dismissAndCallCompletionBlocks(withCategory: nil) - - super.tearDown() - } - - func testSnackbarTypographyThemer() { - // Given - let typographyScheme = MDCTypographyScheme() - let message = MDCSnackbarMessage() - message.text = "How much wood would a woodchuck chuck if a woodchuck could chuck wood?" - typographyScheme.button = UIFont.boldSystemFont(ofSize: 12) - typographyScheme.body2 = UIFont.systemFont(ofSize: 19) - MDCSnackbarManager.buttonFont = UIFont.systemFont(ofSize: 21) - MDCSnackbarManager.messageFont = UIFont.systemFont(ofSize: 30) - - // When - MDCSnackbarTypographyThemer.applyTypographyScheme(typographyScheme) - - MDCSnackbarManager.show(message) - - // Then - XCTAssertEqual(MDCSnackbarManager.buttonFont, typographyScheme.button) - XCTAssertEqual(MDCSnackbarManager.messageFont, typographyScheme.body2) - } - -} From 40e66874e6ab9000226e825b75d71f91c93e07c0 Mon Sep 17 00:00:00 2001 From: Bryan Oltman Date: Tue, 28 Jul 2020 13:05:36 -0700 Subject: [PATCH 39/43] [Snackbar] Delete deprecated MDCSnackbarFontThemer. PiperOrigin-RevId: 323636613 --- .../src/FontThemer/MDCSnackbarFontThemer.h | 61 ------------------- .../src/FontThemer/MDCSnackbarFontThemer.m | 30 --------- .../FontThemer/MaterialSnackbar+FontThemer.h | 15 ----- .../tests/unit/MDCSnackbarFontThemerTests.m | 57 ----------------- 4 files changed, 163 deletions(-) delete mode 100644 components/Snackbar/src/FontThemer/MDCSnackbarFontThemer.h delete mode 100644 components/Snackbar/src/FontThemer/MDCSnackbarFontThemer.m delete mode 100644 components/Snackbar/src/FontThemer/MaterialSnackbar+FontThemer.h delete mode 100644 components/Snackbar/tests/unit/MDCSnackbarFontThemerTests.m diff --git a/components/Snackbar/src/FontThemer/MDCSnackbarFontThemer.h b/components/Snackbar/src/FontThemer/MDCSnackbarFontThemer.h deleted file mode 100644 index 5f3ba74c884..00000000000 --- a/components/Snackbar/src/FontThemer/MDCSnackbarFontThemer.h +++ /dev/null @@ -1,61 +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. - -#import - -#import "MaterialSnackbar.h" -#import "MaterialThemes.h" - -#pragma mark - Soon to be deprecated - -/** - Used to apply a font scheme to theme to MDCSnackbarMessageView. - - @warning This API will eventually be deprecated. See the individual method documentation for - details on replacement APIs. - Learn more at docs/theming.md#migration-guide-themers-to-theming-extensions - */ -@interface MDCSnackbarFontThemer : NSObject -@end - -@interface MDCSnackbarFontThemer (Deprecated) - -/** - Applies a font scheme to theme to a MDCSnackbarMessageView. - - @warning This API will soon be deprecated. Please consider using MDCSnackbarTypographyThemer - instead. - - @param fontScheme The font scheme to apply to MDCSnackbarMessageView. - @param snackbarMessageView A MDCSnackbarMessageView instance to apply a font scheme. - - @warning This API will eventually be deprecated. There is no replacement yet. - Track progress here: https://github.com/material-components/material-components-ios/issues/7172 - Learn more at docs/theming.md#migration-guide-themers-to-theming-extensions - */ -+ (void)applyFontScheme:(nonnull id)fontScheme - toSnackbarMessageView:(nonnull MDCSnackbarMessageView *)snackbarMessageView; - -/** - Applies a font scheme to theme to a MDCSnackbarMessageView. - - @param fontScheme The font scheme to apply to MDCSnackbarMessageView. - - @warning This API will eventually be deprecated. There is no replacement yet. - Track progress here: https://github.com/material-components/material-components-ios/issues/7172 - Learn more at docs/theming.md#migration-guide-themers-to-theming-extensions - */ -+ (void)applyFontScheme:(nonnull id)fontScheme; - -@end diff --git a/components/Snackbar/src/FontThemer/MDCSnackbarFontThemer.m b/components/Snackbar/src/FontThemer/MDCSnackbarFontThemer.m deleted file mode 100644 index 1aa16b721fd..00000000000 --- a/components/Snackbar/src/FontThemer/MDCSnackbarFontThemer.m +++ /dev/null @@ -1,30 +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. - -#import "MDCSnackbarFontThemer.h" - -@implementation MDCSnackbarFontThemer - -+ (void)applyFontScheme:(nonnull id)fontScheme - toSnackbarMessageView:(nonnull MDCSnackbarMessageView *)snackbarMessageView { - snackbarMessageView.messageFont = fontScheme.body2; - snackbarMessageView.buttonFont = fontScheme.button; -} - -+ (void)applyFontScheme:(nonnull id)fontScheme { - MDCSnackbarManager.messageFont = fontScheme.body2; - MDCSnackbarManager.buttonFont = fontScheme.button; -} - -@end diff --git a/components/Snackbar/src/FontThemer/MaterialSnackbar+FontThemer.h b/components/Snackbar/src/FontThemer/MaterialSnackbar+FontThemer.h deleted file mode 100644 index 8c6e0d0c6ae..00000000000 --- a/components/Snackbar/src/FontThemer/MaterialSnackbar+FontThemer.h +++ /dev/null @@ -1,15 +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. - -#import "MDCSnackbarFontThemer.h" diff --git a/components/Snackbar/tests/unit/MDCSnackbarFontThemerTests.m b/components/Snackbar/tests/unit/MDCSnackbarFontThemerTests.m deleted file mode 100644 index 9c802357d3e..00000000000 --- a/components/Snackbar/tests/unit/MDCSnackbarFontThemerTests.m +++ /dev/null @@ -1,57 +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. - -#import - -#import "MaterialSnackbar+FontThemer.h" -#import "MaterialSnackbar.h" -#import "MaterialThemes.h" - -@interface MDCSnackbarFontThemerTests : XCTestCase -@end - -@implementation MDCSnackbarFontThemerTests - -- (void)tearDown { - [MDCSnackbarManager dismissAndCallCompletionBlocksWithCategory:nil]; - - [super tearDown]; -} - -- (void)testSnackbarFontThemerUsingUIAppearance { - MDCSnackbarMessage *message = [[MDCSnackbarMessage alloc] init]; - message.text = @"How much wood would a woodchuck chuck if a woodchuck could chuck wood?"; - [MDCSnackbarManager showMessage:message]; - MDCBasicFontScheme *fontScheme = [[MDCBasicFontScheme alloc] init]; - fontScheme.button = [UIFont boldSystemFontOfSize:12]; - fontScheme.body2 = [UIFont systemFontOfSize:13]; - [MDCSnackbarFontThemer applyFontScheme:fontScheme - toSnackbarMessageView:[MDCSnackbarMessageView appearance]]; - XCTAssertEqualObjects([MDCSnackbarMessageView appearance].messageFont, fontScheme.body2); - XCTAssertEqualObjects([MDCSnackbarMessageView appearance].buttonFont, fontScheme.button); -} - -- (void)testSnackbarFontThemer { - MDCSnackbarMessage *message = [[MDCSnackbarMessage alloc] init]; - message.text = @"How much wood would a woodchuck chuck if a woodchuck could chuck wood?"; - [MDCSnackbarManager showMessage:message]; - MDCBasicFontScheme *fontScheme = [[MDCBasicFontScheme alloc] init]; - fontScheme.button = [UIFont boldSystemFontOfSize:12]; - fontScheme.body2 = [UIFont systemFontOfSize:13]; - [MDCSnackbarFontThemer applyFontScheme:fontScheme]; - XCTAssertEqualObjects(MDCSnackbarManager.messageFont, fontScheme.body2); - XCTAssertEqualObjects(MDCSnackbarManager.buttonFont, fontScheme.button); -} - -@end From 1b68b485221f21a1a4ee4655f3971a3b56b769e4 Mon Sep 17 00:00:00 2001 From: Bryan Oltman Date: Tue, 28 Jul 2020 16:24:53 -0700 Subject: [PATCH 40/43] [ProgressView] Remove request for VoiceOver focus in MDCProgressView's setHidden. PiperOrigin-RevId: 323676726 --- components/ProgressView/src/MDCProgressView.m | 5 ----- 1 file changed, 5 deletions(-) diff --git a/components/ProgressView/src/MDCProgressView.m b/components/ProgressView/src/MDCProgressView.m index 68513f1526d..752170434ba 100644 --- a/components/ProgressView/src/MDCProgressView.m +++ b/components/ProgressView/src/MDCProgressView.m @@ -217,11 +217,6 @@ - (void)setProgress:(float)progress completion:userCompletion]; } -- (void)setHidden:(BOOL)hidden { - [super setHidden:hidden]; - UIAccessibilityPostNotification(UIAccessibilityLayoutChangedNotification, hidden ? nil : self); -} - - (void)setHidden:(BOOL)hidden animated:(BOOL)animated completion:(void (^__nullable)(BOOL finished))userCompletion { From db2169948d93187427e8d51cfaeaeb2b9c3b0152 Mon Sep 17 00:00:00 2001 From: Jeff Verkoeyen Date: Wed, 29 Jul 2020 09:39:51 -0400 Subject: [PATCH 41/43] Automatic changelog preparation for release. --- .gitattributes | 28 ++++++++++-- CHANGELOG.md | 114 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 139 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 6979b0784b8..1549c9f8271 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,117 @@ +# #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 + +* [Material io bottom app bar](https://github.com/material-components/material-components-ios/commit/d6255fa0f431f3ee62b83637f0a50fb07c153292) (Andrew Overton) + +### BottomNavigation + +* [When large content view is enabled, on long presses it will correctly switch to the last selected item](https://github.com/material-components/material-components-ios/commit/3481620f18b9c3e62d1f9a5858f362843269099b) (Alyssa Weiss) + +### ButtonBar + +* [Fix ButtonBar typical example's crash by making containerScheme a property.](https://github.com/material-components/material-components-ios/commit/0a0f7347fac7639ac132934705e88f69d7fedc6b) (Wenyu Zhang) + +### Buttons + +* [Add rippleColor and rippleStyle APIs](https://github.com/material-components/material-components-ios/commit/9039d47602220f44f5ea2fa72d1683173e35b5c8) (Cody Weaver) +* [Add rippleEdgeInsets API.](https://github.com/material-components/material-components-ios/commit/330125967a95335c7c678982715b2bbdb085d9de) (Cody Weaver) +* [Add rippleMaximumRadius API.](https://github.com/material-components/material-components-ios/commit/2a5996b407f3ba821672bf35cee5cace2bebdbd8) (Cody Weaver) +* [Fix lint error `UppercaseAttributedString` in `MDCButton`](https://github.com/material-components/material-components-ios/commit/3132beea793356e3b0f6cb77c32c9c5a1a23e68b) (Nobody) +* [Remove unused TitleColorAccessibilityMutator.](https://github.com/material-components/material-components-ios/commit/7b45e787db8aaa9885e8c42bb94aa6406658f3dc) (Bryan Oltman) + +### Chips + +* [Add rippleForState APIs.](https://github.com/material-components/material-components-ios/commit/8a509d558ffdf3225b56823140c632fb2aa39ca1) (Cody Weaver) + +### Dialogs + +* [Improved titleIconView example with animation.](https://github.com/material-components/material-components-ios/commit/89ddea0cf1a5d6b646be808f288978318d7ce0dc) (Galia Kaufman) + +### FeatureHighlight + +* [Delete deprecated FeatureHighlightAccessibilityMutator.](https://github.com/material-components/material-components-ios/commit/180a5bf724f0fac5fa824660cca1c11a02de9721) (Bryan Oltman) + +### HeaderStackView + +* [Delete deprecated MDCHeaderStackViewColorThemer](https://github.com/material-components/material-components-ios/commit/77c7bfae5f113d90971c95f20fccb82cd3fe1cd2) (Bryan Oltman) + +### ProgressView + +* [Remove request for VoiceOver focus in MDCProgressView's setHidden.](https://github.com/material-components/material-components-ios/commit/1b68b485221f21a1a4ee4655f3971a3b56b769e4) (Bryan Oltman) + +### Shapes + +* [Add unit tests demonstrating a divide by zero error.](https://github.com/material-components/material-components-ios/commit/fb4f43d36c7b1887f8396dada4d88177ea32d3d7) (Jeff Verkoeyen) +* [Fix divide by zero bug.](https://github.com/material-components/material-components-ios/commit/2db3fd1ced49aefcd3d440e91e7fbc675967053d) (Jeff Verkoeyen) +* [Fixes for the shape shadow layer if the borderWidth is set multiple times and there isn't a prepareShadowPath pass.](https://github.com/material-components/material-components-ios/commit/a5ddb265b2967e449e18ab228f1da841e3bf4ae3) (Yarden Eitan) +* [Update shapeLayer to the correct path to correctly be a mask for content.](https://github.com/material-components/material-components-ios/commit/f4263fe70ed103b05912bec7cdf48b271d3b31ca) (Yarden Eitan) +* [Updates the shapeGenerator with a line width to inset the line rather than center it.](https://github.com/material-components/material-components-ios/commit/3f3816f014bc4c5f0760c8838a0dc6916dd42f33) (Yarden Eitan) + +### Slider + +* [Add EarlGrey test to expose bug where tapping on slider track opposite anchor point doesn't trigger UIControlEventValueChanged](https://github.com/material-components/material-components-ios/commit/0495b7987138dab2608f57772fdc5869481e59a7) (Bryan Oltman) +* [Remove supplemental directory from examples.](https://github.com/material-components/material-components-ios/commit/de482d077aa4fa1310bcb268952ce98a6d2291ba) (Bryan Oltman) + +### Snackbar + +* [Delete deprecated MDCSnackbarFontThemer.](https://github.com/material-components/material-components-ios/commit/40e66874e6ab9000226e825b75d71f91c93e07c0) (Bryan Oltman) +* [Delete deprecated MDCSnackbarTypographyThemer.](https://github.com/material-components/material-components-ios/commit/9b23c3474ad643d9ae63e6b163cd989e0c36d135) (Bryan Oltman) +* [Use different margins for multi-line snackbars](https://github.com/material-components/material-components-ios/commit/d6498592978d0ef3e22f11a7c49fc9f1aad25547) (Alyssa Weiss) + +### Tabs + +* [Adding theming extension for TabBarView](https://github.com/material-components/material-components-ios/commit/6f3b23dfb83d883b88aa061c05de4dc6c4d72a0c) (Alyssa Weiss) + +### TextControls + +* [Add debug stuff to text controls examples](https://github.com/material-components/material-components-ios/commit/93fb8836455d7716810afa054e09ccb9e4e02220) (Andrew Overton) +* [Expose preferredContainerHeight on MDCBaseTextField](https://github.com/material-components/material-components-ios/commit/e360f3c1c4e26bfad204876072241de753fc261e) (Andrew Overton) +* [Fix jumpiness in text areas](https://github.com/material-components/material-components-ios/commit/66497f78375748c4d71d1a45a82420ff438373e4) (Andrew Overton) +* [Fix secureTextEntry layout pass infinite loop](https://github.com/material-components/material-components-ios/commit/b18a9191cc84973f74003e277627676bb245177f) (Andrew Overton) + +### private/Snapshot + +* [Internal change.](https://github.com/material-components/material-components-ios/commit/8410fe26310d619a73d3ca54d2a143b33d4f9ffb) (Wenyu Zhang) + +### private/TextControlsPrivate + +* [Make filled positioning reference subclass NSObject](https://github.com/material-components/material-components-ios/commit/ae6938670e6083c94f300cbd2e9484d69ec060a4) (Andrew Overton) + +### private/ThumbTrack + +* [Add centerVisibleArea support with snapshot tests.](https://github.com/material-components/material-components-ios/commit/a142aae3e7be48a732d1730c1858f9c15125abc3) (Wenyu Zhang) +* [Add snapshot tests to MDCThumbTrack.](https://github.com/material-components/material-components-ios/commit/e4efea4264dd431768cc491c8e442624f4d39937) (Wenyu Zhang) +* [Fix an issue where sliders with non-min/max anchor points do not publish a UIControlEventValueChanged event when a track tap causes the value to cross the anchor point.](https://github.com/material-components/material-components-ios/commit/c5dbf01d5a072596ee9447a67e6dff5090936ea7) (Bryan Oltman) +* [Internal Change](https://github.com/material-components/material-components-ios/commit/6aea60a148025c15ddf8b8558192f4366abfb672) (Alyssa Weiss) +* [Use shapeGenerator to render the backing layer.](https://github.com/material-components/material-components-ios/commit/634db350f56c83c4337bbe7041be86beb347734f) (Wenyu Zhang) + +### schemes/Shape + +* [Add test demonstrating issue with calling borderWidth multiple times with same value.](https://github.com/material-components/material-components-ios/commit/719d68811944b8d20e92d83db3d95d6844dedc70) (Yarden Eitan) + +## Multi-component changes + +* [Allow setting underline thickness regardless of state](https://github.com/material-components/material-components-ios/commit/0950ece8f3d980d9fd13cd2718dc32e67e4ed18c) (Andrew Overton) +* [Prepare to expose preferredContainerHeight on text controls](https://github.com/material-components/material-components-ios/commit/49b441e84976df1d1f70b1809c2630c5073f1a44) (Andrew Overton) +* [Replace material.io API doc links with links to the relevant header f…](https://github.com/material-components/material-components-ios/commit/07c46757cc73d995eddc6205455d8c480f3270b5) (Andrew Overton) + +--- + # 111.0.0 In this Major release we removed some deprecated action sheet themers, made visual improvements to Chips, ProgressView, Banner and Tabs. We also made links clickable in Alert Dialogs From dccdeb42d458e3d783f8fa032bac31498cadef81 Mon Sep 17 00:00:00 2001 From: Jeff Verkoeyen Date: Thu, 30 Jul 2020 09:02:57 -0400 Subject: [PATCH 42/43] Update changelog. --- CHANGELOG.md | 27 +++++++++++++++++++++------ 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1549c9f8271..0960d32d9e0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,15 +1,30 @@ -# #develop# +# 112.0.0 + +This major releases deletes several deprecated APIs, expands Ripple support, and fixes several bugs. -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 +Buttons' `MDCTitleColorAccessibilityMutator` and FeatureHighlight's +`MDCFeatureHighlightAccessibilityMutator` have been deleted. There are no official replacements for +these APIs. + +`MDCHeaderStackViewColorThemer` has been deleted. There is no replacement for this API; use the +AppBar Theming APIs instead. + +`MDCSnackbarFontThemer` and `MDCSnackbarTypographyThemer` have been deleted. Use the Snackbar +Theming APIs instead. -Replace this text with links to deprecation guides. ## New features -Replace this text with example code for each new feature. +Buttons has several new Ripple APIs, including `rippleStyle`, `rippleColor`, `rippleEdgeInsets`, +and `rippleMaximumRadius`. These APIs are intended to act as replacements for the similar Ink APIs. + +Chips similarly has added a `rippleForState` API. + +TabBarView now has a Theming extension. + +TextControls now expose a `preferredContainerHeight` API. + ## API changes ## Component changes From af602310f179ef3e76e5b44a71c29fa7c089ea00 Mon Sep 17 00:00:00 2001 From: Jeff Verkoeyen Date: Thu, 30 Jul 2020 09:03:19 -0400 Subject: [PATCH 43/43] Bump the release. --- MaterialComponents.podspec | 2 +- MaterialComponentsBeta.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 +- 12 files changed, 14 insertions(+), 14 deletions(-) diff --git a/MaterialComponents.podspec b/MaterialComponents.podspec index 788c7c5dede..725ad915021 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 = "111.0.0" + mdc.version = "112.0.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/MaterialComponentsBeta.podspec b/MaterialComponentsBeta.podspec index e06741787ff..18a20e9aff0 100644 --- a/MaterialComponentsBeta.podspec +++ b/MaterialComponentsBeta.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |mdc| mdc.name = "MaterialComponentsBeta" - mdc.version = "111.0.0" + mdc.version = "112.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" diff --git a/MaterialComponentsEarlGreyTests.podspec b/MaterialComponentsEarlGreyTests.podspec index fd722e66b6e..216a8ddf4ce 100644 --- a/MaterialComponentsEarlGreyTests.podspec +++ b/MaterialComponentsEarlGreyTests.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "MaterialComponentsEarlGreyTests" - s.version = "111.0.0" + s.version = "112.0.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 a8b360c6463..038ff821185 100644 --- a/MaterialComponentsExamples.podspec +++ b/MaterialComponentsExamples.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "MaterialComponentsExamples" - s.version = "111.0.0" + s.version = "112.0.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 4b8784ea271..1d95fa20235 100644 --- a/MaterialComponentsSnapshotTests.podspec +++ b/MaterialComponentsSnapshotTests.podspec @@ -53,7 +53,7 @@ end Pod::Spec.new do |s| s.name = "MaterialComponentsSnapshotTests" - s.version = "111.0.0" + s.version = "112.0.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 12cd89fb66d..6d852789748 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -111.0.0 +112.0.0 diff --git a/catalog/MDCCatalog/Info.plist b/catalog/MDCCatalog/Info.plist index 0bdf2b1bef3..b192a35ef14 100644 --- a/catalog/MDCCatalog/Info.plist +++ b/catalog/MDCCatalog/Info.plist @@ -15,11 +15,11 @@ CFBundlePackageType APPL CFBundleShortVersionString - 111.0.0 + 112.0.0 CFBundleSignature ???? CFBundleVersion - 111.0.0 + 112.0.0 LSRequiresIPhoneOS UIAppFonts diff --git a/catalog/MDCDragons/Info.plist b/catalog/MDCDragons/Info.plist index 0988818b047..0f2bb89a6f3 100644 --- a/catalog/MDCDragons/Info.plist +++ b/catalog/MDCDragons/Info.plist @@ -15,9 +15,9 @@ CFBundlePackageType APPL CFBundleShortVersionString - 111.0.0 + 112.0.0 CFBundleVersion - 111.0.0 + 112.0.0 LSRequiresIPhoneOS UILaunchStoryboardName diff --git a/catalog/MaterialCatalog/MaterialCatalog.podspec b/catalog/MaterialCatalog/MaterialCatalog.podspec index a102022349d..60b061982b3 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 = "111.0.0" + s.version = "112.0.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 df1227ab8d1..0f4aec508be 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 = @"111.0.0"; +static NSString* const kMDCLibraryInfoVersionString = @"112.0.0"; @implementation MDCLibraryInfo diff --git a/components/LibraryInfo/tests/unit/LibraryInfoTests.m b/components/LibraryInfo/tests/unit/LibraryInfoTests.m index 3cd91150309..367aafe04d8 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: "111.0.0", etc. + // Accept: "112.0.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 c8120b615d4..7a0d3b84a8d 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 = "111.0.0" + s.version = "112.0.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"