diff --git a/CHANGELOG.md b/CHANGELOG.md index 7c75e6e096d..3f330c2e1c6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,42 @@ +# 119.4.0 + +In this minor release, we added a new contentEdgeInsets API to Banner, added support for vertically centered image views in MDCSelfSizingStereoCell, and improved support for tvOS. + +## API changes + +### Banner + +*new* property: `contentEdgeInsets` in `MDCBannerView`. + +### Lists + +*new* enum: `MDCSelfSizingStereoCellImageViewVerticalPosition`. +*new* property: `leadingImageViewVerticalPosition` in `MDCSelfSizingStereoCell`. +*new* property: `trailingImageViewVerticalPosition` in `MDCSelfSizingStereoCell`. + +## Component changes + +### Banner + +* [ Add contentEdgeInsets support to replace layoutMargins usage in sizeThatFits:.](https://github.com/material-components/material-components-ios/commit/8bd8b295e227310a8c835ec9d3f58ca3b651319a) (Wenyu Zhang) +* [setNeedsLayout before showing Banner in BannerAutolayoutSwiftExampleViewController](https://github.com/material-components/material-components-ios/commit/1faf60e6f5fee51470fc2b21b26304879d6405da) (Wenyu Zhang) + +### Cards + +* [Update setting corner radius to match team style guide](https://github.com/material-components/material-components-ios/commit/4b7c446e7e14b43202421311894e388b18df6650) (Cody Weaver) + +### List + +* [Support vertically centered image views in MDCSelfSizingStereoCell](https://github.com/material-components/material-components-ios/commit/bdeb1f7e9d23708ab01440cb3f7e9d681c195038) (Andrew Overton) + +## Multi-component changes + +* [Additional requested docs changes](https://github.com/material-components/material-components-ios/commit/b42de42c876dada1437193a2c0b793b5cef41e70) (Andrew Overton) +* [Ensure swift snippets always come before objc](https://github.com/material-components/material-components-ios/commit/08ca10b07876de6f363c7f6baf6b265f3980fea1) (Andrew Overton) +* [Improve support for tvOS.](https://github.com/material-components/material-components-ios/commit/3bc7339b7a8e834a661de78fe3ae9805468f5259) (Jeff Verkoeyen) + +--- + # 119.3.0 In this minor release we added a new method to the `MDCBaseTextFieldDelegate` protocol and fixed bugs in NavigationDrawer, TextControls, and Tabs. diff --git a/MaterialComponents.podspec b/MaterialComponents.podspec index 156cd5b580c..efa09b47ebf 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 = "119.3.0" + mdc.version = "119.4.0" mdc.authors = "The Material Components authors." mdc.summary = "A collection of stand-alone production-ready UI libraries focused on design details." mdc.homepage = "https://github.com/material-components/material-components-ios" diff --git a/MaterialComponentsEarlGreyTests.podspec b/MaterialComponentsEarlGreyTests.podspec index 2b3bd60ae6e..81b8b6ac9fd 100644 --- a/MaterialComponentsEarlGreyTests.podspec +++ b/MaterialComponentsEarlGreyTests.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "MaterialComponentsEarlGreyTests" - s.version = "119.3.0" + s.version = "119.4.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 ca3dbc5158a..470084e93f6 100644 --- a/MaterialComponentsExamples.podspec +++ b/MaterialComponentsExamples.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "MaterialComponentsExamples" - s.version = "119.3.0" + s.version = "119.4.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 b170974b749..9c8f2c202c4 100644 --- a/MaterialComponentsSnapshotTests.podspec +++ b/MaterialComponentsSnapshotTests.podspec @@ -53,7 +53,7 @@ end Pod::Spec.new do |s| s.name = "MaterialComponentsSnapshotTests" - s.version = "119.3.0" + s.version = "119.4.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 c2b04563bda..d8b28152970 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -119.3.0 +119.4.0 diff --git a/catalog/MDCCatalog/Info.plist b/catalog/MDCCatalog/Info.plist index bc10c76c81f..cd2753764d6 100644 --- a/catalog/MDCCatalog/Info.plist +++ b/catalog/MDCCatalog/Info.plist @@ -15,11 +15,11 @@ CFBundlePackageType APPL CFBundleShortVersionString - 119.3.0 + 119.4.0 CFBundleSignature ???? CFBundleVersion - 119.3.0 + 119.4.0 LSRequiresIPhoneOS UIAppFonts diff --git a/catalog/MDCDragons/Info.plist b/catalog/MDCDragons/Info.plist index a804cabf07c..6b9bbd85eee 100644 --- a/catalog/MDCDragons/Info.plist +++ b/catalog/MDCDragons/Info.plist @@ -15,9 +15,9 @@ CFBundlePackageType APPL CFBundleShortVersionString - 119.3.0 + 119.4.0 CFBundleVersion - 119.3.0 + 119.4.0 LSRequiresIPhoneOS UILaunchStoryboardName diff --git a/catalog/MaterialCatalog/MaterialCatalog.podspec b/catalog/MaterialCatalog/MaterialCatalog.podspec index 2bfe0fcdec7..fe68d8d3781 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 = "119.3.0" + s.version = "119.4.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/AppBar/README.md b/components/AppBar/README.md index d95838cedb5..b6b0fd75f50 100644 --- a/components/AppBar/README.md +++ b/components/AppBar/README.md @@ -21,7 +21,6 @@ information and actions relating to the current screen. * [Using top app bars](#using-top-app-bars) * [Regular top app bar](#regular-top-app-bar) -* [Contextual action bar](#contextual-action-bar) * [Theming](#theming) * [Migration guides](#migration-guides) * [Unsupported](#unsupported) @@ -104,16 +103,10 @@ NSLog(@"accessibilityLabel: %@",self.navigationItem.rightBarButtonItem.accessibi ``` -## Types - -There are two types of top app bar: -1. [Regular top app bar](#regular-top-app-bar) -1. [Contextual action bar](#contextual-action-bar) - -![Regular top app bar and contextual action bars](docs/assets/appbar-types.png) - ## Regular top app bar +![Example of an iOS regular top app bar](docs/assets/app-bar-example.png) + Regular top app bars are the only top app bars supported on iOS. ### Top app bar examples @@ -641,10 +634,6 @@ See the [FlexibleHeader](../FlexibleHeader) documentation for additional usage g -------------------------------- | -------------------- | ------------------------------------------ | ------------- **Icons** | `-[UIViewController navigationItem]` | `-setLeftBarButtonItems:`
`-leftBarButtonItems`
`-setRightBarButtonItems:`
`-rightBarButtonItems` | `nil` -## Contextual action bar - -Contextual action bars are not implemented on iOS. - ## Theming `MDCAppBarViewController` supports Material Theming using a Container Scheme. To theme your app bar, add the `AppBar+Theming` subspec to your `Podfile`: diff --git a/components/AppBar/docs/assets/app-bar-example.png b/components/AppBar/docs/assets/app-bar-example.png new file mode 100644 index 00000000000..30b0040c9e9 Binary files /dev/null and b/components/AppBar/docs/assets/app-bar-example.png differ diff --git a/components/AppBar/docs/assets/appbar-types.png b/components/AppBar/docs/assets/appbar-types.png deleted file mode 100644 index e754eb3766a..00000000000 Binary files a/components/AppBar/docs/assets/appbar-types.png and /dev/null differ diff --git a/components/Banner/examples/AppBarBannerExample.m b/components/Banner/examples/AppBarBannerExample.m index 9103aa3bf94..a7a93e418fa 100644 --- a/components/Banner/examples/AppBarBannerExample.m +++ b/components/Banner/examples/AppBarBannerExample.m @@ -86,7 +86,7 @@ - (void)dismissBanner { - (void)showBanner { self.banner = [[MDCBannerView alloc] init]; [self.banner applyThemeWithScheme:_containerScheme]; - self.banner.layoutMargins = UIEdgeInsetsMake(0, 8, 0, 8); + self.banner.contentEdgeInsets = UIEdgeInsetsMake(0, 8, 0, 8); self.banner.textView.text = @"This banner has been set as bottomBar of this AppBar."; [self.banner.leadingButton setTitle:@"Dismiss" forState:UIControlStateNormal]; [self.banner.leadingButton addTarget:self diff --git a/components/Banner/examples/BannerAutolayoutSwiftExampleViewController.swift b/components/Banner/examples/BannerAutolayoutSwiftExampleViewController.swift index 73724aab2fd..87625118ba0 100644 --- a/components/Banner/examples/BannerAutolayoutSwiftExampleViewController.swift +++ b/components/Banner/examples/BannerAutolayoutSwiftExampleViewController.swift @@ -13,11 +13,10 @@ // limitations under the License. import UIKit - import MaterialComponents.MaterialBanner -import MaterialComponents.MaterialBanner_Theming +import MaterialComponents.MaterialBanner_Theming import MaterialComponents.MaterialButtons -import MaterialComponents.MaterialButtons_Theming +import MaterialComponents.MaterialButtons_Theming import MaterialComponents.MaterialColorScheme import MaterialComponents.MaterialContainerScheme @@ -33,7 +32,8 @@ class BannerAutoLayoutSwiftExampleViewController: UIViewController { showBannerButton.translatesAutoresizingMaskIntoConstraints = false showBannerButton.applyTextTheme(withScheme: containerScheme) showBannerButton.setTitle("Material Banner", for: .normal) - showBannerButton.addTarget(self, action: #selector(self.didTapShowBannerButton), for: .touchUpInside) + showBannerButton.addTarget( + self, action: #selector(self.didTapShowBannerButton), for: .touchUpInside) view.addSubview(showBannerButton) showBannerButton.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true showBannerButton.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true @@ -45,7 +45,8 @@ class BannerAutoLayoutSwiftExampleViewController: UIViewController { bannerView.layoutMargins = .zero let actionButton = bannerView.leadingButton actionButton.setTitle("Dismiss", for: .normal) - actionButton.addTarget(self, action: #selector(self.didTapDismissOnBannerView), for: .touchUpInside) + actionButton.addTarget( + self, action: #selector(self.didTapDismissOnBannerView), for: .touchUpInside) bannerView.applyTheme(withScheme: containerScheme) view.addSubview(bannerView) bannerView.isHidden = true @@ -60,7 +61,8 @@ class BannerAutoLayoutSwiftExampleViewController: UIViewController { @objc func didTapShowBannerButton() { bannerView.isHidden = false - UIAccessibility.post(notification:.layoutChanged, argument: bannerView); + bannerView.setNeedsLayout() + UIAccessibility.post(notification: .layoutChanged, argument: bannerView) } @objc func didTapDismissOnBannerView() { diff --git a/components/Banner/src/MDCBannerView.h b/components/Banner/src/MDCBannerView.h index 6bf72d377e3..e0f9722e430 100644 --- a/components/Banner/src/MDCBannerView.h +++ b/components/Banner/src/MDCBannerView.h @@ -86,6 +86,15 @@ __attribute__((objc_subclassing_restricted)) @interface MDCBannerView */ @property(nonatomic, readwrite, strong, nonnull) UIColor *dividerColor; +/** + The insets applied to the banner for all its content. + + The banner uses this property to determine @c intrinsicContentSize and @c sizeThatFits:. + + The default value is @c UIEdgeInsetsZero. + */ +@property(nonatomic, readwrite, assign) UIEdgeInsets contentEdgeInsets; + /* Indicates whether the banner should automatically update its font when the device’s UIContentSizeCategory is changed. diff --git a/components/Banner/src/MDCBannerView.m b/components/Banner/src/MDCBannerView.m index 5b2159ccbd1..5fa0fb5669e 100644 --- a/components/Banner/src/MDCBannerView.m +++ b/components/Banner/src/MDCBannerView.m @@ -36,6 +36,8 @@ @interface MDCBannerView () +@property(nonatomic, readwrite, strong) UIView *contentView; + @property(nonatomic, readwrite, strong) UITextView *textView; @property(nonatomic, readwrite, strong) UIImageView *imageView; @@ -47,6 +49,12 @@ @interface MDCBannerView () @property(nonatomic, readwrite, strong) UIView *divider; @property(nonatomic, readwrite, assign) CGFloat dividerHeight; +// Content constraints +@property(nonatomic, readwrite, strong) NSLayoutConstraint *contentViewConstraintTop; +@property(nonatomic, readwrite, strong) NSLayoutConstraint *contentViewConstraintBottom; +@property(nonatomic, readwrite, strong) NSLayoutConstraint *contentViewConstraintLeft; +@property(nonatomic, readwrite, strong) NSLayoutConstraint *contentViewConstraintRight; + // Image constraints @property(nonatomic, readwrite, strong) NSLayoutConstraint *imageViewConstraintLeading; @property(nonatomic, readwrite, strong) NSLayoutConstraint *imageViewConstraintCenterY; @@ -123,6 +131,12 @@ - (void)commonBannerViewInit { self.backgroundColor = UIColor.whiteColor; _bannerViewLayoutStyle = MDCBannerViewLayoutStyleAutomatic; self.layoutMargins = UIEdgeInsetsZero; + self.contentEdgeInsets = UIEdgeInsetsZero; + + UIView *contentView = [[UIView alloc] init]; + contentView.translatesAutoresizingMaskIntoConstraints = NO; + _contentView = contentView; + [self addSubview:contentView]; // Create textView UITextView *textView = [[UITextView alloc] init]; @@ -138,7 +152,7 @@ - (void)commonBannerViewInit { textView.textAlignment = NSTextAlignmentNatural; textView.textContainerInset = UIEdgeInsetsZero; textView.backgroundColor = UIColor.clearColor; - [self addSubview:textView]; + [contentView addSubview:textView]; _textView = textView; // Create imageView @@ -149,13 +163,13 @@ - (void)commonBannerViewInit { imageView.contentMode = UIViewContentModeCenter; imageView.clipsToBounds = YES; imageView.hidden = YES; - [self addSubview:imageView]; + [contentView addSubview:imageView]; _imageView = imageView; // Create a button container to organize buttons UIView *buttonContainerView = [[UIView alloc] init]; buttonContainerView.translatesAutoresizingMaskIntoConstraints = NO; - [self addSubview:buttonContainerView]; + [contentView addSubview:buttonContainerView]; self.buttonContainerView = buttonContainerView; // Create leadingButton and trailingButton @@ -194,6 +208,15 @@ - (UIColor *)dividerColor { return self.divider.backgroundColor; } +- (void)setContentEdgeInsets:(UIEdgeInsets)contentEdgeInsets { + _contentEdgeInsets = contentEdgeInsets; + + self.contentViewConstraintBottom.constant = -contentEdgeInsets.bottom; + self.contentViewConstraintTop.constant = contentEdgeInsets.top; + self.contentViewConstraintLeft.constant = contentEdgeInsets.left; + self.contentViewConstraintRight.constant = -contentEdgeInsets.right; +} + - (CGFloat)mdc_currentElevation { return 0; } @@ -201,6 +224,7 @@ - (CGFloat)mdc_currentElevation { #pragma mark - Constraints Helpers - (void)setupConstraints { + [self setUpContentConstraints]; [self setUpImageViewConstraints]; [self setUpTextViewConstraints]; [self setUpButtonContainerConstraints]; @@ -208,12 +232,27 @@ - (void)setupConstraints { [self setUpDividerConstraints]; } +- (void)setUpContentConstraints { + self.contentViewConstraintLeft = + [self.contentView.leftAnchor constraintEqualToAnchor:self.layoutMarginsGuide.leftAnchor + constant:self.contentEdgeInsets.left]; + self.contentViewConstraintRight = + [self.contentView.rightAnchor constraintEqualToAnchor:self.layoutMarginsGuide.rightAnchor + constant:-self.contentEdgeInsets.right]; + self.contentViewConstraintTop = + [self.contentView.topAnchor constraintEqualToAnchor:self.layoutMarginsGuide.topAnchor + constant:self.contentEdgeInsets.top]; + self.contentViewConstraintBottom = + [self.contentView.bottomAnchor constraintEqualToAnchor:self.layoutMarginsGuide.bottomAnchor + constant:-self.contentEdgeInsets.bottom]; +} + - (void)setUpImageViewConstraints { self.imageViewConstraintLeading = - [self.imageView.leadingAnchor constraintEqualToAnchor:self.layoutMarginsGuide.leadingAnchor + [self.imageView.leadingAnchor constraintEqualToAnchor:self.contentView.leadingAnchor constant:kLeadingPadding]; self.imageViewConstraintTopLarge = - [self.imageView.topAnchor constraintEqualToAnchor:self.layoutMarginsGuide.topAnchor + [self.imageView.topAnchor constraintEqualToAnchor:self.contentView.topAnchor constant:kTopPaddingLarge]; self.imageViewConstraintCenterY = [self.imageView.centerYAnchor constraintEqualToAnchor:self.buttonContainerView.centerYAnchor]; @@ -221,38 +260,38 @@ - (void)setUpImageViewConstraints { - (void)setUpTextViewConstraints { self.textViewConstraintTop = - [self.textView.topAnchor constraintEqualToAnchor:self.layoutMarginsGuide.topAnchor + [self.textView.topAnchor constraintEqualToAnchor:self.contentView.topAnchor constant:kTopPaddingLarge]; self.textViewConstraintCenterY = [self.textView.centerYAnchor constraintEqualToAnchor:self.buttonContainerView.centerYAnchor]; self.textViewConstraintTrailing = - [self.textView.trailingAnchor constraintEqualToAnchor:self.layoutMarginsGuide.trailingAnchor + [self.textView.trailingAnchor constraintEqualToAnchor:self.contentView.trailingAnchor constant:-kTextTrailingPadding]; self.textViewConstraintLeadingWithImage = [self.textView.leadingAnchor constraintEqualToAnchor:self.imageView.trailingAnchor constant:kSpaceBetweenIconImageAndTextView]; self.textViewConstraintLeadingWithMargin = - [self.textView.leadingAnchor constraintEqualToAnchor:self.layoutMarginsGuide.leadingAnchor + [self.textView.leadingAnchor constraintEqualToAnchor:self.contentView.leadingAnchor constant:kLeadingPadding]; } - (void)setUpButtonContainerConstraints { - self.buttonContainerConstraintLeading = [self.buttonContainerView.leadingAnchor - constraintEqualToAnchor:self.layoutMarginsGuide.leadingAnchor - constant:kLeadingPadding]; + self.buttonContainerConstraintLeading = + [self.buttonContainerView.leadingAnchor constraintEqualToAnchor:self.contentView.leadingAnchor + constant:kLeadingPadding]; self.buttonContainerConstraintWidthWithLeadingButton = [self.buttonContainerView.widthAnchor constraintEqualToAnchor:self.leadingButton.widthAnchor]; self.buttonContainerConstraintTrailing = [self.buttonContainerView.trailingAnchor - constraintEqualToAnchor:self.layoutMarginsGuide.trailingAnchor + constraintEqualToAnchor:self.contentView.trailingAnchor constant:-kButtonContainerTrailingPadding]; - self.buttonContainerConstraintBottom = [self.buttonContainerView.bottomAnchor - constraintEqualToAnchor:self.layoutMarginsGuide.bottomAnchor - constant:-kBottomPadding]; + self.buttonContainerConstraintBottom = + [self.buttonContainerView.bottomAnchor constraintEqualToAnchor:self.contentView.bottomAnchor + constant:-kBottomPadding]; self.buttonContainerConstraintLeadingWithTextView = [self.buttonContainerView.leadingAnchor constraintEqualToAnchor:self.textView.trailingAnchor constant:kHorizontalSpaceBetweenTextViewAndButton]; self.buttonContainerConstraintTopWithMargin = - [self.buttonContainerView.topAnchor constraintEqualToAnchor:self.layoutMarginsGuide.topAnchor + [self.buttonContainerView.topAnchor constraintEqualToAnchor:self.contentView.topAnchor constant:kTopPaddingSmall]; self.buttonContainerConstraintTopWithImageViewGreater = [self.buttonContainerView.topAnchor constraintGreaterThanOrEqualToAnchor:self.imageView.bottomAnchor @@ -315,6 +354,10 @@ - (void)setUpDividerConstraints { } - (void)deactivateAllConstraints { + self.contentViewConstraintBottom.active = NO; + self.contentViewConstraintTop.active = NO; + self.contentViewConstraintLeft.active = NO; + self.contentViewConstraintRight.active = NO; self.imageViewConstraintLeading.active = NO; self.imageViewConstraintTopLarge.active = NO; self.imageViewConstraintCenterY.active = NO; @@ -361,7 +404,7 @@ - (void)setFrame:(CGRect)frame { - (CGSize)sizeThatFits:(CGSize)size { MDCBannerViewLayoutStyle layoutStyle = [self layoutStyleForSizeToFit:size]; - CGFloat frameHeight = self.layoutMargins.top + self.layoutMargins.bottom; + CGFloat frameHeight = self.contentEdgeInsets.top + self.contentEdgeInsets.bottom; CGSize contentSize = [self contentSizeForLayoutSize:size]; switch (layoutStyle) { case MDCBannerViewLayoutStyleSingleRow: { @@ -444,6 +487,10 @@ - (void)layoutSubviews { - (void)updateConstraintsWithLayoutStyle:(MDCBannerViewLayoutStyle)layoutStyle { [self deactivateAllConstraints]; + self.contentViewConstraintBottom.active = YES; + self.contentViewConstraintTop.active = YES; + self.contentViewConstraintLeft.active = YES; + self.contentViewConstraintRight.active = YES; self.imageViewConstraintLeading.active = YES; if (!self.imageView.hidden) { @@ -561,6 +608,7 @@ - (CGFloat)getFrameHeightOfImageViewAndTextViewWithSizeToFit:(CGSize)sizeToFit { - (CGSize)contentSizeForLayoutSize:(CGSize)layoutSize { CGFloat remainingWidth = layoutSize.width; CGFloat marginsPadding = self.layoutMargins.left + self.layoutMargins.right; + marginsPadding += self.contentEdgeInsets.left + self.contentEdgeInsets.right; remainingWidth -= marginsPadding; remainingWidth -= (kLeadingPadding + kButtonContainerTrailingPadding); return CGSizeMake(remainingWidth, layoutSize.height); diff --git a/components/Banner/tests/snapshot/MDCBannerSnapshotTests.m b/components/Banner/tests/snapshot/MDCBannerSnapshotTests.m index 1ddff3f17d6..18dbd8f9d97 100644 --- a/components/Banner/tests/snapshot/MDCBannerSnapshotTests.m +++ b/components/Banner/tests/snapshot/MDCBannerSnapshotTests.m @@ -572,4 +572,62 @@ - (void)testPreferredFontForAXSContentSizeCategory { andVerifyForView:self.bannerView]; } } + +#pragma mark - contentEdgeInsets + +- (void)testBannerWithContentEdgeInsets { + // When + self.bannerView.textView.text = kBannerLongText; + MDCButton *button1 = self.bannerView.leadingButton; + [button1 setTitle:@"Action1" forState:UIControlStateNormal]; + [button1 setTitleColor:UIColor.blackColor forState:UIControlStateNormal]; + button1.uppercaseTitle = YES; + MDCButton *button2 = self.bannerView.trailingButton; + [button2 setTitle:@"Action2" forState:UIControlStateNormal]; + [button2 setTitleColor:UIColor.blackColor forState:UIControlStateNormal]; + button2.uppercaseTitle = YES; + self.bannerView.imageView.hidden = YES; + self.bannerView.contentEdgeInsets = UIEdgeInsetsMake(20, 20, 20, 20); + + // Then + [self generateSnapshotAndVerifyForView:self.bannerView]; +} + +- (void)testBannerWithContentEdgeInsetsLTR { + // When + self.bannerView.textView.text = kBannerLongText; + MDCButton *button1 = self.bannerView.leadingButton; + [button1 setTitle:@"Action1" forState:UIControlStateNormal]; + [button1 setTitleColor:UIColor.blackColor forState:UIControlStateNormal]; + button1.uppercaseTitle = YES; + MDCButton *button2 = self.bannerView.trailingButton; + [button2 setTitle:@"Action2" forState:UIControlStateNormal]; + [button2 setTitleColor:UIColor.blackColor forState:UIControlStateNormal]; + button2.uppercaseTitle = YES; + self.bannerView.imageView.hidden = YES; + self.bannerView.contentEdgeInsets = UIEdgeInsetsMake(20, 10, 20, 50); + + // Then + [self generateSnapshotAndVerifyForView:self.bannerView]; +} + +- (void)testBannerWithContentEdgeInsetsRTL { + // When + self.bannerView.textView.text = kBannerLongText; + MDCButton *button1 = self.bannerView.leadingButton; + [button1 setTitle:@"Action1" forState:UIControlStateNormal]; + [button1 setTitleColor:UIColor.blackColor forState:UIControlStateNormal]; + button1.uppercaseTitle = YES; + MDCButton *button2 = self.bannerView.trailingButton; + [button2 setTitle:@"Action2" forState:UIControlStateNormal]; + [button2 setTitleColor:UIColor.blackColor forState:UIControlStateNormal]; + button2.uppercaseTitle = YES; + self.bannerView.imageView.hidden = YES; + self.bannerView.contentEdgeInsets = UIEdgeInsetsMake(20, 10, 20, 50); + [self changeViewToRTL:self.bannerView]; + + // Then + [self generateSnapshotAndVerifyForView:self.bannerView]; +} + @end diff --git a/components/BottomAppBar/README.md b/components/BottomAppBar/README.md index 132ace98bf0..5d0aaf896f5 100644 --- a/components/BottomAppBar/README.md +++ b/components/BottomAppBar/README.md @@ -121,10 +121,6 @@ trailingButton.accessibilityHint = @"Purchase the item"; ``` -## Types - -There is only one type of bottom app bar. - ## Bottom app bar Bottom app bars group primary and secondary actions at the bottom of the screen, where they are easily reachable by the user's thumb. diff --git a/components/BottomNavigation/README.md b/components/BottomNavigation/README.md index 1e5a6d0965f..9f262f883e2 100644 --- a/components/BottomNavigation/README.md +++ b/components/BottomNavigation/README.md @@ -135,10 +135,6 @@ override func viewWillLayoutSubviews() { ``` -## Types - -There is only one type of bottom navigation bar. - ## Bottom navigation bar ![A bottom navigation item with home, mail, favorites, reader, and birthday sections](docs/assets/bottom-nav-example.png) diff --git a/components/Buttons/docs/buttons.md b/components/Buttons/docs/buttons.md index 2ed4efc9ffc..7c65002393b 100644 --- a/components/Buttons/docs/buttons.md +++ b/components/Buttons/docs/buttons.md @@ -20,7 +20,6 @@ api_doc_root: true * [Text button](#text-button) * [Outlined button](#outlined-button) * [Contained button](#contained-button) -* [Toggle button](#toggle-button) * [Theming](#theming) - - - @@ -163,12 +162,7 @@ this day." ## Types -There are four types of buttons: - -1. [Text button](#text-button) -2. [Outlined button](#outlined-button) -3. [Contained button](#contained-button) -4. [Toggle button](#toggle-button) (*not supported in iOS*) +There are four types of buttons: 1. [Text button](#text-button) 2. [Outlined button](#outlined-button) 3. [Contained button](#contained-button) 4. Toggle button (*not supported in iOS*)" ![Example of the four button types](assets/buttons_types.png) @@ -186,16 +180,16 @@ All Material buttons are implemented by `MDCButton`, a subclass of [`UIButton`]( To use a text button use the text button theming method on the `MDCButton` theming extension. For more information on theming extensions see the [Theming section](#theming). -#### Objective-C -```objc -[self.button applyTextThemeWithScheme:self.containerScheme]; -``` - #### Swift ```swift button.applyTextTheme(withScheme: containerScheme) ``` + +#### Objective-C +```objc +[self.button applyTextThemeWithScheme:self.containerScheme]; +``` ### Anatomy and key properties @@ -243,16 +237,15 @@ A text button has a text label, a transparent container and an optional icon. To achieve an outlined button use the outlined button theming method on the `MDCButton` theming extension. To access the theming extension see the [Theming section](#theming). -#### Objective-C -```objc -[self.button applyOutlinedThemeWithScheme:self.containerScheme]; -``` - #### Swift - ```swift button.applyOutlinedTheme(withScheme: containerScheme) ``` + +#### Objective-C +```objc +[self.button applyOutlinedThemeWithScheme:self.containerScheme]; +``` ### Anatomy and Key properties @@ -300,16 +293,15 @@ An outlined button has a text label, a container, and an optional icon. Contained buttons are implemented by `MDCButton`. 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 -```objc -[self.button applyContainedThemeWithScheme:self.containerScheme]; -``` - #### Swift - ```swift button.applyContainedTheme(withScheme: containerScheme) ``` + +#### Objective-C +```objc +[self.button applyContainedThemeWithScheme:self.containerScheme]; +``` ### Anatomy and Key properties @@ -346,10 +338,6 @@ A contained button has a text label, a container, and an optional icon. **Icon** | `imageView` | `setImage:forState:`
`imageForState:` | `nil` **Color** | `imageView.tintColor` | `setImageViewTintColor:forState:`
`imageViewTintColorForState:` | `nil` -## Toggle button - -[Toggle buttons](https://material.io/components/buttons/#toggle-button) can be used to select from a group of choices. They are not supported on iOS. - ## Theming You can theme an `MDCButton` to match any of the Material Button styles using theming diff --git a/components/Buttons/docs/fabs.md b/components/Buttons/docs/fabs.md index 499671b22c2..7829a0f8e2d 100644 --- a/components/Buttons/docs/fabs.md +++ b/components/Buttons/docs/fabs.md @@ -142,11 +142,7 @@ this day." ## Types -There are three types of FABs: - -1. [Regular FABs](#regular-fabs) -2. [Mini FABs](#mini-fabs) -3. [Extended FABs](#extended-fabs) +There are three types of FABs: 1\. [Regular FABs](#regular-fab) 2\. [Mini FABs](#mini-fab) 3\. [Extended FABs](#extended-fab) ![Three FABs, one of each type.](assets/fab-types.png) @@ -218,16 +214,16 @@ To create a mini FAB use the `+floatingButtonWithShape:` constructor with a valu For more information on theming FABs see the [Theming section](#theming). +#### Swift +```swift +let fab = MDCFloatingButton(shape: mini) +``` + #### Objective-C ```objc MDCFloatingButton *fab = [MDCFloatingButton floatingButtonWithShape:MDCFloatingButtonShapeMini]; ``` - -#### Swift -```swift -let fab = MDCFloatingButton(shape: mini) -``` ### Anatomy and key properties @@ -269,18 +265,18 @@ To create an extended FAB use the `+floatingButtonWithShape:` constructor with a For more information on theming FABs see the [Theming section](#theming). +#### Swift +```swift +let fab = MDCFloatingButton(shape: .default) +fab.mode = .expanded +``` + #### Objective-C ```objc MDCFloatingButton *fab = [MDCFloatingButton floatingButtonWithShape:MDCFloatingButtonShapeDefault]; fab.mode = MDCFloatingButtonModeExpanded; ``` - -#### Swift -```swift -let fab = MDCFloatingButton(shape: .default) -fab.mode = .expanded -``` ### Anatomy and key properties diff --git a/components/Buttons/src/MDCButton.m b/components/Buttons/src/MDCButton.m index 296b421c7c6..d263d5205cd 100644 --- a/components/Buttons/src/MDCButton.m +++ b/components/Buttons/src/MDCButton.m @@ -262,6 +262,7 @@ - (void)commonMDCButtonInit { } #ifdef __IPHONE_13_4 +#if !TARGET_OS_TV if (@available(iOS 13.4, *)) { if ([self respondsToSelector:@selector(pointerStyleProvider)]) { __weak __typeof__(self) weakSelf = self; @@ -282,7 +283,8 @@ - (void)commonMDCButtonInit { self.pointerInteractionEnabled = NO; } } -#endif +#endif // !TARGET_OS_TV +#endif // __IPHONE_13_4 } - (void)dealloc { diff --git a/components/Buttons/src/MDCFloatingButton+Animation.m b/components/Buttons/src/MDCFloatingButton+Animation.m index a680315473e..0aedcdf59e9 100644 --- a/components/Buttons/src/MDCFloatingButton+Animation.m +++ b/components/Buttons/src/MDCFloatingButton+Animation.m @@ -95,6 +95,7 @@ - (void)expand:(BOOL)animated completion:(void (^_Nullable)(void))completion { // Because of this, we instead temporarily disable pointer interaction for the button while it // animates and reenable (if previously enabled) once the animation has ended. #ifdef __IPHONE_13_4 +#if !TARGET_OS_TV BOOL wasPointerInteractionEnabled = NO; if (@available(iOS 13.4, *)) { if ([self respondsToSelector:@selector(isPointerInteractionEnabled)]) { @@ -102,7 +103,8 @@ - (void)expand:(BOOL)animated completion:(void (^_Nullable)(void))completion { self.pointerInteractionEnabled = NO; } } -#endif +#endif // !TARGET_OS_TV +#endif // __IPHONE_13_4 void (^expandActions)(void) = ^{ self.layer.transform = CATransform3DConcat(self.layer.transform, [MDCFloatingButton expandTransform]); @@ -113,12 +115,14 @@ - (void)expand:(BOOL)animated completion:(void (^_Nullable)(void))completion { [self.layer removeAnimationForKey:kMDCFloatingButtonOpacityKey]; [self.imageView.layer removeAnimationForKey:kMDCFloatingButtonTransformKey]; #ifdef __IPHONE_13_4 +#if !TARGET_OS_TV if (@available(iOS 13.4, *)) { if ([self respondsToSelector:@selector(isPointerInteractionEnabled)]) { self.pointerInteractionEnabled = wasPointerInteractionEnabled; } } -#endif +#endif // !TARGET_OS_TV +#endif // __IPHONE_13_4 if (completion) { completion(); } @@ -191,6 +195,7 @@ - (void)collapse:(BOOL)animated completion:(void (^_Nullable)(void))completion { // Because of this, we instead temporarily disable pointer interaction for the button while it // animates and reenable (if previously enabled) once the animation has ended. #ifdef __IPHONE_13_4 +#if !TARGET_OS_TV BOOL wasPointerInteractionEnabled = NO; if (@available(iOS 13.4, *)) { if ([self respondsToSelector:@selector(isPointerInteractionEnabled)]) { @@ -198,7 +203,8 @@ - (void)collapse:(BOOL)animated completion:(void (^_Nullable)(void))completion { self.pointerInteractionEnabled = NO; } } -#endif +#endif // !TARGET_OS_TV +#endif // __IPHONE_13_4 void (^collapseActions)(void) = ^{ self.layer.transform = @@ -210,12 +216,14 @@ - (void)collapse:(BOOL)animated completion:(void (^_Nullable)(void))completion { [self.layer removeAnimationForKey:kMDCFloatingButtonOpacityKey]; [self.imageView.layer removeAnimationForKey:kMDCFloatingButtonTransformKey]; #ifdef __IPHONE_13_4 +#if !TARGET_OS_TV if (@available(iOS 13.4, *)) { if ([self respondsToSelector:@selector(isPointerInteractionEnabled)]) { self.pointerInteractionEnabled = wasPointerInteractionEnabled; } } -#endif +#endif // !TARGET_OS_TV +#endif // __IPHONE_13_4 if (completion) { completion(); } diff --git a/components/Cards/README.md b/components/Cards/README.md index e6239dd7d0d..bf65a612160 100644 --- a/components/Cards/README.md +++ b/components/Cards/README.md @@ -175,8 +175,6 @@ card.accessibilityLabel = [NSString ``` -## Types - ## Card ![Card with sample image and buttons](docs/assets/custom-card.png) @@ -299,7 +297,7 @@ _**Note:** All the optional elements of a card's content are implemented through **Elevation** | N/A | `-setShadowElevation:forState:`
`-shadowElevationForState:` | 1 **Ripple color** | `rippleView.rippleColor` | N/A | `nil` -### Theming +## Theming Cards supports Material Theming using a Container Scheme. `MDCCard` and `MDCCardCollectionCell` have both default and outlined theming methods. Learn more about theming extensions [here](../../docs/theming.md). Below is a screenshot of an `MDCCard` with the Material Design Shrine theme: diff --git a/components/Cards/src/Theming/MDCCard+MaterialTheming.m b/components/Cards/src/Theming/MDCCard+MaterialTheming.m index 0f2d78825eb..d95864f6c58 100644 --- a/components/Cards/src/Theming/MDCCard+MaterialTheming.m +++ b/components/Cards/src/Theming/MDCCard+MaterialTheming.m @@ -14,6 +14,8 @@ #import "MDCCard+MaterialTheming.h" +#import "MaterialCards.h" + static const MDCShadowElevation kNormalElevation = 1; static const MDCShadowElevation kHighlightedElevation = 1; static const CGFloat kBorderWidth = 1; @@ -35,7 +37,7 @@ - (void)applyThemeWithScheme:(id)scheme { if (shapeScheme) { [self applyThemeWithShapeScheme:shapeScheme]; } else { - self.layer.cornerRadius = (CGFloat)4; + self.layer.cornerRadius = 4; } [self setShadowElevation:kNormalElevation forState:UIControlStateNormal]; @@ -66,7 +68,7 @@ - (void)applyOutlinedThemeWithScheme:(nonnull id)scheme { if (shapeScheme) { [self applyThemeWithShapeScheme:shapeScheme]; } else { - self.layer.cornerRadius = (CGFloat)4; + self.layer.cornerRadius = 4; } NSUInteger maximumStateValue = UIControlStateNormal | UIControlStateSelected | diff --git a/components/Dialogs/README.md b/components/Dialogs/README.md index f2f6523fe10..229ea068ffb 100644 --- a/components/Dialogs/README.md +++ b/components/Dialogs/README.md @@ -86,20 +86,11 @@ the `-performAccessibilityEscape` method in your custom dialog view controller c } ``` -## Types - -There are four types of dialogs: - -1. [Alert](#alert-dialog) -1. Simple -1. Confirmation -1. Full-screen - -![Examples of the four types of dialogs.](docs/assets/dialogs-types.png) - ## Alert dialog -The alert style is the only style supported on iOS. Consider using the [ActionSheet](https://github.com/material-components/material-components-ios/blob/develop/components/ActionSheet/README.md) component in situations where one of the unsupported dialog types would have been appropriate. +![Example alert dialog with lorem ipsum sample text.](docs/assets/alert-example.png) + +Alerts are the only type of dialog supported on iOS. Consider using the [ActionSheet](https://github.com/material-components/material-components-ios/blob/develop/components/ActionSheet/README.md) component in situations where one of the unsupported dialog types would have been appropriate. To present either an `MDCAlertController` or a custom dialog view controller, set its `modalPresentationStyle` property to `UIModalPresentationCustom` and its `transitioningDelegate` property to an diff --git a/components/Dialogs/docs/assets/alert-example.png b/components/Dialogs/docs/assets/alert-example.png new file mode 100644 index 00000000000..b41ecefa4b7 Binary files /dev/null and b/components/Dialogs/docs/assets/alert-example.png differ diff --git a/components/LibraryInfo/src/MDCLibraryInfo.m b/components/LibraryInfo/src/MDCLibraryInfo.m index 2c1afb58aff..179c27ac002 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 = @"119.3.0"; +static NSString* const kMDCLibraryInfoVersionString = @"119.4.0"; @implementation MDCLibraryInfo diff --git a/components/LibraryInfo/tests/unit/LibraryInfoTests.m b/components/LibraryInfo/tests/unit/LibraryInfoTests.m index 75e451b9fcd..af47b1c34af 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: "119.3.0", etc. + // Accept: "119.4.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/components/List/README.md b/components/List/README.md index dfe5dc252c5..4739ee207b5 100644 --- a/components/List/README.md +++ b/components/List/README.md @@ -173,19 +173,6 @@ Single-line list items contain a maximum of one line of text. ![Image of three single-line list items with sample text](docs/assets/single-line-list-example.png) -#### Objective-C - -```objc -- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView - cellForItemAtIndexPath:(NSIndexPath *)indexPath { - MDCSelfSizingStereoCell *cell = - (MDCSelfSizingStereoCell *)[collectionView dequeueReusableCellWithReuseIdentifier:kSelfSizingStereoCellIdentifier - forIndexPath:indexPath]; - cell.titleLabel.text = @"This is a single-line list"; - return cell; -} -``` - #### Swift ```swift @@ -200,31 +187,30 @@ func collectionView(_ collectionView: UICollectionView, return cell } ``` - - -### Two-line list - -Two-line list items contain a maximum of two lines of text. - -### Two-line list example - -![Image of three two-line list items with sample text](docs/assets/two-line-list-example.png) - #### Objective-C ```objc - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath { MDCSelfSizingStereoCell *cell = - [collectionView dequeueReusableCellWithReuseIdentifier:kSelfSizingStereoCellIdentifier + (MDCSelfSizingStereoCell *)[collectionView dequeueReusableCellWithReuseIdentifier:kSelfSizingStereoCellIdentifier forIndexPath:indexPath]; - cell.titleLabel.text = @"This is a two-line list"; - cell.detailLabel.text = @"This is secondary text that occupies one line."; + cell.titleLabel.text = @"This is a single-line list"; return cell; } ``` + + +### Two-line list + +Two-line list items contain a maximum of two lines of text. +### Two-line list example + +![Image of three two-line list items with sample text](docs/assets/two-line-list-example.png) + + #### Swift ```swift @@ -240,17 +226,7 @@ func collectionView(_ collectionView: UICollectionView, return cell } ``` - - -### Three-line list - -Three-line list items contains a maximum of three lines of text. -### Three-line list example - -![Image of three three-line list items with sample text](docs/assets/three-line-list-example.png) - - #### Objective-C ```objc @@ -259,12 +235,22 @@ Three-line list items contains a maximum of three lines of text. MDCSelfSizingStereoCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:kSelfSizingStereoCellIdentifier forIndexPath:indexPath]; - cell.titleLabel.text = @"This is a three-line list"; - cell.detailLabel.text = @"This is secondary text\nthat occupies two lines."; + cell.titleLabel.text = @"This is a two-line list"; + cell.detailLabel.text = @"This is secondary text that occupies one line."; return cell; } ``` + + +### Three-line list + +Three-line list items contains a maximum of three lines of text. +### Three-line list example + +![Image of three three-line list items with sample text](docs/assets/three-line-list-example.png) + + #### Swift ```swift @@ -280,6 +266,20 @@ func collectionView(_ collectionView: UICollectionView, return cell } ``` + +#### Objective-C + +```objc +- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView + cellForItemAtIndexPath:(NSIndexPath *)indexPath { + MDCSelfSizingStereoCell *cell = + [collectionView dequeueReusableCellWithReuseIdentifier:kSelfSizingStereoCellIdentifier + forIndexPath:indexPath]; + cell.titleLabel.text = @"This is a three-line list"; + cell.detailLabel.text = @"This is secondary text\nthat occupies two lines."; + return cell; +} +``` ## Theming @@ -375,14 +375,6 @@ to take: 1. Initialize `MDCInkView` on init and add it as a subview: - #### Objective-C - - ```objc - _inkView = [[MDCInkView alloc] initWithFrame:self.bounds]; - _inkView.usesLegacyInkRipple = NO; - [self addSubview:_inkView]; - ``` - #### Swift ```swift @@ -390,6 +382,14 @@ to take: inkView.usesLegacyInkRipple = false addSubview(inkView) ``` + + #### Objective-C + + ```objc + _inkView = [[MDCInkView alloc] initWithFrame:self.bounds]; + _inkView.usesLegacyInkRipple = NO; + [self addSubview:_inkView]; + ``` 1. Initialize a `CGPoint` property in your cell (`CGPoint _lastTouch;`) to @@ -400,6 +400,16 @@ and save where the touches were so we can then start the ripple animation from that point: + #### Swift + + ```swift + override func touchesBegan(_ touches: Set, with event: UIEvent?) { + let touch = touches.first + let location = touch?.location(in: self) + lastTouch = location + } + ``` + #### Objective-C ```objc @@ -411,35 +421,12 @@ that point: [super touchesBegan:touches withEvent:event]; } ``` - - #### Swift - - ```swift - override func touchesBegan(_ touches: Set, with event: UIEvent?) { - let touch = touches.first - let location = touch?.location(in: self) - lastTouch = location - } - ``` 1. Override the `setHighlighted` method for your cell and apply the start and stop ripple animations: - #### Objective-C - - ```objc - - (void)setHighlighted:(BOOL)highlighted { - [super setHighlighted:highlighted]; - if (highlighted) { - [_inkView startTouchBeganAnimationAtPoint:_lastTouch completion:nil]; - } else { - [_inkView startTouchEndedAnimationAtPoint:_lastTouch completion:nil]; - } - } - ``` - #### Swift ```swift @@ -455,21 +442,25 @@ stop ripple animations: // get... } ``` - - -1. When the cell is reused we must make sure no outstanding ripple animations -stay on the cell so we need to clear the ink before: - #### Objective-C ```objc - - (void)prepareForReuse { - [_inkView cancelAllAnimationsAnimated:NO]; - [super prepareForReuse]; + - (void)setHighlighted:(BOOL)highlighted { + [super setHighlighted:highlighted]; + if (highlighted) { + [_inkView startTouchBeganAnimationAtPoint:_lastTouch completion:nil]; + } else { + [_inkView startTouchEndedAnimationAtPoint:_lastTouch completion:nil]; + } } ``` + +1. When the cell is reused we must make sure no outstanding ripple animations +stay on the cell so we need to clear the ink before: + + #### Swift ```swift @@ -478,6 +469,15 @@ stay on the cell so we need to clear the ink before: super.prepareForReuse() } ``` + + #### Objective-C + + ```objc + - (void)prepareForReuse { + [_inkView cancelAllAnimationsAnimated:NO]; + [super prepareForReuse]; + } + ``` Now there is ink in our cells! @@ -497,6 +497,12 @@ and their superview (the cell's `contentView`). constraints that are set up to be accessible throughout the file: + #### Swift + ```swift + var imageLeftPaddingConstraint: NSLayoutConstraint + var imageRightPaddingConstraint: NSLayoutConstraint + var imageWidthConstraint: NSLayoutConstraint + ``` #### Objective-C ```objc @@ -504,13 +510,6 @@ and their superview (the cell's `contentView`). NSLayoutConstraint *_imageRightPaddingConstraint; NSLayoutConstraint *_imageWidthConstraint; ``` - - #### Swift - ```swift - var imageLeftPaddingConstraint: NSLayoutConstraint - var imageRightPaddingConstraint: NSLayoutConstraint - var imageWidthConstraint: NSLayoutConstraint - ``` This is in order to support the changing layout if an image is set or not. @@ -521,6 +520,13 @@ when the cell is set up. For that we expose a `setCellWidth` method that sets the width constraint of the `contentView`: + #### Swift + ```swift + func set(cellWidth: CGFloat) { + cellWidthConstraint.constant = cellWidth + cellWidthConstraint.isActive = true + } + ``` #### Objective-C ```objc @@ -529,20 +535,20 @@ the width constraint of the `contentView`: _cellWidthConstraint.active = YES; } ``` - - #### Swift - ```swift - func set(cellWidth: CGFloat) { - cellWidthConstraint.constant = cellWidth - cellWidthConstraint.isActive = true - } - ``` and then in the collection view's `cellForItemAtIndexPath` delegate method we set the width: + #### Swift + ```swift + var cellWidth = collectionView.bounds.width + if #available(iOS 11.0, *) { + cellWidth -= collectionView.adjustedContentInset.left + collectionView.adjustedContentInset.right + } + set(cellWidth: cellWidth) + ``` #### Objective-C ```objc @@ -553,15 +559,6 @@ the width constraint of the `contentView`: } [cell setCellWidth:cellWidth]; ``` - - #### Swift - ```swift - var cellWidth = collectionView.bounds.width - if #available(iOS 11.0, *) { - cellWidth -= collectionView.adjustedContentInset.left + collectionView.adjustedContentInset.right - } - set(cellWidth: cellWidth) - ``` 1. In our collection view's flow layout we must set an `estimatedItemSize` so @@ -571,17 +568,16 @@ the collection view will defer the size calculations to its content. might break in runtime. - - #### Objective-C - ```objc - _flowLayout.estimatedItemSize = CGSizeMake(kSmallArbitraryCellWidth, kSmallestCellHeight); - ``` - #### Swift ```swift flowLayout.estimatedItemSize = CGSize(width: kSmallArbitraryCellWidth, height: kSmallestCellHeight) ``` + + #### Objective-C + ```objc + _flowLayout.estimatedItemSize = CGSizeMake(kSmallArbitraryCellWidth, kSmallestCellHeight); + ``` ### Typography @@ -601,6 +597,17 @@ support it in our cells we need to follow these steps: set/update methods: + #### Swift + ```swift + func updateTitleFont() { + if (_titleFont == nil) { + _titleFont = defaultTitleFont + } + _titleLabel.font = + _titleFont.mdc_fontSized(forMaterialTextStyle: .subheadline, + scaledForDynamicType: mdc_adjustsFontForContentSizeCategory) + } + ``` #### Objective-C ```objc @@ -614,18 +621,6 @@ set/update methods: [self setNeedsLayout]; } ``` - - #### Swift - ```swift - func updateTitleFont() { - if (_titleFont == nil) { - _titleFont = defaultTitleFont - } - _titleLabel.font = - _titleFont.mdc_fontSized(forMaterialTextStyle: .subheadline, - scaledForDynamicType: mdc_adjustsFontForContentSizeCategory) - } - ``` 1. Add an observer in the cell to check for the @@ -633,6 +628,13 @@ set/update methods: text size has been changed. + #### Swift + ```swift + NotificationCenter.default.addObserver(self, + selector: #selector(contentSizeCategoryDidChange(notification:)), + name: UIContentSizeCategory.didChangeNotification, + object: nil) + ``` #### Objective-C ```objc @@ -642,20 +644,19 @@ text size has been changed. name:UIContentSizeCategoryDidChangeNotification object:nil]; ``` - - #### Swift - ```swift - NotificationCenter.default.addObserver(self, - selector: #selector(contentSizeCategoryDidChange(notification:)), - name: UIContentSizeCategory.didChangeNotification, - object: nil) - ``` In the selector update the font sizes to reflect the change: + #### Swift + ```swift + func contentSizeCategoryDidChange(_: NSNotification) { + updateTitleFont() + updateDetailsFont() + } + ``` #### Objective-C ```objc @@ -664,20 +665,18 @@ text size has been changed. [self updateDetailsFont]; } ``` - - #### Swift - ```swift - func contentSizeCategoryDidChange(_: NSNotification) { - updateTitleFont() - updateDetailsFont() - } - ``` 1. Add an observer also in the `UIViewController` so we can reload the collection view once there is a change: + #### Swift + ```swift + func contentSizeCategoryDidChange(_: NSNotification) { + collectionView.reloadData() + } + ``` #### Objective-C ```objc @@ -685,13 +684,6 @@ text size has been changed. [self.collectionView reloadData]; } ``` - - #### Swift - ```swift - func contentSizeCategoryDidChange(_: NSNotification) { - collectionView.reloadData() - } - ``` ### iPhone X safe area support @@ -701,6 +693,12 @@ text size has been changed. aware of the safe area: + #### Swift + ```swift + if #available(iOS 11.0, *) { + collectionView.contentInsetAdjustmentBehavior = .always + } + ``` #### Objective-C ```objc @@ -710,13 +708,6 @@ text size has been changed. } #endif ``` - - #### Swift - ```swift - if #available(iOS 11.0, *) { - collectionView.contentInsetAdjustmentBehavior = .always - } - ``` Lastly, as seen in the self-sizing section on step 2, when setting the width @@ -730,6 +721,22 @@ text size has been changed. code changes to achieve that: + #### Swift + ```swift + override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { + super.traitCollectionDidChange(previousTraitCollection) + self.collectionView.collectionViewLayout.invalidateLayout() + self.collectionView.reloadData() + } + + override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) { + super.viewWillTransition(to: size, with: coordinator) + self.collectionView.collectionViewLayout.invalidateLayout() + coordinator.animate(alongsideTransition: nil) { (_) in + self.collectionView.collectionViewLayout.invalidateLayout() + } + } + ``` #### Objective-C ```objc @@ -750,23 +757,6 @@ text size has been changed. }]; } ``` - - #### Swift - ```swift - override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { - super.traitCollectionDidChange(previousTraitCollection) - self.collectionView.collectionViewLayout.invalidateLayout() - self.collectionView.reloadData() - } - - override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) { - super.viewWillTransition(to: size, with: coordinator) - self.collectionView.collectionViewLayout.invalidateLayout() - coordinator.animate(alongsideTransition: nil) { (_) in - self.collectionView.collectionViewLayout.invalidateLayout() - } - } - ``` ### Right to left text support @@ -774,31 +764,29 @@ text size has been changed. To support right to left text we need to import `MDFInternationalization`: +#### Swift +```swift +import MDFInternationalization +``` #### Objective-C ```objc #import ``` - -#### Swift -```swift -import MDFInternationalization -``` and for each of our cell's subviews me need to update the `autoResizingMask`: +#### Swift +```swift +_titleLabel.autoresizingMask = + MDFTrailingMarginAutoresizingMaskForLayoutDirection(mdf_effectiveUserInterfaceLayoutDirection) +``` #### Objective-C ```objc _titleLabel.autoresizingMask = MDFTrailingMarginAutoresizingMaskForLayoutDirection(self.mdf_effectiveUserInterfaceLayoutDirection); ``` - -#### Swift -```swift -_titleLabel.autoresizingMask = - MDFTrailingMarginAutoresizingMaskForLayoutDirection(mdf_effectiveUserInterfaceLayoutDirection) -``` diff --git a/components/List/src/MDCSelfSizingStereoCell.h b/components/List/src/MDCSelfSizingStereoCell.h index 1e1cda02df9..0c3ea99d1e7 100644 --- a/components/List/src/MDCSelfSizingStereoCell.h +++ b/components/List/src/MDCSelfSizingStereoCell.h @@ -15,6 +15,7 @@ #import #import "MDCBaseCell.h" +#import "MDCSelfSizingStereoCellImageViewVerticalPosition.h" /** MDCSelfSizingStereoCell is intended to be an easy to use readymade implementation of a basic @@ -42,11 +43,23 @@ __attribute__((objc_subclassing_restricted)) @interface MDCSelfSizingStereoCell */ @property(nonatomic, strong, readonly) UIImageView *leadingImageView; +/** + This property influences the positioning of @c leadingImageView. The default value is @c .top. + */ +@property(nonatomic, assign) + MDCSelfSizingStereoCellImageViewVerticalPosition leadingImageViewVerticalPosition; + /** The UIImageView responsible for displaying the trailing image. */ @property(nonatomic, strong, readonly) UIImageView *trailingImageView; +/** + This property influences the positioning of @c trailingImageView. The default value is @c .top. + */ +@property(nonatomic, assign) + MDCSelfSizingStereoCellImageViewVerticalPosition trailingImageViewVerticalPosition; + /** The UILabel responsible for displaying the title text. By default, `numberOfLines` is set to 0 so the label wraps and the self-sizing capabilities of the cell are best utilized. diff --git a/components/List/src/MDCSelfSizingStereoCell.m b/components/List/src/MDCSelfSizingStereoCell.m index 8082447a282..ceff848fa11 100644 --- a/components/List/src/MDCSelfSizingStereoCell.m +++ b/components/List/src/MDCSelfSizingStereoCell.m @@ -172,12 +172,15 @@ - (MDCSelfSizingStereoCellLayout *)layoutForCellWidth:(CGFloat)cellWidth { CGFloat flooredCellWidth = floor(cellWidth); MDCSelfSizingStereoCellLayout *layout = self.cachedLayouts[@(flooredCellWidth)]; if (!layout) { - layout = [[MDCSelfSizingStereoCellLayout alloc] initWithLeadingImageView:self.leadingImageView - trailingImageView:self.trailingImageView - textContainer:self.textContainer - titleLabel:self.titleLabel - detailLabel:self.detailLabel - cellWidth:flooredCellWidth]; + layout = [[MDCSelfSizingStereoCellLayout alloc] + initWithLeadingImageView:self.leadingImageView + leadingImageViewVerticalPosition:self.leadingImageViewVerticalPosition + trailingImageView:self.trailingImageView + trailingImageViewVerticalPosition:self.trailingImageViewVerticalPosition + textContainer:self.textContainer + titleLabel:self.titleLabel + detailLabel:self.detailLabel + cellWidth:flooredCellWidth]; self.cachedLayouts[@(flooredCellWidth)] = layout; } return layout; diff --git a/components/List/src/MDCSelfSizingStereoCellImageViewVerticalPosition.h b/components/List/src/MDCSelfSizingStereoCellImageViewVerticalPosition.h new file mode 100644 index 00000000000..2b1bea5862f --- /dev/null +++ b/components/List/src/MDCSelfSizingStereoCellImageViewVerticalPosition.h @@ -0,0 +1,31 @@ +// 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 + +/** + This enum represents different positions for the side image views in an @c + MDCSelfSizingStereoCell. + */ +typedef NS_ENUM(NSUInteger, MDCSelfSizingStereoCellImageViewVerticalPosition) { + /** + This value results in the image view being positioned at the top of the cell. + */ + MDCSelfSizingStereoCellImageViewVerticalPositionTop, + /** + This value results in the image view being positioned in the vertical center of the + cell. + */ + MDCSelfSizingStereoCellImageViewVerticalPositionCenter, +}; diff --git a/components/List/src/MaterialList.h b/components/List/src/MaterialList.h index a2bc924868d..25807cf8da4 100644 --- a/components/List/src/MaterialList.h +++ b/components/List/src/MaterialList.h @@ -15,3 +15,4 @@ #import "MDCBaseCell.h" #import "MDCSelfSizingLayoutAttributes.h" #import "MDCSelfSizingStereoCell.h" +#import "MDCSelfSizingStereoCellImageViewVerticalPosition.h" diff --git a/components/List/src/private/MDCSelfSizingStereoCellLayout.h b/components/List/src/private/MDCSelfSizingStereoCellLayout.h index b017b0c0c9d..b23e7b1ecdb 100644 --- a/components/List/src/private/MDCSelfSizingStereoCellLayout.h +++ b/components/List/src/private/MDCSelfSizingStereoCellLayout.h @@ -17,6 +17,8 @@ #import #import +#import "MDCSelfSizingStereoCellImageViewVerticalPosition.h" + @interface MDCSelfSizingStereoCellLayout : NSObject @property(nonatomic, assign, readonly) CGFloat cellWidth; @@ -28,7 +30,11 @@ @property(nonatomic, assign, readonly) CGRect trailingImageViewFrame; - (instancetype)initWithLeadingImageView:(UIImageView *)leadingImageView + leadingImageViewVerticalPosition: + (MDCSelfSizingStereoCellImageViewVerticalPosition)leadingImageViewVerticalPosition trailingImageView:(UIImageView *)trailingImageView + trailingImageViewVerticalPosition: + (MDCSelfSizingStereoCellImageViewVerticalPosition)trailingImageViewVerticalPosition textContainer:(UIView *)textContainer titleLabel:(UILabel *)titleLabel detailLabel:(UILabel *)detailLabel diff --git a/components/List/src/private/MDCSelfSizingStereoCellLayout.m b/components/List/src/private/MDCSelfSizingStereoCellLayout.m index 5551de034bf..e2e7f5ab67f 100644 --- a/components/List/src/private/MDCSelfSizingStereoCellLayout.m +++ b/components/List/src/private/MDCSelfSizingStereoCellLayout.m @@ -15,6 +15,7 @@ */ #import "MDCSelfSizingStereoCellLayout.h" +#import "MDCSelfSizingStereoCellImageViewVerticalPosition.h" static const CGFloat kVerticalMarginMin = 8.0; static const CGFloat kVerticalMarginMax = 16.0; @@ -39,146 +40,203 @@ @interface MDCSelfSizingStereoCellLayout () @implementation MDCSelfSizingStereoCellLayout - (instancetype)initWithLeadingImageView:(UIImageView *)leadingImageView + leadingImageViewVerticalPosition: + (MDCSelfSizingStereoCellImageViewVerticalPosition)leadingImageViewVerticalPosition trailingImageView:(UIImageView *)trailingImageView + trailingImageViewVerticalPosition: + (MDCSelfSizingStereoCellImageViewVerticalPosition)trailingImageViewVerticalPosition textContainer:(UIView *)textContainer titleLabel:(UILabel *)titleLabel detailLabel:(UILabel *)detailLabel cellWidth:(CGFloat)cellWidth { self = [super init]; if (self) { - self.cellWidth = cellWidth; - [self assignFrameForLeadingImageView:leadingImageView]; - [self assignFrameForTrailingImageView:trailingImageView]; - [self assignFramesForTextContainer:textContainer titleLabel:titleLabel detailLabel:detailLabel]; - self.calculatedHeight = [self calculateHeight]; + [self calculateLayoutWithLeadingImageView:leadingImageView + leadingImageViewVerticalPosition:leadingImageViewVerticalPosition + trailingImageView:trailingImageView + trailingImageViewVerticalPosition:trailingImageViewVerticalPosition + textContainer:textContainer + titleLabel:titleLabel + detailLabel:detailLabel + cellWidth:cellWidth]; } return self; } -- (void)assignFrameForLeadingImageView:(UIImageView *)leadingImageView { - CGSize size = [self sizeForImage:leadingImageView.image]; - CGFloat leadingPadding = 0; - CGFloat topPadding = 0; - if (!CGSizeEqualToSize(size, CGSizeZero)) { - leadingPadding = kHorizontalMargin; - topPadding = [self verticalMarginForImageViewOfSize:size]; +/** + This method calculates the frames for the subviews in the cell. It starts by determining the + leading and trailing image view frames. Then it uses those frames to determine the frame of the + title label, detail label, and the view that contains them. Then, it calculates the height of the + cell. Finally, it vertically centers the iamge views if necessary. + */ +- (void)calculateLayoutWithLeadingImageView:(UIImageView *)leadingImageView + leadingImageViewVerticalPosition: + (MDCSelfSizingStereoCellImageViewVerticalPosition)leadingImageViewVerticalPosition + trailingImageView:(UIImageView *)trailingImageView + trailingImageViewVerticalPosition: + (MDCSelfSizingStereoCellImageViewVerticalPosition)trailingImageViewVerticalPosition + textContainer:(UIView *)textContainer + titleLabel:(UILabel *)titleLabel + detailLabel:(UILabel *)detailLabel + cellWidth:(CGFloat)cellWidth { + // Initially assume an image view frame of .zero. + CGRect leadingImageViewFrame = CGRectZero; + CGSize leadingImageViewSize = [self sizeForImage:leadingImageView.image]; + BOOL displaysLeadingImageView = !CGSizeEqualToSize(leadingImageViewSize, CGSizeZero); + if (displaysLeadingImageView) { + // Calculate non-zero image view frame because image exists and has a valid size. + CGFloat leadingImageViewMinX = kHorizontalMargin; + CGFloat leadingImageViewMinY = [self verticalMarginForImageViewOfSize:leadingImageViewSize]; + leadingImageViewFrame = CGRectMake(leadingImageViewMinX, leadingImageViewMinY, + leadingImageViewSize.width, leadingImageViewSize.height); } - CGRect rect = CGRectZero; - rect.origin = CGPointMake(leadingPadding, topPadding); - rect.size = size; - self.leadingImageViewFrame = rect; -} -- (void)assignFrameForTrailingImageView:(UIImageView *)trailingImageView { - CGSize size = [self sizeForImage:trailingImageView.image]; - CGFloat trailingPadding = 0; - CGFloat topPadding = 0; - if (!CGSizeEqualToSize(size, CGSizeZero)) { - trailingPadding = kHorizontalMargin; - topPadding = [self verticalMarginForImageViewOfSize:size]; + // Initially assume an image view frame of .zero. + CGRect trailingImageViewFrame = CGRectZero; + CGSize trailingImageViewSize = [self sizeForImage:trailingImageView.image]; + BOOL displaysTrailingImageView = !CGSizeEqualToSize(trailingImageViewSize, CGSizeZero); + if (displaysTrailingImageView) { + // Calculate non-zero image view frame because image exists and has a valid size. + CGFloat trailingImageViewMinX = cellWidth - kHorizontalMargin - trailingImageViewSize.width; + CGFloat trailingImageViewMinY = [self verticalMarginForImageViewOfSize:trailingImageViewSize]; + trailingImageViewFrame = CGRectMake(trailingImageViewMinX, trailingImageViewMinY, + trailingImageViewSize.width, trailingImageViewSize.height); } - CGFloat originX = self.cellWidth - trailingPadding - size.width; - CGRect rect = CGRectZero; - rect.origin = CGPointMake(originX, topPadding); - rect.size = size; - self.trailingImageViewFrame = rect; -} -- (void)assignFramesForTextContainer:(UIView *)textContainer - titleLabel:(UILabel *)titleLabel - detailLabel:(UILabel *)detailLabel { + // Initialize text-related view frame as .zero. + CGRect titleLabelFrame = CGRectZero; + CGRect detailLabelFrame = CGRectZero; + CGRect textContainerFrame = CGRectZero; + BOOL containsTitleText = titleLabel.text.length > 0; BOOL containsDetailText = detailLabel.text.length > 0; - if (!containsTitleText && !containsDetailText) { - self.titleLabelFrame = CGRectZero; - self.detailLabelFrame = CGRectZero; - self.textContainerFrame = CGRectZero; - return; - } + if (containsTitleText || containsDetailText) { + // Determine min x of text region + CGFloat textContainerMinX = kHorizontalMargin; + if (displaysLeadingImageView) { + textContainerMinX = CGRectGetMaxX(leadingImageViewFrame) + kHorizontalMargin; + } - BOOL hasLeadingImage = !CGRectEqualToRect(self.leadingImageViewFrame, CGRectZero); - BOOL hasTrailingImage = !CGRectEqualToRect(self.trailingImageViewFrame, CGRectZero); - CGFloat leadingImageViewMaxX = (hasLeadingImage ? CGRectGetMaxX(self.leadingImageViewFrame) : 0); - CGFloat textContainerMinX = leadingImageViewMaxX + kHorizontalMargin; - CGFloat trailingImageViewMinX = - (hasTrailingImage ? CGRectGetMinX(self.trailingImageViewFrame) : self.cellWidth); - CGFloat textContainerMaxX = trailingImageViewMinX - kHorizontalMargin; - CGFloat textContainerMinY = kVerticalMarginMax; - CGFloat textContainerWidth = textContainerMaxX - textContainerMinX; - CGFloat textContainerHeight = 0; - - const CGSize fittingSize = CGSizeMake(textContainerWidth, CGFLOAT_MAX); - - CGSize titleSize = [titleLabel sizeThatFits:fittingSize]; - titleSize.width = textContainerWidth; - const CGFloat titleLabelMinX = 0; - CGFloat titleLabelMinY = 0; - CGPoint titleOrigin = CGPointMake(titleLabelMinX, titleLabelMinY); - CGRect titleFrame = CGRectZero; - titleFrame.origin = titleOrigin; - titleFrame.size = titleSize; - self.titleLabelFrame = titleFrame; - - CGSize detailSize = [detailLabel sizeThatFits:fittingSize]; - detailSize.width = textContainerWidth; - const CGFloat detailLabelMinX = 0; - CGFloat detailLabelMinY = CGRectGetMaxY(titleFrame); - if (titleLabel.text.length > 0 && detailLabel.text.length > 0) { - detailLabelMinY += kInterLabelVerticalPadding; - } - CGPoint detailOrigin = CGPointMake(detailLabelMinX, detailLabelMinY); - CGRect detailFrame = CGRectZero; - detailFrame.origin = detailOrigin; - detailFrame.size = detailSize; - self.detailLabelFrame = detailFrame; + // Determine max x of text region + CGFloat textContainerMaxX = cellWidth - kHorizontalMargin; + if (displaysTrailingImageView) { + textContainerMaxX = CGRectGetMinX(trailingImageViewFrame) - kHorizontalMargin; + } - textContainerHeight = CGRectGetMaxY(self.detailLabelFrame); + // Begin determining the frame of the view that contains the title and detail labels. + // The final frame of this view must be calculated further down because it depends on the frames + // of the labels it contains. + CGFloat textContainerMinY = kVerticalMarginMax; + CGFloat textContainerWidth = textContainerMaxX - textContainerMinX; + CGFloat textContainerHeight = 0; - CGRect textContainerFrame = CGRectZero; - CGPoint textContainerOrigin = CGPointMake(textContainerMinX, textContainerMinY); - CGSize textContainerSize = CGSizeMake(textContainerWidth, textContainerHeight); - textContainerFrame.origin = textContainerOrigin; - textContainerFrame.size = textContainerSize; - self.textContainerFrame = textContainerFrame; + CGSize fittingSize = CGSizeMake(textContainerWidth, CGFLOAT_MAX); + + // Determine title label size and then frame within container view + CGSize titleSize = [titleLabel sizeThatFits:fittingSize]; + titleSize.width = textContainerWidth; + CGFloat titleLabelMinX = 0; + CGFloat titleLabelMinY = 0; + CGPoint titleOrigin = CGPointMake(titleLabelMinX, titleLabelMinY); + titleLabelFrame.origin = titleOrigin; + titleLabelFrame.size = titleSize; - BOOL hasOnlyTitleText = containsTitleText && !containsDetailText; - BOOL shouldVerticallyCenterTitleText = hasOnlyTitleText && hasLeadingImage; - if (shouldVerticallyCenterTitleText) { - CGFloat leadingImageViewCenterY = CGRectGetMidY(self.leadingImageViewFrame); - CGFloat textContainerCenterY = CGRectGetMidY(self.textContainerFrame); - CGFloat difference = textContainerCenterY - leadingImageViewCenterY; - CGRect offsetTextContainerRect = CGRectOffset(self.textContainerFrame, 0, -difference); - BOOL willExtendPastMargin = offsetTextContainerRect.origin.y < kVerticalMarginMax; - if (!willExtendPastMargin) { - self.textContainerFrame = offsetTextContainerRect; + // Determine detail label size and then frame within container view + CGSize detailSize = [detailLabel sizeThatFits:fittingSize]; + detailSize.width = textContainerWidth; + CGFloat detailLabelMinX = 0; + CGFloat detailLabelMinY = CGRectGetMaxY(titleLabelFrame); + if (titleLabel.text.length > 0 && detailLabel.text.length > 0) { + detailLabelMinY += kInterLabelVerticalPadding; + } + CGPoint detailOrigin = CGPointMake(detailLabelMinX, detailLabelMinY); + detailLabelFrame.origin = detailOrigin; + detailLabelFrame.size = detailSize; + + // Determine title and detail label container view height and then frame + textContainerHeight = CGRectGetMaxY(detailLabelFrame); + + CGPoint textContainerOrigin = CGPointMake(textContainerMinX, textContainerMinY); + CGSize textContainerSize = CGSizeMake(textContainerWidth, textContainerHeight); + textContainerFrame.origin = textContainerOrigin; + textContainerFrame.size = textContainerSize; + + // Usually the image views are positioned at the top. However, this looks funny if there is only + // one line of text. If there is only one line of text, make it have the same center Y as the + // image views. + BOOL hasOnlyTitleText = containsTitleText && !containsDetailText; + BOOL shouldVerticallyCenterTitleText = hasOnlyTitleText && displaysLeadingImageView; + if (shouldVerticallyCenterTitleText) { + CGFloat leadingImageViewCenterY = CGRectGetMidY(leadingImageViewFrame); + CGFloat textContainerCenterY = CGRectGetMidY(textContainerFrame); + CGFloat difference = textContainerCenterY - leadingImageViewCenterY; + CGRect offsetTextContainerRect = CGRectOffset(textContainerFrame, 0, -difference); + BOOL willExtendPastMargin = offsetTextContainerRect.origin.y < kVerticalMarginMax; + if (!willExtendPastMargin) { + textContainerFrame = offsetTextContainerRect; + } } } + + // Calculate the height of the cell. + CGFloat calculatedHeight = [self calculateHeightWithLeadingImageViewFrame:leadingImageViewFrame + trailingImageViewFrame:trailingImageViewFrame + textContainerFrame:textContainerFrame]; + + // Center the image views if the user has specified that they want the image views centered. + if (displaysLeadingImageView && + leadingImageViewVerticalPosition == MDCSelfSizingStereoCellImageViewVerticalPositionCenter) { + CGFloat verticallyCenteredLeadingImageViewMinY = + (0.5f * calculatedHeight) - (0.5f * leadingImageViewSize.height); + leadingImageViewFrame = + CGRectMake(CGRectGetMinX(leadingImageViewFrame), verticallyCenteredLeadingImageViewMinY, + leadingImageViewSize.width, leadingImageViewSize.height); + } + + if (displaysTrailingImageView && + trailingImageViewVerticalPosition == MDCSelfSizingStereoCellImageViewVerticalPositionCenter) { + CGFloat verticallyCenteredTrailingImageViewMinY = + (0.5f * calculatedHeight) - (0.5f * trailingImageViewSize.height); + trailingImageViewFrame = + CGRectMake(CGRectGetMinX(trailingImageViewFrame), verticallyCenteredTrailingImageViewMinY, + trailingImageViewSize.width, trailingImageViewSize.height); + } + + // Set the properties to be read by the cell. + self.textContainerFrame = textContainerFrame; + self.titleLabelFrame = titleLabelFrame; + self.detailLabelFrame = detailLabelFrame; + self.cellWidth = cellWidth; + self.leadingImageViewFrame = leadingImageViewFrame; + self.trailingImageViewFrame = trailingImageViewFrame; + self.calculatedHeight = calculatedHeight; } -- (CGFloat)calculateHeight { +- (CGFloat)calculateHeightWithLeadingImageViewFrame:(CGRect)leadingImageViewFrame + trailingImageViewFrame:(CGRect)trailingImageViewFrame + textContainerFrame:(CGRect)textContainerFrame { CGFloat maxHeight = 0; CGFloat leadingImageViewRequiredVerticalSpace = 0; CGFloat trailingImageViewRequiredVerticalSpace = 0; CGFloat textContainerRequiredVerticalSpace = 0; - if (!CGRectEqualToRect(self.leadingImageViewFrame, CGRectZero)) { + if (!CGRectEqualToRect(leadingImageViewFrame, CGRectZero)) { leadingImageViewRequiredVerticalSpace = - CGRectGetMaxY(self.leadingImageViewFrame) + - [self verticalMarginForImageViewOfSize:self.leadingImageViewFrame.size]; + CGRectGetMaxY(leadingImageViewFrame) + + [self verticalMarginForImageViewOfSize:leadingImageViewFrame.size]; if (leadingImageViewRequiredVerticalSpace > maxHeight) { maxHeight = leadingImageViewRequiredVerticalSpace; } } - if (!CGRectEqualToRect(self.trailingImageViewFrame, CGRectZero)) { + if (!CGRectEqualToRect(trailingImageViewFrame, CGRectZero)) { trailingImageViewRequiredVerticalSpace = - CGRectGetMaxY(self.trailingImageViewFrame) + - [self verticalMarginForImageViewOfSize:self.trailingImageViewFrame.size]; + CGRectGetMaxY(trailingImageViewFrame) + + [self verticalMarginForImageViewOfSize:trailingImageViewFrame.size]; if (trailingImageViewRequiredVerticalSpace > maxHeight) { maxHeight = trailingImageViewRequiredVerticalSpace; } } - if (!CGRectEqualToRect(self.textContainerFrame, CGRectZero)) { - textContainerRequiredVerticalSpace = - CGRectGetMaxY(self.textContainerFrame) + kVerticalMarginMax; + if (!CGRectEqualToRect(textContainerFrame, CGRectZero)) { + textContainerRequiredVerticalSpace = CGRectGetMaxY(textContainerFrame) + kVerticalMarginMax; if (textContainerRequiredVerticalSpace > maxHeight) { maxHeight = textContainerRequiredVerticalSpace; } @@ -202,8 +260,9 @@ - (CGSize)sizeForImage:(UIImage *)image { } - (CGFloat)verticalMarginForImageViewOfSize:(CGSize)size { - CGFloat leadingImageHeight = size.height; - if (leadingImageHeight > 0 && leadingImageHeight <= kImageSideLengthMedium) { + if (size.height == 0) { + return 0; + } else if (size.height > 0 && size.height <= kImageSideLengthMedium) { return kVerticalMarginMax; } else { return kVerticalMarginMin; diff --git a/components/List/tests/snapshot/MDCSelfSizingStereoCellSnapshotTests.m b/components/List/tests/snapshot/MDCSelfSizingStereoCellSnapshotTests.m index 5a40df7253c..a643909469b 100644 --- a/components/List/tests/snapshot/MDCSelfSizingStereoCellSnapshotTests.m +++ b/components/List/tests/snapshot/MDCSelfSizingStereoCellSnapshotTests.m @@ -156,6 +156,29 @@ - (void)testCellWithTitleAndDetailAndImage { [self generateSnapshotAndVerifyForView:self.collectionView]; } +- (void)testCellWithTitleAndDetailAndVerticallyCenteredImage { + // When + MDCSelfSizingStereoCell *cell = [[MDCSelfSizingStereoCell alloc] init]; + cell.titleLabel.text = @"This is a title label. This is a title label. This is a title label. " + @"This is a title label. This is a title label."; + cell.detailLabel.text = @"This is a detail label. This is a detail label. This is a detail " + @"label. This is a detail label. This is a detail label."; + cell.leadingImageView.image = [UIImage mdc_testImageOfSize:CGSizeMake(24, 24) + withStyle:MDCSnapshotTestImageStyleCheckerboard]; + cell.trailingImageView.image = [UIImage mdc_testImageOfSize:CGSizeMake(24, 24) + withStyle:MDCSnapshotTestImageStyleRectangles]; + cell.leadingImageViewVerticalPosition = MDCSelfSizingStereoCellImageViewVerticalPositionCenter; + cell.trailingImageViewVerticalPosition = MDCSelfSizingStereoCellImageViewVerticalPositionCenter; + self.arrayOfCells = @[ cell ]; + + CGSize cellSize = [cell systemLayoutSizeFittingSize:CGSizeMake(170, CGFLOAT_MAX)]; + self.collectionView.frame = CGRectMake(0, 0, cellSize.width, cellSize.height); + self.collectionViewLayout.estimatedItemSize = cellSize; + + // Then + [self generateSnapshotAndVerifyForView:self.collectionView]; +} + - (void)testCellWithDynamicTypeForContentSizeCategoryExtraSmallEnabledForTitleAndDetail { if (@available(iOS 10.0, *)) { // Given @@ -280,6 +303,8 @@ - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView dequeuedCell.leadingImageView.image = cell.leadingImageView.image; dequeuedCell.trailingImageView.image = cell.trailingImageView.image; dequeuedCell.mdc_adjustsFontForContentSizeCategory = cell.mdc_adjustsFontForContentSizeCategory; + dequeuedCell.leadingImageViewVerticalPosition = cell.leadingImageViewVerticalPosition; + dequeuedCell.trailingImageViewVerticalPosition = cell.trailingImageViewVerticalPosition; [dequeuedCell removeFromSuperview]; return dequeuedCell; } diff --git a/components/ScalableFontDescriptor/src/MDCScalableFontDescriptor.m b/components/ScalableFontDescriptor/src/MDCScalableFontDescriptor.m index 10e692d70c3..29aeb77f9cf 100644 --- a/components/ScalableFontDescriptor/src/MDCScalableFontDescriptor.m +++ b/components/ScalableFontDescriptor/src/MDCScalableFontDescriptor.m @@ -54,7 +54,9 @@ - (NSString *)description { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ textStyles = @[ +#if !TARGET_OS_TV UIFontTextStyleLargeTitle, +#endif UIFontTextStyleTitle1, UIFontTextStyleTitle2, UIFontTextStyleTitle3, diff --git a/components/TextControls/README.md b/components/TextControls/README.md index 5d12c51436e..6e7d3aada96 100644 --- a/components/TextControls/README.md +++ b/components/TextControls/README.md @@ -412,14 +412,6 @@ import MaterialComponents.MaterialTextControls_OutlinedTextFieldsTheming From there, call either the default or the error theming method. -#### Objective-C - -```objc -MDCContainerScheme *containerScheme = ... // Set up a container scheme; -[textField applyThemeWithScheme:self.containerScheme]; // Default theming method -[textField applyErrorThemeWithScheme:self.containerScheme]; // Error theming method -``` - #### Swift ```swift @@ -427,4 +419,12 @@ let containerScheme = ... // Set up a container scheme textField.applyTheme(withScheme: self.containerScheme) // Default theming method textField.applyErrorTheme(withScheme: self.containerScheme) // Error theming method ``` + +#### Objective-C + +```objc +MDCContainerScheme *containerScheme = ... // Set up a container scheme; +[textField applyThemeWithScheme:self.containerScheme]; // Default theming method +[textField applyErrorThemeWithScheme:self.containerScheme]; // Error theming method +``` diff --git a/demos/supplemental/RemoteImageServiceForMDCDemos.podspec b/demos/supplemental/RemoteImageServiceForMDCDemos.podspec index ad270c1879c..647d91e5b50 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 = "119.3.0" + s.version = "119.4.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" diff --git a/scripts/release b/scripts/release index 492669c1e30..1f9df689c7d 100755 --- a/scripts/release +++ b/scripts/release @@ -351,18 +351,24 @@ publish_release() { enforce_changelog_version - ghtoken=$(cat ~/.gh.json | grep "github_token" | cut -d'"' -f4) + ghtoken="$(cut -d " " -f 2 <<< $(cat ~/.config/gh/hosts.yml | grep "oauth_token: "))" if [ -z "$ghtoken" ]; then - echo "Must be authenticated with gh." + echo "Error: Unable to find a token in ~/.config/gh/hosts.yml." echo - echo "Install gh by running:" + echo "You must authenticate with the official GitHub command line interface tool:" + echo "https://github.com/cli/cli" echo - echo " npm install -g gh" + echo "Not to be confused with the following unofficial (and no longer maintained) GitHub command line tool that this script used to use:" + echo "https://github.com/node-gh/gh" echo - echo "And then get an auth token by running:" - echo - echo " gh user" + echo "To proceed, do the following:" + echo "1. Install the official command line tool" + echo "2. Authenticate using 'gh auth login'" + echo " a. When it asks 'What account do you want to log into?', choose 'GitHub.com'" + echo " b. When it asks 'How would you like to authenticate?', choose 'Login with a web browser'" + echo "3. Re-run the scripts/release command" echo + echo "You will probably have to delete or move the old gh to make way for the new (official) one." exit 1 fi