diff --git a/CHANGELOG.md b/CHANGELOG.md index c30b4cbaa83..94deaa0f173 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,129 @@ +# 110.1.0 + +In this minor release, we deprecated `visibleAreaInsets` from `MDCChipView` and added `centerVisibleArea` API. We annotated several APIs as to be deprecated in Buttons. Ripple support is added to CollectionCells and Collections as well. + +## New deprecations + +### Chips + +`visibleAreaInsets` is deprecated. + +## New features + +### CollectionCells + +Ripple is supported as an opt-in behavior. + +**Objective-C** + +```objc +MDCCollectionCell *cell= [[MDCCollectionCell alloc] init]; +cell.enableRippleBehavior = YES; +``` + +**Swift** + +```swift +let cell = MDCCollectionCell() +cell.enableRippleBehavior = true +``` + +### Chips + +Chips supports `centerVisibleArea`, which is often used to configure invisible part of frame for tappable area. + +**Objective-C** + +```objc +MDChipView *chip = [[MDCChipView alloc] init]; +chip.centerVisibleArea = YES; +``` + +**Swift** + +```swift +let chip = MDCChipView() +chip.centerVisibleArea = true +``` + +## API changes + +### Chips + +*new* property: `centerVisibleArea` in `MDCChipView` + +### CollectionCells + +*new* property: `enableRippleBehavior` in `MDCCollectionViewCell` + +*new* property: `rippleView` in `MDCCollectionViewCell` + +### Collections + +*new* property: `enableRippleBehavior` in `MDCCollectionViewController` + +*new* method: `- collectionView:rippleTouchController:rippleViewAtIndexPath:` in `MDCCollectionViewStylingDelegate` + +### TextFields + +*new* property: `useConstraintsForIntrinsicContentSize` in `MDCMultilineTextField` + +## Component changes + +### ActionSheet + +* [Fix broken links](https://github.com/material-components/material-components-ios/commit/4cc338ac65a02d7999013e0865474bea386411fd) (Andrew Overton) + +### AppBar + +* [Remove redundant To Be Deprecated annotation.](https://github.com/material-components/material-components-ios/commit/2445d525894be3ce63b97f8c116eb08ee600c644) (Jeff Verkoeyen) + +### BottomNavigation + +* [Add check for existence of UIPointerInteraction to prevent iOS 13 beta crashes.](https://github.com/material-components/material-components-ios/commit/7e252891630355519879ef0fe5d1b305061e9f18) (Bryan Oltman) + +### Buttons + +* [Mark more apis as to-be-deprecated.](https://github.com/material-components/material-components-ios/commit/89477800f19d3fff1162d6cad164ae9248379ae2) (Jeff Verkoeyen) + +### Chips + +* [Add centerVisibleArea to MDCChipView and deprecate visibleAreaInsets.](https://github.com/material-components/material-components-ios/commit/87422cf14f454ce8c0c9f4a9c3233f2aa73ef6b4) (Wenyu Zhang) + +### Dialogs + +* [Modify dialogs docs](https://github.com/material-components/material-components-ios/commit/9806cab151bf8feb7b22e431cb032d247b76a965) (Andrew Overton) + +### NavigationDrawer + +* [Updated documentation](https://github.com/material-components/material-components-ios/commit/b9e03824255ce734cf4741d19d25696f5108bf7f) (Josue Lopes) + +### ProgressView + +* [Fix RTL support on MDCProgressGradientView.](https://github.com/material-components/material-components-ios/commit/39de3e3efbabda3e20aabf14485f872979b8d230) (Wenyu Zhang) +* [Reimplement animation on indeterminate mode to meet specification.](https://github.com/material-components/material-components-ios/commit/989635ba8b6663eda33534c464c12cd4cf4ec1e2) (Wenyu Zhang) +* [Reverse the start point and end point of indeterminate progress stroke to make it aligning with the animation direction.](https://github.com/material-components/material-components-ios/commit/a411506026c6a27b5439264dba552900ef7fb11a) (Wenyu Zhang) + +### Snackbar + +* [Use the ToBeDeprecated convention for to-be-deprecated APIs.](https://github.com/material-components/material-components-ios/commit/55a51fad9a00327350489e9c4bec4f1ff2824fd9) (Jeff Verkoeyen) + +### TextFields + +* [Updates MDCMultilineTextField to better support being embedded in self-sizing cells.](https://github.com/material-components/material-components-ios/commit/b3a632da4654d30ffa6f152bf4954951305c84d6) (Nobody) +* [Updates MDCMultilineTextField's bottom textfield constraint to use the textInsets as the constant.](https://github.com/material-components/material-components-ios/commit/e214be3b2d4a945dcb8e3e0c4fe914c9e628b327) (Nobody) + +### private/ThumbTrack + +* [Move private MDCDiscreteDotView class into its own header and implementation files.](https://github.com/material-components/material-components-ios/commit/ba636497272b28ec28d2fbc65fb673b673bfa2cb) (Bryan Oltman) + +## Multi-component changes + +* [Add Ripple as an opt-in for MDCCollections and MDCCollectionCells](https://github.com/material-components/material-components-ios/commit/f8465c8fb3e68bc950c9991d803c223cdc82b60c) (Yarden Eitan) +* [Delete obsolete markdown files](https://github.com/material-components/material-components-ios/commit/b2051644778785099ed79f1c718191f69c3d1177) (Andrew Overton) + +--- + # 110.0.0 In this major release we removed an API from `MDCProgressView` and fixed bugs in ActionSheet, Dialogs, ProgressView, Ripple, and TextControls. diff --git a/MaterialComponents.podspec b/MaterialComponents.podspec index 43029b74d69..106af8fb4f6 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 = "110.0.0" + mdc.version = "110.1.0" mdc.authors = "The Material Components authors." mdc.summary = "A collection of stand-alone production-ready UI libraries focused on design details." mdc.homepage = "https://github.com/material-components/material-components-ios" @@ -669,6 +669,7 @@ Pod::Spec.new do |mdc| component.dependency "MDFInternationalization" component.dependency "MaterialComponents/CollectionLayoutAttributes" component.dependency "MaterialComponents/Ink" + component.dependency "MaterialComponents/Ripple" component.dependency "MaterialComponents/Typography" component.dependency "MaterialComponents/Palettes" component.dependency "MaterialComponents/private/Icons/ic_check" @@ -723,6 +724,7 @@ Pod::Spec.new do |mdc| component.dependency "MaterialComponents/CollectionLayoutAttributes" component.dependency "MaterialComponents/Ink" component.dependency "MaterialComponents/Palettes" + component.dependency "MaterialComponents/Ripple" component.dependency "MaterialComponents/ShadowElevations" component.dependency "MaterialComponents/ShadowLayer" component.dependency "MaterialComponents/Typography" diff --git a/MaterialComponentsBeta.podspec b/MaterialComponentsBeta.podspec index c07ed1ceaca..d77119e6fa9 100644 --- a/MaterialComponentsBeta.podspec +++ b/MaterialComponentsBeta.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |mdc| mdc.name = "MaterialComponentsBeta" - mdc.version = "110.0.0" + mdc.version = "110.1.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 193f47c5657..dfef45a45f6 100644 --- a/MaterialComponentsEarlGreyTests.podspec +++ b/MaterialComponentsEarlGreyTests.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "MaterialComponentsEarlGreyTests" - s.version = "110.0.0" + s.version = "110.1.0" s.authors = "The Material Components authors." s.summary = "This spec is an aggregate of all the Material Components EarlGrey tests." s.description = "This spec is made for use in the MDC Catalog." diff --git a/MaterialComponentsExamples.podspec b/MaterialComponentsExamples.podspec index 0edb20edddc..12d02a7ebc3 100644 --- a/MaterialComponentsExamples.podspec +++ b/MaterialComponentsExamples.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "MaterialComponentsExamples" - s.version = "110.0.0" + s.version = "110.1.0" s.authors = "The Material Components authors." s.summary = "This spec is an aggregate of all the Material Components examples." s.description = "This spec is made for use in the MDC Catalog. Used in conjunction with CatalogByConvention we create our Material Catalog." diff --git a/MaterialComponentsSnapshotTests.podspec b/MaterialComponentsSnapshotTests.podspec index 90c947f78f2..eda5bbbfbda 100644 --- a/MaterialComponentsSnapshotTests.podspec +++ b/MaterialComponentsSnapshotTests.podspec @@ -53,7 +53,7 @@ end Pod::Spec.new do |s| s.name = "MaterialComponentsSnapshotTests" - s.version = "110.0.0" + s.version = "110.1.0" s.authors = "The Material Components authors." s.summary = "This spec is an aggregate of all the Material Components snapshot tests." s.homepage = "https://github.com/material-components/material-components-ios" diff --git a/VERSION b/VERSION index 453b0b9e043..12ac3ccab56 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -110.0.0 +110.1.0 diff --git a/catalog/MDCCatalog/Info.plist b/catalog/MDCCatalog/Info.plist index d6676711990..ba644c4eddb 100644 --- a/catalog/MDCCatalog/Info.plist +++ b/catalog/MDCCatalog/Info.plist @@ -15,11 +15,11 @@ CFBundlePackageType APPL CFBundleShortVersionString - 110.0.0 + 110.1.0 CFBundleSignature ???? CFBundleVersion - 110.0.0 + 110.1.0 LSRequiresIPhoneOS UIAppFonts diff --git a/catalog/MDCCatalog/MDCCatalogComponentsController.swift b/catalog/MDCCatalog/MDCCatalogComponentsController.swift index da9f76836d4..a94087fecc7 100644 --- a/catalog/MDCCatalog/MDCCatalogComponentsController.swift +++ b/catalog/MDCCatalog/MDCCatalogComponentsController.swift @@ -12,21 +12,21 @@ // See the License for the specific language governing permissions and // limitations under the License. +import UIKit import CatalogByConvention import MaterialCatalog - import MaterialComponents.MaterialFlexibleHeader -import MaterialComponents.MaterialIcons_ic_arrow_back -import MaterialComponents.MaterialInk import MaterialComponents.MaterialLibraryInfo +import MaterialComponents.MaterialRipple import MaterialComponents.MaterialShadowElevations import MaterialComponents.MaterialShadowLayer import MaterialComponents.MaterialThemes import MaterialComponents.MaterialTypography +import MaterialComponents.MaterialIcons_ic_arrow_back -import UIKit - -class MDCCatalogComponentsController: UICollectionViewController, UICollectionViewDelegateFlowLayout, MDCInkTouchControllerDelegate { +class MDCCatalogComponentsController: UICollectionViewController, + UICollectionViewDelegateFlowLayout, MDCRippleTouchControllerDelegate +{ fileprivate struct Constants { static let headerScrollThreshold: CGFloat = 30 @@ -39,10 +39,10 @@ class MDCCatalogComponentsController: UICollectionViewController, UICollectionVi fileprivate lazy var headerViewController = MDCFlexibleHeaderViewController() - private lazy var inkController: MDCInkTouchController = { - let controller = MDCInkTouchController(view: self.collectionView!) - controller.delaysInkSpread = true + private lazy var rippleController: MDCRippleTouchController = { + let controller = MDCRippleTouchController() controller.delegate = self + controller.shouldProcessRippleWithScrollViewGestures = false return controller }() @@ -146,7 +146,7 @@ class MDCCatalogComponentsController: UICollectionViewController, UICollectionVi override func viewDidLoad() { super.viewDidLoad() - inkController.addInkView() + rippleController.addRipple(to: self.collectionView!) let containerView = UIView(frame: headerViewController.headerView.bounds) containerView.autoresizingMask = [.flexibleWidth, .flexibleHeight] @@ -318,27 +318,24 @@ class MDCCatalogComponentsController: UICollectionViewController, UICollectionVi return node.children.count } - func inkViewForView(_ view: UIView) -> MDCInkView { - let foundInkView = MDCInkView.injectedInkView(for: view) - foundInkView.inkStyle = .bounded - foundInkView.inkColor = UIColor(white:0.957, alpha: 0.2) - return foundInkView - } - - // MARK: MDCInkTouchControllerDelegate + // MARK: MDCRippleTouchControllerDelegate - func inkTouchController(_ inkTouchController: MDCInkTouchController, - shouldProcessInkTouchesAtTouchLocation location: CGPoint) -> Bool { - return self.collectionView!.indexPathForItem(at: location) != nil + func rippleTouchController( + _ rippleTouchController: MDCRippleTouchController, + shouldProcessRippleTouchesAtTouchLocation location: CGPoint + ) -> Bool { + return self.collectionView?.indexPathForItem(at: location) != nil } - func inkTouchController(_ inkTouchController: MDCInkTouchController, - inkViewAtTouchLocation location: CGPoint) -> MDCInkView? { - if let indexPath = self.collectionView!.indexPathForItem(at: location) { - let cell = self.collectionView!.cellForItem(at: indexPath) - return inkViewForView(cell!) + func rippleTouchController( + _ rippleTouchController: MDCRippleTouchController, + rippleViewAtTouchLocation location: CGPoint + ) -> MDCRippleView? { + if let indexPath = self.collectionView?.indexPathForItem(at: location) { + let cell = self.collectionView?.cellForItem(at: indexPath) + return MDCRippleView.injectedRippleView(for: cell!) } - return MDCInkView() + return MDCRippleView() } // MARK: UICollectionViewDelegate @@ -346,8 +343,8 @@ class MDCCatalogComponentsController: UICollectionViewController, UICollectionVi override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { let cell = - collectionView.dequeueReusableCell(withReuseIdentifier: "MDCCatalogCollectionViewCell", - for: indexPath) + collectionView.dequeueReusableCell(withReuseIdentifier: "MDCCatalogCollectionViewCell", + for: indexPath) cell.backgroundColor = AppTheme.containerScheme.colorScheme.backgroundColor let componentName = node.children[indexPath.row].title @@ -355,8 +352,8 @@ class MDCCatalogComponentsController: UICollectionViewController, UICollectionVi catalogCell.populateView(componentName) } - // Ensure that ink animations aren't recycled. - MDCInkView.injectedInkView(for: view).cancelAllAnimations(animated: false) + // Ensure that ripple animations aren't recycled. + MDCRippleView.injectedRippleView(for: view).cancelAllRipples(animated: false, completion: nil) return cell } diff --git a/catalog/MDCDragons/Info.plist b/catalog/MDCDragons/Info.plist index a7978b69e1d..15d9f2a0ee0 100644 --- a/catalog/MDCDragons/Info.plist +++ b/catalog/MDCDragons/Info.plist @@ -15,9 +15,9 @@ CFBundlePackageType APPL CFBundleShortVersionString - 110.0.0 + 110.1.0 CFBundleVersion - 110.0.0 + 110.1.0 LSRequiresIPhoneOS UILaunchStoryboardName diff --git a/catalog/MaterialCatalog/MaterialCatalog.podspec b/catalog/MaterialCatalog/MaterialCatalog.podspec index efdc5a352d9..040211479cf 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 = "110.0.0" + s.version = "110.1.0" s.summary = "Helper Objective-C classes for the MDC catalog." s.description = "This spec is made for use in the MDC Catalog." s.homepage = "https://github.com/material-components/material-components-ios" diff --git a/components/ActionSheet/README.md b/components/ActionSheet/README.md index ebd33c7d26b..4574baef949 100644 --- a/components/ActionSheet/README.md +++ b/components/ActionSheet/README.md @@ -177,21 +177,21 @@ The layout of the Action Sheet list items can be adjusted with the `contentEdgeInsets` API. Positive values will inset the content and negative values will outset the conent. The insets apply to all action items. -Action Sheet showing three items with default edge content insets. +Action Sheet showing three items with default edge content insets. Action Sheet showing three items with default content insets. For example, setting top and bottom insets (positive values) will reduce the -height of the Action list items. +height of the Action list items. -Action Sheet showing three items with top and bottom content edge insets. +Action Sheet showing three items with top and bottom content edge insets. Action Sheet showing three items with top and bottom content edge insets. Setting a left outset (negative value) and right inset (positive value) will shift the Action's content to the trailing edge. -Action Sheet showing three items with a left content edge outset and right inset shifting content to the right. +Action Sheet showing three items with a left content edge outset and right inset shifting content to the right. Action Sheet showing three items with a left content edge outset and right inset shifting content to the right. diff --git a/components/AppBar/docs/README.md b/components/AppBar/docs/README.md deleted file mode 100644 index 3fd42515be5..00000000000 --- a/components/AppBar/docs/README.md +++ /dev/null @@ -1,85 +0,0 @@ -# App bars: top - - - -The Material Design top app bar displays information and actions relating to the current view. - -
- An animation showing a top app bar appearing and disappearing. -
- - - -## Related components - -* [FlexibleHeader](../../FlexibleHeader) -* [HeaderStackView](../../HeaderStackView) -* [NavigationBar](../../NavigationBar) - - - -- - - - -## Overview - -App bar is composed of the following components: - -* [FlexibleHeader](../../FlexibleHeader) -* [HeaderStackView](../../HeaderStackView) -* [NavigationBar](../../NavigationBar) - -It is essentially a FlexibleHeader with a HeaderStackView and NavigationBar added as subviews. - -`MDCAppBarViewController` is the primary API for the component. All integration strategies will -make use of it in some manner. Unlike UIKit, which shares a single `UINavigationBar` instance across -many view controllers in a stack, app bar relies on each view controller creating and managing its -own `MDCAppBarViewController` instance. - -## Installation - -- [Typical installation](../../../docs/component-installation.md) - -## Usage - -- [Typical use: View controller containment, as a navigation controller](typical-use-navigation-controller.md) -- [Typical use: View controller containment, as a child](typical-use-child.md) -- [Typical use: View controller containment, as a container](typical-use-container.md) -- [Typical use: Tracking a scroll view](../../FlexibleHeader/docs/typical-use-tracking-a-scroll-view.md) -- [Typical use: Enabling observation of the tracking scroll view](../../FlexibleHeader/docs/typical-use-scroll-view-observation.md) -- [UINavigationItem support](uinavigationitem-support.md) -- [Interactive background views](interactive-background-views.md) -- [Adjusting the top layout guide of a view controller](../../FlexibleHeader/docs/top-layout-guide-adjustment.md) - -## Behavioral flags - -A behavioral flag is a temporary API that is introduced to allow client teams to migrate from an old -behavior to a new one in a graceful fashion. Behavioral flags all go through the following life -cycle: - -1. The flag is introduced. The default is chosen such that clients must opt in to the new behavior. -2. After some time, the default changes to the new behavior and the flag is marked as deprecated. -3. After some time, the flag is removed. - -- [Recommended behavioral flags](recommended-behavioral-flags.md) -- [Removing safe area insets from the min/max heights](../../FlexibleHeader/docs/behavior-minmax-safearea.md) -- [Enabling top layout guide adjustment](../../FlexibleHeader/docs/behavior-top-layout-adjustment.md) -- [Enabling inferred top safe area insets](../../FlexibleHeader/docs/behavior-inferred-top-safe-area-inset.md) - -See the [FlexibleHeader](../../FlexibleHeader) documentation for additional usage guides. - -## Extensions - -- [Theming](theming.md) - -## Accessibility - -- [Accessibility](accessibility.md) - -## Migration guides - -- [Migration guide: MDCAppBar to MDCAppBarViewController](migration-guide-appbar-appbarviewcontroller.md) - -## Unsupported - -- [Color Theming](color-theming.md) -- [Typography Theming](typography-theming.md) diff --git a/components/AppBar/docs/accessibility.md b/components/AppBar/docs/accessibility.md deleted file mode 100644 index 294708db1ec..00000000000 --- a/components/AppBar/docs/accessibility.md +++ /dev/null @@ -1,27 +0,0 @@ -### MDCAppBar Accessibility - -Because the App Bar mirrors the state of your view controller's navigationItem, making an App Bar accessible often -does not require any extra work. - -See the following examples: - -##### Objective-C -``` -self.navigationItem.rightBarButtonItem = - [[UIBarButtonItem alloc] initWithTitle:@"Right" - style:UIBarButtonItemStyleDone - target:nil - action:nil]; - -NSLog(@"accessibilityLabel: %@",self.navigationItem.rightBarButtonItem.accessibilityLabel); -// Prints out "accessibilityLabel: Right" -``` - -##### Swift -``` -self.navigationItem.rightBarButtonItem = - UIBarButtonItem(title: "Right", style: .done, target: nil, action: nil) - -print("accessibilityLabel: \(self.navigationItem.rightBarButtonItem.accessibilityLabel)") -// Prints out "accessibilityLabel: Right" -``` diff --git a/components/AppBar/docs/color-theming.md b/components/AppBar/docs/color-theming.md deleted file mode 100644 index 8618282ee59..00000000000 --- a/components/AppBar/docs/color-theming.md +++ /dev/null @@ -1,37 +0,0 @@ -### Color Theming - -You can theme an app bar with your app's color scheme using the ColorThemer extension. - -You must first add the Color Themer extension to your project: - -```bash -pod 'MaterialComponents/AppBar+ColorThemer' -``` - - -#### Swift -```swift -// Step 1: Import the ColorThemer extension -import MaterialComponents.MaterialAppBar_ColorThemer - -// Step 2: Create or get a color scheme -let colorScheme = MDCSemanticColorScheme() - -// Step 3: Apply the color scheme to your component -MDCAppBarColorThemer.applySemanticColorScheme(colorScheme, to: component) -``` - -#### Objective-C - -```objc -// Step 1: Import the ColorThemer extension -#import "MaterialAppBar+ColorThemer.h" - -// Step 2: Create or get a color scheme -id colorScheme = [[MDCSemanticColorScheme alloc] initWithDefaults:MDCColorSchemeDefaultsMaterial201804]; - -// Step 3: Apply the color scheme to your component -[MDCAppBarColorThemer applySemanticColorScheme:colorScheme - toAppBar:component]; -``` - diff --git a/components/AppBar/docs/interactive-background-views.md b/components/AppBar/docs/interactive-background-views.md deleted file mode 100644 index a2acc67f972..00000000000 --- a/components/AppBar/docs/interactive-background-views.md +++ /dev/null @@ -1,19 +0,0 @@ -### Interactive background views - -Scenario: you've added a background image to your App Bar and you'd now like to be able to tap the -background image. - -This is not trivial to do with the App Bar APIs due to considerations being discussed in -[Issue #184](https://github.com/material-components/material-components-ios/issues/184). - -The heart of the limitation is that we're using a view (`headerStackView`) to lay out the Navigation -Bar. If you add a background view behind the `headerStackView` instance then `headerStackView` will -end up eating all of your touch events. - -Until [Issue #184](https://github.com/material-components/material-components-ios/issues/184) is resolved, our recommendation for building interactive background views is the following: - -1. Do not use the App Bar component. -2. Create your own Flexible Header. Learn more by reading the Flexible Header - [Usage](../../FlexibleHeader/#usage) docs. -3. Add your views to this flexible header instance. -4. Create a Navigation Bar if you need one. Treat it like any other custom view. diff --git a/components/AppBar/docs/migration-guide-appbar-appbarviewcontroller.md b/components/AppBar/docs/migration-guide-appbar-appbarviewcontroller.md deleted file mode 100644 index e8ddc715e44..00000000000 --- a/components/AppBar/docs/migration-guide-appbar-appbarviewcontroller.md +++ /dev/null @@ -1,59 +0,0 @@ -### Migration guide: MDCAppBar to MDCAppBarViewController - -Deprecation schedule: - -- October 15, 2018: MDCAppBar and any references to it in MDC will deprecated. -- November 15, 2018: MDCAppBar and any references to it in MDC will be deleted. - -`MDCAppBarViewController` is a direct replacement for `MDCAppBar`. The migration essentially looks -like so: - -```swift -// Step 1 -- let appBar = MDCAppBar() -+ let appBarViewController = MDCAppBarViewController() - -// Step 2 -- self.addChildViewController(appBar.headerViewController) -+ self.addChildViewController(appBarViewController) - -// Step 3 -- appBar.addSubviewsToParent() -+ // Match the width of the parent view. -+ CGRect frame = appBarViewController.view.frame; -+ frame.origin.x = 0; -+ frame.size.width = appBarViewController.parentViewController.view.bounds.size.width; -+ appBarViewController.view.frame = frame; -+ -+ view.addSubview(appBarViewController.view) -+ appBarViewController.didMove(toParentViewController: self) -``` - -`MDCAppBarViewController` is a subclass of `MDCFlexibleHeaderViewController`, meaning you configure -an `MDCAppBarViewController` instance exactly the same way you'd configure an -`MDCFlexibleHeaderViewController` instance. - -`MDCAppBar` also already uses `MDCAppBarViewController` under the hood so you can directly replace -any references of `appBar.headerViewController` with `appBarViewController`. - -#### Swift find and replace recommendations - -| Find | Replace | -|:-----|:-------------| -| `let appBar = MDCAppBar()` | `let appBarViewController = MDCAppBarViewController()` | -| `self.addChildViewController(appBar.headerViewController)` | `self.addChildViewController(appBarViewController)` | -| `appBar.addSubviewsToParent()` | `view.addSubview(appBarViewController.view)`
`appBarViewController.didMove(toParentViewController: self)` | -| `self.appBar.headerViewController` | `self.appBarViewController` | - -#### Objective-C find and replace recommendations - -| Find | Replace | -|:-----|:-------------| -| `MDCAppBar *appBar;` | `MDCAppBarViewController *appBarViewController;` | -| `appBar = [[MDCAppBar alloc] init]` | `appBarViewController = [[MDCAppBarViewController alloc] init]` | -| `addChildViewController:appBar.headerViewController` | `addChildViewController:appBarViewController` | -| `[self.appBar addSubviewsToParent];` | `[self.view addSubview:self.appBarViewController.view];`
`[self.appBarViewController didMoveToParentViewController:self];` | - -#### Example migrations - -- [MDCCatalog examples](https://github.com/material-components/material-components-ios/commit/50e1fd091d8d08426f390c124bf6310c54174d8c) diff --git a/components/AppBar/docs/recommended-behavioral-flags.md b/components/AppBar/docs/recommended-behavioral-flags.md deleted file mode 100644 index 28a7f751a62..00000000000 --- a/components/AppBar/docs/recommended-behavioral-flags.md +++ /dev/null @@ -1,35 +0,0 @@ -### Recommended behavioral flags - -The app bar component and its dependencies include a variety of flags that affect the behavior of -the `MDCAppBarViewController`. Many of these flags represent feature flags that we are using -to allow client teams to migrate from an old behavior to a new, usually less-buggy one. - -You are encouraged to set all of the behavioral flags immediately after creating an instance of the -app bar. - -The minimal set of recommended flag values are: - - -#### Swift -```swift -// Enables support for iPad popovers and extensions. -// Automatically enables topLayoutGuideAdjustmentEnabled as well, but does not set a -// topLayoutGuideViewController. -appBarViewController.inferTopSafeAreaInsetFromViewController = true - -// Enables support for iPhone X safe area insets. -appBarViewController.headerView.minMaxHeightIncludesSafeArea = false -``` - -#### Objective-C - -```objc -// Enables support for iPad popovers and extensions. -// Automatically enables topLayoutGuideAdjustmentEnabled as well, but does not set a -// topLayoutGuideViewController. -appBarViewController.inferTopSafeAreaInsetFromViewController = YES; - -// Enables support for iPhone X safe area insets. -appBarViewController.headerView.minMaxHeightIncludesSafeArea = NO; -``` - diff --git a/components/AppBar/docs/theming.md b/components/AppBar/docs/theming.md deleted file mode 100644 index 2d501f0ea35..00000000000 --- a/components/AppBar/docs/theming.md +++ /dev/null @@ -1,46 +0,0 @@ -### Theming - -`MDCAppBarViewController` supports Material Theming using a Container Scheme. -There are two variants for Material Theming of an AppBar. The Surface Variant colors the App Bar -background to be `surfaceColor` and the Primary Variant colors the App Bar background to be -`primaryColor`. - - - -#### Swift - -```swift -// Import the AppBar Theming Extensions module -import MaterialComponents.MaterialAppBar_Theming - -... - -// Apply your app's Container Scheme to the App Bar controller -let containerScheme = MDCContainerScheme() - -// Either Primary Theme -appBarViewController.applyPrimaryTheme(withScheme: containerScheme) - -// Or Surface Theme -appBarViewController.applySurfaceTheme(withScheme: containerScheme) -``` - -#### Objective-C - -```objc -// Import the AppBar Theming Extensions header -#import "MaterialAppBar+Theming.h" - -... - -// Apply your app's Container Scheme to the App Bar controller -MDCContainerScheme *containerScheme = [[MDCContainerScheme alloc] init]; - -// Either Primary Theme -[self.appBarController applyPrimaryThemeWithScheme:containerScheme]; - -// Or Surface Theme -[self.appBarController applySurfaceThemeWithScheme:containerScheme]; -``` - - diff --git a/components/AppBar/docs/typical-use-child.md b/components/AppBar/docs/typical-use-child.md deleted file mode 100644 index bc87347637c..00000000000 --- a/components/AppBar/docs/typical-use-child.md +++ /dev/null @@ -1,61 +0,0 @@ -### Typical use: View controller containment, as a child - -When an `MDCAppBarViewController` instance is added as a child to another view controller. In this -case, the parent view controller is often the object that creates and manages the -`MDCAppBarViewController` instance. This allows the parent view controller to configure the app bar -directly. - -You'll typically push the parent onto a navigation controller, in which case you will also hide the -navigation controller's navigation bar using `UINavigationController`'s -`-setNavigationBarHidden:animated:`. - -#### Example - - -#### Swift -```swift -let appBarViewController = MDCAppBarViewController() - -override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) { - super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil) - - self.addChildViewController(appBarViewController) -} - -override func viewDidLoad() { - super.viewDidLoad() - - view.addSubview(appBarViewController.view) - appBarViewController.didMove(toParentViewController: self) -} -``` - -#### Objective-C - -```objc -@interface MyViewController () -@property(nonatomic, strong, nonnull) MDCAppBarViewController *appBarViewController; -@end - -@implementation MyViewController - -- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil { - self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]; - if (self) { - _appBarViewController = [[MDCAppBarViewController alloc] init]; - - [self addChildViewController:_appBarViewController]; - } - return self; -} - -- (void)viewDidLoad { - [super viewDidLoad]; - - [self.view addSubview:self.appBarViewController.view]; - [self.appBarViewController didMoveToParentViewController:self]; -} - -@end -``` - diff --git a/components/AppBar/docs/typical-use-container.md b/components/AppBar/docs/typical-use-container.md deleted file mode 100644 index c3b953e6a76..00000000000 --- a/components/AppBar/docs/typical-use-container.md +++ /dev/null @@ -1,37 +0,0 @@ -### Typical use: View controller containment, as a container - -There are cases where adding an `MDCAppBarViewController` as a child is not possible, most notably: - -- UIPageViewController's view is a horizontally-paging scroll view, meaning there is no fixed view - to which an app bar could be added. -- Any other view controller that animates its content horizontally without providing a fixed, - non-horizontally-moving parent view. - -In such cases, using `MDCAppBarContainerViewController` is preferred. -`MDCAppBarContainerViewController` is a simple container view controller that places a content view -controller as a sibling to an `MDCAppBarViewController`. - -**Note:** the trade off to using this API is that it will affect your view controller hierarchy. If -the view controller makes any assumptions about its parent view controller or its -navigationController properties then these assumptions may break once the view controller is -wrapped. - -You'll typically push the container view controller onto a navigation controller, in which case you -will also hide the navigation controller's navigation bar using UINavigationController's -`-setNavigationBarHidden:animated:`. - -#### Example - - -#### Swift -```swift -let container = MDCAppBarContainerViewController(contentViewController: <#T##UIViewController#>) -``` - -#### Objective-C - -```objc -MDCAppBarContainerViewController *container = - [[MDCAppBarContainerViewController alloc] initWithContentViewController:<#(nonnull UIViewController *)#>]; -``` - diff --git a/components/AppBar/docs/typical-use-navigation-controller.md b/components/AppBar/docs/typical-use-navigation-controller.md deleted file mode 100644 index acbfbc9a1b7..00000000000 --- a/components/AppBar/docs/typical-use-navigation-controller.md +++ /dev/null @@ -1,43 +0,0 @@ -### Typical use: View controller containment, as a navigation controller - -The easiest integration path for using the app bar is through the `MDCAppBarNavigationController`. -This API is a subclass of UINavigationController that automatically adds an -`MDCAppBarViewController` instance to each view controller that is pushed onto it, unless an app bar -or flexible header already exists. - -When using the `MDCAppBarNavigationController` you will, at a minimum, need to configure the added -app bar's background color using the delegate. - -#### Example - - -#### Swift -```swift -let navigationController = MDCAppBarNavigationController() -navigationController.pushViewController(<#T##viewController: UIViewController##UIViewController#>, animated: <#T##Bool#>) - -// MARK: MDCAppBarNavigationControllerDelegate - -func appBarNavigationController(_ navigationController: MDCAppBarNavigationController, - willAdd appBarViewController: MDCAppBarViewController, - asChildOf viewController: UIViewController) { - appBarViewController.headerView.backgroundColor = <#(UIColor)#> -} -``` - -#### Objective-C - -```objc -MDCAppBarNavigationController *navigationController = - [[MDCAppBarNavigationController alloc] init]; -[navigationController pushViewController:<#(nonnull UIViewController *)#> animated:<#(BOOL)#>]; - -#pragma mark - MDCAppBarNavigationControllerDelegate - -- (void)appBarNavigationController:(MDCAppBarNavigationController *)navigationController - willAddAppBarViewController:(MDCAppBarViewController *)appBarViewController - asChildOfViewController:(UIViewController *)viewController { - appBarViewController.headerView.backgroundColor = <#(nonnull UIColor *)#>; -} -``` - diff --git a/components/AppBar/docs/typography-theming.md b/components/AppBar/docs/typography-theming.md deleted file mode 100644 index c3f40abc1f9..00000000000 --- a/components/AppBar/docs/typography-theming.md +++ /dev/null @@ -1,37 +0,0 @@ -### Typography Theming - -You can theme an app bar with your app's typography scheme using the TypographyThemer extension. - -You must first add the Typography Themer extension to your project: - -```bash -pod 'MaterialComponents/AppBar+TypographyThemer' -``` - - -#### Swift -```swift -// Step 1: Import the TypographyThemer extension -import MaterialComponents.MaterialAppBar_TypographyThemer - -// Step 2: Create or get a typography scheme -let typographyScheme = MDCTypographyScheme() - -// Step 3: Apply the typography scheme to your component -MDCAppBarTypographyThemer.applyTypographyScheme(typographyScheme, to: component) -``` - -#### Objective-C - -```objc -// Step 1: Import the TypographyThemer extension -#import "MaterialAppBar+TypographyThemer.h" - -// Step 2: Create or get a typography scheme -id typographyScheme = [[MDCTypographyScheme alloc] init]; - -// Step 3: Apply the typography scheme to your component -[MDCAppBarTypographyThemer applyTypographyScheme:colorScheme - toAppBar:component]; -``` - diff --git a/components/AppBar/docs/uinavigationitem-support.md b/components/AppBar/docs/uinavigationitem-support.md deleted file mode 100644 index c84f896bc0a..00000000000 --- a/components/AppBar/docs/uinavigationitem-support.md +++ /dev/null @@ -1,9 +0,0 @@ -### UINavigationItem support - -The App Bar begins mirroring the state of your view controller's `navigationItem` in the provided -`navigationBar` once you call `addSubviewsToParent`. - -Learn more by reading the Navigation Bar section on -[Observing UINavigationItem instances](../../NavigationBar/#observing-uinavigationitem-instances). -Notably: read the section on "Exceptions" to understand which UINavigationItem are **not** -supported. diff --git a/components/AppBar/src/MDCAppBarContainerViewController.h b/components/AppBar/src/MDCAppBarContainerViewController.h index 4dc9c89df42..a0a874b9a30 100644 --- a/components/AppBar/src/MDCAppBarContainerViewController.h +++ b/components/AppBar/src/MDCAppBarContainerViewController.h @@ -64,8 +64,6 @@ @interface MDCAppBarContainerViewController (ToBeDeprecated) -#pragma mark - To be deprecated - /** If enabled, the content view controller's top layout guide will be adjusted as the flexible header's height changes and the content view controller view's frame will be set to the container diff --git a/components/BottomNavigation/src/MDCBottomNavigationBar.m b/components/BottomNavigation/src/MDCBottomNavigationBar.m index 7a39d0fbad4..29d1624a419 100644 --- a/components/BottomNavigation/src/MDCBottomNavigationBar.m +++ b/components/BottomNavigation/src/MDCBottomNavigationBar.m @@ -641,9 +641,13 @@ - (void)setItems:(NSArray *)items { #ifdef __IPHONE_13_4 if (@available(iOS 13.4, *)) { - UIPointerInteraction *pointerInteraction = - [[UIPointerInteraction alloc] initWithDelegate:self]; - [itemView addInteraction:pointerInteraction]; + // Because some iOS 13 betas did not have the UIPointerInteraction class, we need to verify + // that it exists before attempting to use it. + if (NSClassFromString(@"UIPointerInteraction")) { + UIPointerInteraction *pointerInteraction = + [[UIPointerInteraction alloc] initWithDelegate:self]; + [itemView addInteraction:pointerInteraction]; + } } #endif diff --git a/components/Buttons/src/MDCButton.h b/components/Buttons/src/MDCButton.h index 13b6741ae76..6670636ca23 100644 --- a/components/Buttons/src/MDCButton.h +++ b/components/Buttons/src/MDCButton.h @@ -178,16 +178,6 @@ */ @property(nullable, nonatomic, strong) id shapeGenerator; -/** - If @c true, @c accessiblityTraits will always include @c UIAccessibilityTraitButton. - If @c false, @c accessibilityTraits will inherit its behavior from @c UIButton. - - @note Defaults to true. - @note This API is intended as a migration flag to restore @c UIButton behavior to @c MDCButton. In - a future version, this API will eventually be deprecated and then deleted. - */ -@property(nonatomic, assign) BOOL accessibilityTraitsIncludesButton; - /** A block that is invoked when the MDCButton receives a call to @c traitCollectionDidChange:. The block is called after the call to the superclass. @@ -340,7 +330,9 @@ */ + (nonnull instancetype)buttonWithType:(UIButtonType)buttonType NS_UNAVAILABLE; -#pragma mark - To Be Deprecated +@end + +@interface MDCButton (ToBeDeprecated) /** Enables the state-based font behavior of the receiver. @@ -384,4 +376,14 @@ */ - (nullable UIFont *)titleFontForState:(UIControlState)state; +/** + If @c true, @c accessiblityTraits will always include @c UIAccessibilityTraitButton. + If @c false, @c accessibilityTraits will inherit its behavior from @c UIButton. + + @note Defaults to true. + @note This API is intended as a migration flag to restore @c UIButton behavior to @c MDCButton. In + a future version, this API will eventually be deprecated and then deleted. + */ +@property(nonatomic, assign) BOOL accessibilityTraitsIncludesButton; + @end diff --git a/components/Buttons/src/MDCButton.m b/components/Buttons/src/MDCButton.m index 8f43f42bd8e..b96aa57993b 100644 --- a/components/Buttons/src/MDCButton.m +++ b/components/Buttons/src/MDCButton.m @@ -114,6 +114,9 @@ @interface MDCButton () { @property(nonatomic, strong, readonly, nonnull) MDCStatefulRippleView *rippleView; @property(nonatomic, strong) MDCInkView *inkView; @property(nonatomic, readonly, strong) MDCShapedShadowLayer *layer; +@property(nonatomic, assign) BOOL accessibilityTraitsIncludesButton; +@property(nonatomic, assign) BOOL enableTitleFontForState; +@property(nonatomic) UIEdgeInsets hitAreaInsets; @end @implementation MDCButton diff --git a/components/Buttons/src/MDCFlatButton.h b/components/Buttons/src/MDCFlatButton.h index 0b7424d8798..09197e132f3 100644 --- a/components/Buttons/src/MDCFlatButton.h +++ b/components/Buttons/src/MDCFlatButton.h @@ -33,6 +33,10 @@ */ @interface MDCFlatButton : MDCButton +@end + +@interface MDCFlatButton (ToBeDeprecated) + /** Use an opaque background color (default is NO). diff --git a/components/Buttons/src/MDCFlatButton.m b/components/Buttons/src/MDCFlatButton.m index 2ff25bc25d2..748bd2e0b73 100644 --- a/components/Buttons/src/MDCFlatButton.m +++ b/components/Buttons/src/MDCFlatButton.m @@ -17,6 +17,10 @@ #import "MaterialShadowElevations.h" #import "private/MDCButton+Subclassing.h" +@interface MDCFlatButton () +@property(nonatomic) BOOL hasOpaqueBackground; +@end + @implementation MDCFlatButton + (void)initialize { diff --git a/components/Cards/docs/README.md b/components/Cards/docs/README.md deleted file mode 100644 index 3d588ed285d..00000000000 --- a/components/Cards/docs/README.md +++ /dev/null @@ -1,82 +0,0 @@ -# Cards - - - -Cards contain content and actions about a single subject. They can be used standalone, or as part -of a list. Cards are meant to be interactive, and aren't meant to be be used solely for style -purposes. - -
- Cards -
- - - - - -- - - - -## Overview - -Cards provides two different versions, `MDCCard` inheriting from `UIControl` and `MDCCardCollectionCell` inheriting from `UICollectionViewCell`. - -A card's state determines its visual styling. - -When treated as a `UIControl` (`MDCCard`), it has a default styling (`UIControlStateNormal`), and a highlighted styling (`UIControlStateHighlighted`) when interacted with. - -When treated as a `UICollectionViewCell` (`MDCCardCollectionCell`), it has a default styling (`MDCCardCellStateNormal`), a highlighted styling (`MDCCardCellStateHighlighted`), and lastly a selected styling (`MDCCardCellStateSelected`). - -Customization to the card is exposed via its API either in `MDCCard` or `MDCCardCollectionCell`. Currently the card consists of these customizations: - -- The border width for a specific state -- The border color for a specific state -- The shadow elevation for a specific state -- The shadow color for a specific state -- The corner radius for the card - -(`MDCCardCollectionCell` customization only): - -- Changing the image that appears in the Selected state. -- Changing the image tint color that appears in the Selected state. - -An `MDCCard` can be added and used as you would add any `UIView` or `UIControl`, if manually in code, or through Interface Builder. - -An `MDCCardCollectionCell` can be added, used, and reused as a `UICollectionViewCell`, if manually in code, or through Interface Builder. - -MDCCardThemer exposes apis to theme MDCCard and MDCCardCollectionCell instances as either a default or outlined variant. An outlined variant behaves identically to a default styled card, but differs in its coloring and in that it has a stroked border. Use 'applyScheme:toCard:' to style an instance with default values and 'applyOutlinedVariantWithScheme:toCard:' to style an instance with the outlined values. - -### Cards Classes - -#### MDCCard - -`MDCCard` subclasses `UIControl` and provides a simple class for developers to subclass and create custom cards with ink, shadows, corner radius, and stroke matching the Material spec. - -`MDCCard` uses the `highlighted` property that is built-in in `UIControl` and the `UIControlState` to move between states. - -#### MDCCardCollectionCell - -`MDCCardCollectionCell` subclasses `UICollectionViewCell` and provides a simple collection view cell for developers to use in their collections with ink, shadows, corner radius, and stroke matching the Material spec. - -`MDCCardCollectionCell` uses the `selected` property that is built-in in `UICollectionViewCell` and has its own `MDCCardCellState` to keep track of the current state it is in. - -## Installation - -- [Typical installation](../../../docs/component-installation.md) - -## Usage - -- [Typical use: as a view](typical-use-view.md) -- [Typical use: in a collection view](typical-use-collections.md) - -## Extensions - -- [Theming](theming.md) - -## Accessibility - -- [Accessibility Labels](accessibility.md) - -## Unsupported - -- [Color Theming](color-theming.md) -- [Shape Theming](shape-theming.md) diff --git a/components/Chips/examples/ChipsTypicalUseViewController.m b/components/Chips/examples/ChipsTypicalUseViewController.m index e6cbacdc01e..2bf6b5ea313 100644 --- a/components/Chips/examples/ChipsTypicalUseViewController.m +++ b/components/Chips/examples/ChipsTypicalUseViewController.m @@ -30,6 +30,8 @@ @interface ChipsTypicalUseViewController @property(nonatomic, strong) id containerScheme; @property(nonatomic) BOOL popRecognizerDelaysTouches; @property(nonatomic) UIEdgeInsets chipVisibleAreaInsets; +@property(nonatomic) CGSize chipSize; +@property(nonatomic) BOOL chipCenterVisibleArea; @end static ChipModel *MakeModel(NSString *title, @@ -174,6 +176,32 @@ - (void)collectionView:(UICollectionView *)collectionView [self updateClearButton]; } +- (CGSize)collectionView:(UICollectionView *)collectionView + layout:(UICollectionViewLayout *)collectionViewLayout + sizeForItemAtIndexPath:(NSIndexPath *)indexPath { + MDCChipView *chipView = (MDCChipView *)[collectionView cellForItemAtIndexPath:indexPath]; + if (!chipView) { + ChipModel *model = self.model[indexPath.row]; + chipView = [[MDCChipView alloc] init]; + chipView.enableRippleBehavior = YES; + chipView.titleLabel.text = model.title; + chipView.imageView.image = model.showProfilePic ? ChipsExampleAssets.faceImage : nil; + chipView.selectedImageView.image = model.showDoneImage ? ChipsExampleAssets.doneImage : nil; + chipView.accessoryView = model.showDeleteButton ? ChipsExampleAssets.deleteButton : nil; + chipView.centerVisibleArea = self.chipCenterVisibleArea; + if (!UIEdgeInsetsEqualToEdgeInsets(self.chipVisibleAreaInsets, UIEdgeInsetsZero)) { + chipView.visibleAreaInsets = self.chipVisibleAreaInsets; + } + [chipView applyThemeWithScheme:self.containerScheme]; + } + CGSize chipViewSize = [chipView intrinsicContentSize]; + if (!CGSizeEqualToSize(self.chipSize, CGSizeZero)) { + chipViewSize.height = MAX(self.chipSize.height, chipViewSize.height); + chipViewSize.width = MAX(self.chipSize.width, chipViewSize.width); + } + return chipViewSize; +} + @end @implementation ChipsTypicalUseViewController (CatalogByConvention) @@ -204,6 +232,15 @@ - (void)testVisibleAreaInsets { [self.collectionView reloadData]; } +- (void)testCustomSizeWhenCenterVisibleArea { + // Given + self.chipSize = CGSizeMake(44, 44); + self.chipCenterVisibleArea = YES; + + // When + [self.collectionView reloadData]; +} + - (void)setUp { self.chipVisibleAreaInsets = UIEdgeInsetsZero; } diff --git a/components/Chips/src/MDCChipView.h b/components/Chips/src/MDCChipView.h index fe5bf84375a..1a64d2575da 100644 --- a/components/Chips/src/MDCChipView.h +++ b/components/Chips/src/MDCChipView.h @@ -139,8 +139,7 @@ /** The corner radius for the chip. - Use this property to configure corner radius instead of @c self.layer.cornerRadius it works with - @c visibleAreaInsets. + Use this property to configure corner radius instead of @c self.layer.cornerRadius. By default, it is set to keep the chip fully rounded. */ @@ -189,12 +188,15 @@ @property(nonatomic, assign) UIEdgeInsets hitAreaInsets; /** -The inset or outset margins for the rectangle surrounding all of the chip’s visible area. + A Boolean value that determines whether the visible area is centered in the bounds of the view. -A positive value shrinks the visible area of the chip. A negative value expands the visible area -of the chip. + 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. This property + doesn't affect the result of @c sizeThatFits:. + + The default value is @c NO. */ -@property(nonatomic, assign) UIEdgeInsets visibleAreaInsets; +@property(nonatomic, assign) BOOL centerVisibleArea; /** A block that is invoked when the MDCChipView receives a call to @c @@ -344,4 +346,15 @@ of the chip. - (void)setTitleColor:(nullable UIColor *)titleColor forState:(UIControlState)state UI_APPEARANCE_SELECTOR; +#pragma mark - Deprecated + +/** +The inset or outset margins for the rectangle surrounding all of the chip’s visible area. + +A positive value shrinks the visible area of the chip. A negative value expands the visible area +of the chip. +*/ +@property(nonatomic, assign) UIEdgeInsets visibleAreaInsets + __attribute__((deprecated("Consider using centerVisibleArea to adjust visible area."))); + @end diff --git a/components/Chips/src/MDCChipView.m b/components/Chips/src/MDCChipView.m index 41cad5b5f4a..18596284af2 100644 --- a/components/Chips/src/MDCChipView.m +++ b/components/Chips/src/MDCChipView.m @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +#import "MDCChipView.h" #import "private/MDCChipView+Private.h" #import @@ -114,6 +115,8 @@ @interface MDCChipView () @property(nonatomic, strong) MDCInkView *inkView; @property(nonatomic, strong) MDCStatefulRippleView *rippleView; @property(nonatomic, readonly) CGFloat pixelScale; +@property(nonatomic, assign) UIEdgeInsets currentVisibleAreaInsets; +@property(nonatomic, assign) CGFloat currentCornerRadius; @end @implementation MDCChipView { @@ -134,6 +137,7 @@ @implementation MDCChipView { @synthesize mdc_overrideBaseElevation = _mdc_overrideBaseElevation; @synthesize mdc_elevationDidChangeBlock = _mdc_elevationDidChangeBlock; @synthesize cornerRadius = _cornerRadius; +@synthesize visibleAreaInsets = _visibleAreaInsets; @dynamic layer; @@ -213,6 +217,9 @@ - (instancetype)initWithFrame:(CGRect)frame { _imagePadding = MDCChipImagePadding; _titlePadding = MDCChipTitlePadding; _accessoryPadding = MDCChipAccessoryPadding; + _currentVisibleAreaInsets = UIEdgeInsetsZero; + _currentCornerRadius = 0.0f; + _centerVisibleArea = NO; // UIControl has a drag enter/exit boundary that is outside of the frame of the button itself. // Because this is not exposed externally, we can't use -touchesMoved: to calculate when to @@ -257,7 +264,8 @@ - (void)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection { - (void)setShapeGenerator:(id)shapeGenerator { if (!UIEdgeInsetsEqualToEdgeInsets(self.visibleAreaInsets, UIEdgeInsetsZero)) { - // When visibleAreaInsets is set, custom shapeGenerater is not allow to be set through setter. + // When visibleAreaInsets is not UIEdgeInsetsZero, the custom shapeGenerater should not be set + // through setter. return; } @@ -285,27 +293,41 @@ - (id)shapeGenerator { } - (void)configureLayerWithCornerRadius:(CGFloat)cornerRadius { - if (!self.shapeGenerator) { + if (!self.shapeGenerator && + UIEdgeInsetsEqualToEdgeInsets(self.visibleAreaInsets, UIEdgeInsetsZero)) { self.layer.cornerRadius = cornerRadius; self.layer.shadowPath = [UIBezierPath bezierPathWithRoundedRect:self.bounds cornerRadius:cornerRadius].CGPath; } else if (!UIEdgeInsetsEqualToEdgeInsets(self.visibleAreaInsets, UIEdgeInsetsZero)) { - MDCRectangleShapeGenerator *shapeGenerator = [[MDCRectangleShapeGenerator alloc] init]; - MDCCornerTreatment *cornerTreatment = - [[MDCRoundedCornerTreatment alloc] initWithRadius:cornerRadius]; - [shapeGenerator setCorners:cornerTreatment]; - shapeGenerator.topLeftCornerOffset = - CGPointMake(self.visibleAreaInsets.left, self.visibleAreaInsets.top); - shapeGenerator.topRightCornerOffset = - CGPointMake(-self.visibleAreaInsets.right, self.visibleAreaInsets.top); - shapeGenerator.bottomLeftCornerOffset = - CGPointMake(self.visibleAreaInsets.left, -self.visibleAreaInsets.bottom); - shapeGenerator.bottomRightCornerOffset = - CGPointMake(-self.visibleAreaInsets.right, -self.visibleAreaInsets.bottom); - [self configureLayerWithShapeGenerator:shapeGenerator]; + [self configureLayerWithVisibleAreaInsets:self.visibleAreaInsets cornerRadius:cornerRadius]; } } +- (void)configureLayerWithVisibleAreaInsets:(UIEdgeInsets)visibleAreaInsets + cornerRadius:(CGFloat)cornerRadius { + if (UIEdgeInsetsEqualToEdgeInsets(visibleAreaInsets, self.currentVisibleAreaInsets) && + MDCCGFloatEqual(self.currentCornerRadius, cornerRadius)) { + return; + } + + self.currentVisibleAreaInsets = visibleAreaInsets; + self.currentCornerRadius = cornerRadius; + + MDCRectangleShapeGenerator *shapeGenerator = [[MDCRectangleShapeGenerator alloc] init]; + MDCCornerTreatment *cornerTreatment = + [[MDCRoundedCornerTreatment alloc] initWithRadius:cornerRadius]; + [shapeGenerator setCorners:cornerTreatment]; + shapeGenerator.topLeftCornerOffset = CGPointMake(visibleAreaInsets.left, visibleAreaInsets.top); + shapeGenerator.topRightCornerOffset = + CGPointMake(-visibleAreaInsets.right, visibleAreaInsets.top); + shapeGenerator.bottomLeftCornerOffset = + CGPointMake(visibleAreaInsets.left, -visibleAreaInsets.bottom); + shapeGenerator.bottomRightCornerOffset = + CGPointMake(-visibleAreaInsets.right, -visibleAreaInsets.bottom); + + [self configureLayerWithShapeGenerator:shapeGenerator]; +} + - (void)setCornerRadius:(CGFloat)cornerRadius { _cornerRadius = cornerRadius; // When cornerRadius is set to a custom value, corner is not forced to be fully rounded. @@ -742,6 +764,27 @@ - (void)setVisibleAreaInsets:(UIEdgeInsets)visibleAreaInsets { } } +- (UIEdgeInsets)visibleAreaInsets { + if (!UIEdgeInsetsEqualToEdgeInsets(_visibleAreaInsets, UIEdgeInsetsZero)) { + // Use custom visibleAreaInsets value when users sets it. + return _visibleAreaInsets; + } + + UIEdgeInsets visibleAreaInsets = UIEdgeInsetsZero; + if (self.centerVisibleArea) { + CGSize visibleAreaSize = [self sizeThatFits:CGSizeZero]; + 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; + } + + return visibleAreaInsets; +} + #pragma mark - Control - (void)setEnabled:(BOOL)enabled { @@ -782,11 +825,12 @@ - (void)layoutSubviews { _selectedImageView.alpha = self.showSelectedImageView ? 1 : 0; + CGFloat cornerRadius = self.cornerRadius; if (self.shouldFullyRoundCorner) { CGRect visibleFrame = UIEdgeInsetsInsetRect(self.frame, self.visibleAreaInsets); - CGFloat cornerRadius = MIN(CGRectGetHeight(visibleFrame), CGRectGetWidth(visibleFrame)) / 2; - [self configureLayerWithCornerRadius:cornerRadius]; + cornerRadius = MIN(CGRectGetHeight(visibleFrame), CGRectGetWidth(visibleFrame)) / 2; } + [self configureLayerWithCornerRadius:cornerRadius]; // Handle RTL if (self.mdf_effectiveUserInterfaceLayoutDirection == UIUserInterfaceLayoutDirectionRightToLeft) { @@ -926,7 +970,7 @@ - (CGRect)titleLabelFrame { } - (CGSize)sizeThatFits:(CGSize)size { - CGSize visibleAreaSize = CGSizeShrinkWithInsets(size, self.visibleAreaInsets); + CGSize visibleAreaSize = CGSizeShrinkWithInsets(size, _visibleAreaInsets); CGSize contentPaddedSize = CGSizeShrinkWithInsets(visibleAreaSize, self.contentPadding); CGSize imagePaddedSize = CGSizeShrinkWithInsets(contentPaddedSize, self.imagePadding); CGSize titlePaddedSize = CGSizeShrinkWithInsets(contentPaddedSize, self.titlePadding); @@ -965,7 +1009,7 @@ - (CGSize)sizeThatFits:(CGSize)size { if (self.minimumSize.height > 0) { chipSize.height = MAX(self.minimumSize.height, chipSize.height); } - chipSize = CGSizeExpandWithInsets(chipSize, self.visibleAreaInsets); + chipSize = CGSizeExpandWithInsets(chipSize, _visibleAreaInsets); return MDCSizeCeilWithScale(chipSize, self.pixelScale); } diff --git a/components/Chips/tests/snapshot/MDCChipViewSnapshotTests.m b/components/Chips/tests/snapshot/MDCChipViewSnapshotTests.m index b8e20dd0b92..80aba5d3a0d 100644 --- a/components/Chips/tests/snapshot/MDCChipViewSnapshotTests.m +++ b/components/Chips/tests/snapshot/MDCChipViewSnapshotTests.m @@ -663,6 +663,16 @@ - (void)testChipWithCustomCornerRadiusAndResetShapeGeneratorToNil { [self generateSnapshotAndVerifyForView:self.chip]; } +// TODO(b/159934812): Remove this test after removing visibleAreaInsets. +- (void)testChipWithVisibleAreaInsets { + // When + self.chip.visibleAreaInsets = UIEdgeInsetsMake(20, 20, 20, 20); + + // Then + [self generateSnapshotAndVerifyForView:self.chip]; +} + +// TODO(b/159934812): Remove this test after removing visibleAreaInsets. - (void)testChipWithCustomCornerRadiusAndVisibleAreaInsets { // When self.chip.cornerRadius = 5; @@ -672,4 +682,48 @@ - (void)testChipWithCustomCornerRadiusAndVisibleAreaInsets { [self generateSnapshotAndVerifyForView:self.chip]; } +- (void)testChipWithCustomFrame { + // When + self.chip.bounds = CGRectMake(0, 0, 80, 44); + [self.chip layoutIfNeeded]; + + // Then + UIView *snapshotView = [self.chip mdc_addToBackgroundView]; + [self snapshotVerifyView:snapshotView]; +} + +- (void)testChipWithCustomCornerRadiusAndCustomFrame { + // When + self.chip.cornerRadius = 5; + self.chip.bounds = CGRectMake(0, 0, 80, 44); + [self.chip layoutIfNeeded]; + + // Then + UIView *snapshotView = [self.chip mdc_addToBackgroundView]; + [self snapshotVerifyView:snapshotView]; +} + +- (void)testChipWithCustomFrameWhenCenterVisibleArea { + // When + self.chip.centerVisibleArea = YES; + self.chip.bounds = CGRectMake(0, 0, 80, 44); + [self.chip layoutIfNeeded]; + + // Then + UIView *snapshotView = [self.chip mdc_addToBackgroundView]; + [self snapshotVerifyView:snapshotView]; +} + +- (void)testChipWithCustomCornerRadiusAndCustomFrameWhenCenterVisibleArea { + // When + self.chip.centerVisibleArea = YES; + self.chip.cornerRadius = 5; + self.chip.bounds = CGRectMake(0, 0, 80, 44); + [self.chip layoutIfNeeded]; + + // Then + UIView *snapshotView = [self.chip mdc_addToBackgroundView]; + [self snapshotVerifyView:snapshotView]; +} + @end diff --git a/components/CollectionCells/src/MDCCollectionViewCell.h b/components/CollectionCells/src/MDCCollectionViewCell.h index a3ec91a092c..40578fc665f 100644 --- a/components/CollectionCells/src/MDCCollectionViewCell.h +++ b/components/CollectionCells/src/MDCCollectionViewCell.h @@ -12,10 +12,11 @@ // See the License for the specific language governing permissions and // limitations under the License. -#import "MaterialInk.h" - #import +#import "MaterialInk.h" // IWYU pragma: export +#import "MaterialRipple.h" // IWYU pragma: export + /** The available cell accessory view types. Based on UITableViewCellAccessoryType. */ typedef NS_ENUM(NSUInteger, MDCCollectionViewCellAccessoryType) { /** Default value. No accessory view shown. */ @@ -103,4 +104,21 @@ FOUNDATION_EXPORT NSString *_Nonnull const kDeselectedCellAccessibilityHintKey; /** View containing the ink effect. */ @property(nonatomic, strong, nullable) MDCInkView *inkView; +/** + This property determines if an @c MDCCollectionViewCell should use the @c MDCInkView behavior or + not. + + By setting this property to @c YES, @c MDCRippleView is used to provide the user visual + touch feedback, instead of the legacy @c MDCInkView. + + @note Defaults to @c NO. + */ +@property(nonatomic, assign) BOOL enableRippleBehavior; + +/** +The rippleView for the cell that is initiated on tap. The ripple view is the successor of ink +view, and can be used by setting `enableRippleBehavior` to YES. +*/ +@property(nonatomic, strong, nullable) MDCRippleView *rippleView; + @end diff --git a/components/CollectionCells/src/MDCCollectionViewCell.m b/components/CollectionCells/src/MDCCollectionViewCell.m index 208bb91d63b..ef9f7b61037 100644 --- a/components/CollectionCells/src/MDCCollectionViewCell.m +++ b/components/CollectionCells/src/MDCCollectionViewCell.m @@ -17,13 +17,13 @@ #import #import "MaterialCollectionLayoutAttributes.h" +#import "MaterialPalettes.h" #import "MaterialIcons+ic_check.h" #import "MaterialIcons+ic_check_circle.h" #import "MaterialIcons+ic_chevron_right.h" #import "MaterialIcons+ic_info.h" #import "MaterialIcons+ic_radio_button_unchecked.h" #import "MaterialIcons+ic_reorder.h" -#import "MaterialPalettes.h" static CGFloat kEditingControlAppearanceOffset = 16; @@ -73,6 +73,7 @@ @implementation MDCCollectionViewCell { } @synthesize inkView = _inkView; +@synthesize rippleView = _rippleView; - (instancetype)initWithFrame:(CGRect)frame { self = [super initWithFrame:frame]; @@ -125,6 +126,7 @@ - (void)prepareForReuse { self.hidden = NO; [self.inkView cancelAllAnimationsAnimated:NO]; + [self.rippleView cancelAllRipplesAnimated:NO completion:nil]; } - (void)layoutSubviews { @@ -284,7 +286,9 @@ - (MDCInkView *)inkView { if (!_inkView) { _inkView = [[MDCInkView alloc] initWithFrame:self.bounds]; _inkView.usesLegacyInkRipple = NO; - [self addSubview:_inkView]; + if (!self.enableRippleBehavior) { + [self addSubview:_inkView]; + } } return _inkView; } @@ -296,12 +300,51 @@ - (void)setInkView:(MDCInkView *)inkView { if (_inkView) { [_inkView removeFromSuperview]; } - if (inkView) { + if (inkView && !self.enableRippleBehavior) { [self addSubview:inkView]; } _inkView = inkView; } +- (MDCRippleView *)rippleView { + if (!_rippleView) { + _rippleView = [[MDCRippleView alloc] initWithFrame:self.bounds]; + if (self.enableRippleBehavior) { + [self addSubview:_rippleView]; + } + } + return _rippleView; +} + +- (void)setRippleView:(MDCRippleView *)rippleView { + if (rippleView == _rippleView) { + return; + } + if (_rippleView) { + [_rippleView removeFromSuperview]; + } + if (rippleView && self.enableRippleBehavior) { + [self addSubview:rippleView]; + } + _rippleView = rippleView; +} + +- (void)setEnableRippleBehavior:(BOOL)enableRippleBehavior { + if (_enableRippleBehavior == enableRippleBehavior) { + return; + } + _enableRippleBehavior = enableRippleBehavior; + + if (self.enableRippleBehavior) { + [self.inkView removeFromSuperview]; + _rippleView.frame = self.bounds; + [self addSubview:_rippleView]; + } else { + [_rippleView removeFromSuperview]; + [self addSubview:_inkView]; + } +} + #pragma mark - Separator - (void)setShouldHideSeparator:(BOOL)shouldHideSeparator { diff --git a/components/CollectionCells/tests/unit/MDCCollectionViewCellTests.m b/components/CollectionCells/tests/unit/MDCCollectionViewCellTests.m index d598e4db585..e700d223c6b 100644 --- a/components/CollectionCells/tests/unit/MDCCollectionViewCellTests.m +++ b/components/CollectionCells/tests/unit/MDCCollectionViewCellTests.m @@ -37,4 +37,26 @@ - (void)testAccessoryChange { XCTAssertNotEqualObjects(originalImage, newImage); } +- (void)testRippleViewIsNotActiveByDefault { + // Given + MDCCollectionViewCell *cell = [[MDCCollectionViewCell alloc] initWithFrame:CGRectZero]; + + // Then + XCTAssertNil(cell.rippleView.superview); + XCTAssertNotNil(cell.inkView.superview); + XCTAssertFalse(cell.enableRippleBehavior); +} + +- (void)testSettingEnableRippleBehaviorToYes { + // Given + MDCCollectionViewCell *cell = [[MDCCollectionViewCell alloc] initWithFrame:CGRectZero]; + + // When + cell.enableRippleBehavior = YES; + + // Then + XCTAssertEqual(cell.rippleView.superview, cell); + XCTAssertNil(cell.inkView.superview); +} + @end diff --git a/components/Collections/examples/CollectionsRippleExample.m b/components/Collections/examples/CollectionsRippleExample.m new file mode 100644 index 00000000000..ac820fab118 --- /dev/null +++ b/components/Collections/examples/CollectionsRippleExample.m @@ -0,0 +1,92 @@ +// 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 "supplemental/CollectionsRippleExample.h" +#import "MaterialCollections.h" +#import "MaterialPalettes.h" + +static NSString *const kReusableIdentifierItem = @"itemCellIdentifier"; + +@implementation CollectionsRippleExample { + NSArray *_content; +} + +- (void)viewDidLoad { + [super viewDidLoad]; + + // Register cell class. + self.enableRippleBehavior = YES; + [self.collectionView registerClass:[MDCCollectionViewTextCell class] + forCellWithReuseIdentifier:kReusableIdentifierItem]; + + // Populate content. + _content = @[ + @"Default ripple color", @"Custom blue ripple color", @"Custom red ripple color", + @"Ripple hidden on this cell" + ]; + + // Customize collection view settings. + self.styler.cellStyle = MDCCollectionViewCellStyleCard; +} + +#pragma mark - + +- (NSInteger)collectionView:(UICollectionView *)collectionView + numberOfItemsInSection:(NSInteger)section { + return _content.count; +} + +- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView + cellForItemAtIndexPath:(NSIndexPath *)indexPath { + MDCCollectionViewTextCell *cell = + [collectionView dequeueReusableCellWithReuseIdentifier:kReusableIdentifierItem + forIndexPath:indexPath]; + cell.enableRippleBehavior = YES; + cell.textLabel.text = _content[indexPath.item]; + return cell; +} + +#pragma mark - + +- (BOOL)collectionView:(UICollectionView *)collectionView + hidesInkViewAtIndexPath:(NSIndexPath *)indexPath { + // In this example we are not showing ink at a single cell. + if (indexPath.item == 3) { + return YES; + } + return NO; +} + +- (UIColor *)collectionView:(UICollectionView *)collectionView + inkColorAtIndexPath:(NSIndexPath *)indexPath { + // Update cell ink colors. + if (indexPath.item == 1) { + return [MDCPalette.lightBluePalette.tint500 colorWithAlphaComponent:(CGFloat)0.2]; + } else if (indexPath.item == 2) { + return [UIColor colorWithRed:1 green:0 blue:0 alpha:(CGFloat)0.2]; + } + return nil; +} + +#pragma mark - CatalogByConvention + ++ (NSDictionary *)catalogMetadata { + return @{ + @"breadcrumbs" : @[ @"Collections", @"Cell Ripple Example" ], + @"primaryDemo" : @NO, + @"presentable" : @NO, + }; +} + +@end diff --git a/components/Collections/examples/supplemental/CollectionsRippleExample.h b/components/Collections/examples/supplemental/CollectionsRippleExample.h new file mode 100644 index 00000000000..679251e53ad --- /dev/null +++ b/components/Collections/examples/supplemental/CollectionsRippleExample.h @@ -0,0 +1,20 @@ +// 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 "MaterialCollections.h" + +@interface CollectionsRippleExample : MDCCollectionViewController +@end diff --git a/components/Collections/src/MDCCollectionViewController.h b/components/Collections/src/MDCCollectionViewController.h index a9370c70026..7e9cf15c504 100644 --- a/components/Collections/src/MDCCollectionViewController.h +++ b/components/Collections/src/MDCCollectionViewController.h @@ -16,7 +16,7 @@ #import #import "MDCCollectionViewEditingDelegate.h" -#import "MDCCollectionViewStylingDelegate.h" +#import "MDCCollectionViewStylingDelegate.h" // IWYU pragma: export @protocol MDCCollectionViewEditing; @protocol MDCCollectionViewStyling; @@ -41,6 +41,17 @@ /** The collection view editor. */ @property(nonatomic, strong, readonly, nonnull) id editor; +/** + This property determines if an @c MDCCollectionViewController should use the @c MDCInkView behavior + or not. + + By setting this property to @c YES, @c MDCRippleView is used to provide the user visual + touch feedback, instead of the legacy @c MDCInkView. + + @note Defaults to @c NO. + */ +@property(nonatomic, assign) BOOL enableRippleBehavior; + #pragma mark - Subclassing /** diff --git a/components/Collections/src/MDCCollectionViewController.m b/components/Collections/src/MDCCollectionViewController.m index 95348b0d29e..daf245d4fd4 100644 --- a/components/Collections/src/MDCCollectionViewController.m +++ b/components/Collections/src/MDCCollectionViewController.m @@ -14,13 +14,14 @@ #import "MDCCollectionViewController.h" -#import "MDCCollectionViewFlowLayout.h" -#import "MaterialCollectionCells.h" -#import "MaterialInk.h" #import "private/MDCCollectionInfoBarView.h" #import "private/MDCCollectionStringResources.h" #import "private/MDCCollectionViewEditor.h" #import "private/MDCCollectionViewStyler.h" +#import "MaterialCollectionCells.h" +#import "MDCCollectionViewFlowLayout.h" +#import "MaterialInk.h" +#import "MaterialRipple.h" #include @@ -28,12 +29,14 @@ NSString *const MDCCollectionInfoBarKindFooter = @"MDCCollectionInfoBarKindFooter"; @interface MDCCollectionViewController () + MDCInkTouchControllerDelegate, + MDCRippleTouchControllerDelegate> @property(nonatomic, assign) BOOL currentlyActiveInk; @end @implementation MDCCollectionViewController { MDCInkTouchController *_inkTouchController; + MDCRippleTouchController *_rippleTouchController; MDCCollectionInfoBarView *_headerInfoBar; MDCCollectionInfoBarView *_footerInfoBar; BOOL _headerInfoBarDismissed; @@ -101,6 +104,10 @@ - (void)viewDidLoad { _inkTouchController = [[MDCInkTouchController alloc] initWithView:self.collectionView]; _inkTouchController.delegate = self; + _rippleTouchController = [[MDCRippleTouchController alloc] initWithView:self.collectionView + deferred:YES]; + _rippleTouchController.delegate = self; + // Register our supplementary header and footer NSString *classIdentifier = NSStringFromClass([MDCCollectionInfoBarView class]); NSString *headerKind = MDCCollectionInfoBarKindHeader; @@ -134,8 +141,14 @@ - (void)setCollectionView:(__kindof UICollectionView *)collectionView { // Reset editor and ink to provided collection view. _editor = [[MDCCollectionViewEditor alloc] initWithCollectionView:collectionView]; _editor.delegate = self; - _inkTouchController = [[MDCInkTouchController alloc] initWithView:collectionView]; - _inkTouchController.delegate = self; + if (self.enableRippleBehavior) { + _rippleTouchController = [[MDCRippleTouchController alloc] initWithView:collectionView + deferred:YES]; + _rippleTouchController.delegate = self; + } else { + _inkTouchController = [[MDCInkTouchController alloc] initWithView:collectionView]; + _inkTouchController.delegate = self; + } } - (void)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection { @@ -394,15 +407,54 @@ - (MDCInkView *)inkTouchController:(MDCInkTouchController *)inkTouchController } if ([cell isKindOfClass:[MDCCollectionViewCell class]]) { MDCCollectionViewCell *inkCell = (MDCCollectionViewCell *)cell; - if ([inkCell respondsToSelector:@selector(inkView)]) { - // Set cell ink. - ink = [cell performSelector:@selector(inkView)]; + if (!inkCell.enableRippleBehavior) { + if ([inkCell respondsToSelector:@selector(inkView)]) { + // Set cell ink. + ink = [cell performSelector:@selector(inkView)]; + } } } return ink; } +#pragma mark - + +- (BOOL)rippleTouchController:(MDCRippleTouchController *)rippleTouchController + shouldProcessRippleTouchesAtTouchLocation:(CGPoint)location { + // Only store touch location and do not allow ripple processing. This ripple location will be used + // when manually starting/stopping the ripple animation during cell highlight/unhighlight states. + if (!self.currentlyActiveInk) { + _inkTouchLocation = location; + } + return NO; +} + +- (MDCRippleView *)rippleTouchController:(MDCRippleTouchController *)rippleTouchController + rippleViewAtTouchLocation:(CGPoint)location { + NSIndexPath *indexPath = [self.collectionView indexPathForItemAtPoint:location]; + UICollectionViewCell *cell = [self.collectionView cellForItemAtIndexPath:indexPath]; + MDCRippleView *ripple = nil; + + if ([_styler.delegate respondsToSelector:@selector(collectionView: + rippleTouchController:rippleViewAtIndexPath:)]) { + return [_styler.delegate collectionView:self.collectionView + rippleTouchController:rippleTouchController + rippleViewAtIndexPath:indexPath]; + } + if ([cell isKindOfClass:[MDCCollectionViewCell class]]) { + MDCCollectionViewCell *rippleCell = (MDCCollectionViewCell *)cell; + if (rippleCell.enableRippleBehavior) { + if ([rippleCell respondsToSelector:@selector(rippleView)]) { + // Set cell ripple. + ripple = [cell performSelector:@selector(rippleView)]; + } + } + } + + return ripple; +} + #pragma mark - - (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView @@ -453,24 +505,43 @@ - (void)collectionView:(UICollectionView *)collectionView UICollectionViewCell *cell = [collectionView cellForItemAtIndexPath:indexPath]; CGPoint location = [collectionView convertPoint:_inkTouchLocation toView:cell]; - // Start cell ink show animation. - MDCInkView *inkView; - if ([cell respondsToSelector:@selector(inkView)]) { - inkView = [cell performSelector:@selector(inkView)]; + if (self.enableRippleBehavior) { + MDCRippleView *rippleView; + if ([cell respondsToSelector:@selector(rippleView)]) { + rippleView = [cell performSelector:@selector(rippleView)]; + } else { + return; + } + + if ([_styler.delegate respondsToSelector:@selector(collectionView:inkColorAtIndexPath:)]) { + rippleView.rippleColor = [_styler.delegate collectionView:collectionView + inkColorAtIndexPath:indexPath]; + if (!rippleView.rippleColor) { + rippleView.rippleColor = [UIColor colorWithWhite:0 alpha:0.12f]; + } + } + self.currentlyActiveInk = YES; + [rippleView beginRippleTouchDownAtPoint:location animated:YES completion:nil]; } else { - return; - } + // Start cell ink show animation. + MDCInkView *inkView; + if ([cell respondsToSelector:@selector(inkView)]) { + inkView = [cell performSelector:@selector(inkView)]; + } else { + return; + } - // Update ink color if necessary. - if ([_styler.delegate respondsToSelector:@selector(collectionView:inkColorAtIndexPath:)]) { - inkView.inkColor = [_styler.delegate collectionView:collectionView - inkColorAtIndexPath:indexPath]; - if (!inkView.inkColor) { - inkView.inkColor = inkView.defaultInkColor; + // Update ink color if necessary. + if ([_styler.delegate respondsToSelector:@selector(collectionView:inkColorAtIndexPath:)]) { + inkView.inkColor = [_styler.delegate collectionView:collectionView + inkColorAtIndexPath:indexPath]; + if (!inkView.inkColor) { + inkView.inkColor = inkView.defaultInkColor; + } } + self.currentlyActiveInk = YES; + [inkView startTouchBeganAnimationAtPoint:location completion:nil]; } - self.currentlyActiveInk = YES; - [inkView startTouchBeganAnimationAtPoint:location completion:nil]; } - (void)collectionView:(UICollectionView *)collectionView @@ -478,16 +549,28 @@ - (void)collectionView:(UICollectionView *)collectionView UICollectionViewCell *cell = [collectionView cellForItemAtIndexPath:indexPath]; CGPoint location = [collectionView convertPoint:_inkTouchLocation toView:cell]; - // Start cell ink evaporate animation. - MDCInkView *inkView; - if ([cell respondsToSelector:@selector(inkView)]) { - inkView = [cell performSelector:@selector(inkView)]; + if (self.enableRippleBehavior) { + MDCRippleView *rippleView; + if ([cell respondsToSelector:@selector(rippleView)]) { + rippleView = [cell performSelector:@selector(rippleView)]; + } else { + return; + } + + self.currentlyActiveInk = NO; + [rippleView beginRippleTouchUpAnimated:YES completion:nil]; } else { - return; - } + // Start cell ink evaporate animation. + MDCInkView *inkView; + if ([cell respondsToSelector:@selector(inkView)]) { + inkView = [cell performSelector:@selector(inkView)]; + } else { + return; + } - self.currentlyActiveInk = NO; - [inkView startTouchEndedAnimationAtPoint:location completion:nil]; + self.currentlyActiveInk = NO; + [inkView startTouchEndedAnimationAtPoint:location completion:nil]; + } } - (BOOL)collectionView:(UICollectionView *)collectionView @@ -524,9 +607,15 @@ - (BOOL)collectionViewAllowsEditing:(__unused UICollectionView *)collectionView - (void)collectionViewWillBeginEditing:(__unused UICollectionView *)collectionView { if (self.currentlyActiveInk) { - MDCInkView *activeInkView = [self inkTouchController:_inkTouchController - inkViewAtTouchLocation:_inkTouchLocation]; - [activeInkView startTouchEndedAnimationAtPoint:_inkTouchLocation completion:nil]; + if (self.enableRippleBehavior) { + MDCRippleView *activeRippleView = [self rippleTouchController:_rippleTouchController + rippleViewAtTouchLocation:_inkTouchLocation]; + [activeRippleView beginRippleTouchUpAnimated:YES completion:nil]; + } else { + MDCInkView *activeInkView = [self inkTouchController:_inkTouchController + inkViewAtTouchLocation:_inkTouchLocation]; + [activeInkView startTouchEndedAnimationAtPoint:_inkTouchLocation completion:nil]; + } } // Inlay all items. _styler.allowsItemInlay = YES; diff --git a/components/Collections/src/MDCCollectionViewStylingDelegate.h b/components/Collections/src/MDCCollectionViewStylingDelegate.h index 357b32ae932..fcc9872c478 100644 --- a/components/Collections/src/MDCCollectionViewStylingDelegate.h +++ b/components/Collections/src/MDCCollectionViewStylingDelegate.h @@ -14,10 +14,12 @@ #import -#import "MDCCollectionViewStyling.h" +#import "MDCCollectionViewStyling.h" // IWYU pragma: export @class MDCInkTouchController; @class MDCInkView; +@class MDCRippleTouchController; +@class MDCRippleView; /** A delegate protocol which allows setting collection view cell styles. */ @protocol MDCCollectionViewStylingDelegate @@ -178,4 +180,16 @@ inkTouchController:(nonnull MDCInkTouchController *)inkTouchController inkViewAtIndexPath:(nonnull NSIndexPath *)indexPath; +/** + Allows the receiver to set the ripple view at the specified collection view index path. + + @param collectionView The collection view. + @param rippleTouchController The ripple controller of the ink view. + @param indexPath The collection view index path. + @return The rippleView to be used at the specified index path. + */ +- (nonnull MDCRippleView *)collectionView:(nonnull UICollectionView *)collectionView + rippleTouchController:(nonnull MDCRippleTouchController *)rippleTouchController + rippleViewAtIndexPath:(nonnull NSIndexPath *)indexPath; + @end diff --git a/components/Dialogs/README.md b/components/Dialogs/README.md index 0e93ca5759b..292248ee849 100644 --- a/components/Dialogs/README.md +++ b/components/Dialogs/README.md @@ -21,6 +21,15 @@ involve multiple tasks. gif of a dialog being presented and dismissed +There are four types of dialogs: + +1. [Alert](#alert-dialog) +1. [Simple](#simple-dialog) +1. [Confirmation](#confirmation-dialog) +1. [Full-screen](#full-screen-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. + ## Contents * [Using dialogs](#using-dialogs) @@ -30,31 +39,17 @@ involve multiple tasks. ## Using dialogs -The Dialogs component can be used to display Material Design alerts with things like title text and -message text, as well as optional icons and accessory views. It can also be used to modally present -custom dialogs. +Our dialogs offerings consist of three classes. -To use the Dialogs component when presenting your view controller, set its `modalPresentationStyle` +* `MDCAlertController` provides a basic alert interface with support for things like title text, message text, and optional accessory views. +* `MDCDialogTransitionController` is involved in the presentation of dialogs. It conforms to `UIViewControllerAnimatedTransitioning` and `UIViewControllerTransitioningDelegate`, and vends the presentation controllers to be used in presentation and dismissal transitions. It can be used in the presentation of `MDCAlertController`s as well as custom dialog view controller classes. +* `MDCDialogPresentationController` is the `UIPresentationController` subclass provided by `MDCDialogTransitionController`. + +To present either an `MDCAlertController` or a custom dialog, set its `modalPresentationStyle` property to `UIModalPresentationCustom` and its `transitioningDelegate` property to an instance of `MDCDialogTransitionController`. Then, present the view controller from the root controller. -### Dialogs Classes - -#### `MDCDialogPresentationController` and `MDCDialogTransitionController` - -The two classes involved in presenting dialogs are `MDCDialogPresentationController` and -`MDCDialogTransitionController`. These allow the presentation of view controllers in a Material -fashion. `MDCDialogPresentationController` is a subclass of `UIPresentationController` -that observes the presented view controller for preferred content size. -`MDCDialogTransitionController` implements `UIViewControllerAnimatedTransitioning` and -`UIViewControllerTransitioningDelegate` to vend the presentation controller during the transition. - -#### `MDCAlertController` - -`MDCAlertController` provides a basic alert interface that can be used when a custom dialog -view controller class is not necessary. - ### Installing dialogs In order to install Dialogs with [Cocoapods](https://guides.cocoapods.org/using/getting-started.html) first add the component to your `Podfile`: @@ -190,10 +185,10 @@ The following is an anatomy diagram of a Material dialog: ![anatomy](docs/assets/dialogs-anatomy.png) 1. Container -2. Title (optional) -3. Content -4. Buttons (optional) -5. Scrim +1. Title (optional) +1. Content +1. Buttons (optional) +1. Scrim #### Container attributes @@ -266,7 +261,7 @@ MDCContainerScheme *containerScheme = [[MDCContainerScheme alloc] init]; ### Theming Actions Actions in MDCAlertController have emphasis which affects how the Dialog's buttons will be themed. -High, Medium and low emphasis are supported. +High, Medium and Low emphasis are supported. #### Swift diff --git a/components/Dialogs/docs/README.md b/components/Dialogs/docs/README.md deleted file mode 100644 index 73626f1acc5..00000000000 --- a/components/Dialogs/docs/README.md +++ /dev/null @@ -1,54 +0,0 @@ -# Dialogs - - - -Dialogs inform users about a task and can contain critical information, require decisions, or -involve multiple tasks. - -
- Dialogs -
- - - - - -- - - - -## Overview - -To display a modal using MaterialDialogs you set two properties on the view controller to be -presented. Set modalPresentationStyle to UIModalPresentationCustom and set -transitioningDelegate to and instance of MDCDialogTransitionController. Then you present the -view controller from the root controller to display it as a modal dialog. - -### Presentation and transition controller - -Presenting dialogs uses two classes: MDCDialogPresentationController and -MDCDialogTransitionController. These allow the presentation of view controllers in a material -specificed manner. MDCDialogPresentationController is a subclass of UIPresentationController -that observes the presented view controller for preferred content size. -MDCDialogTransitionController implements UIViewControllerAnimatedTransitioning and -UIViewControllerTransitioningDelegate to vend the presentation controller during the transition. - -### Alert controller - -MDCAlertController provides a simple interface for developers to present a modal dialog -according to the Material spec. - -## Installation - -- [Typical installation](../../../docs/component-installation.md) - -## Usage - -- [Typical use: modal dialog](typical-use-modal-dialog.md) -- [Typical use: alert](typical-use-alert.md) - -## Extensions - -- [Theming](theming.md) - -## Accessibility - -- [Accessibility](accessibility.md) diff --git a/components/LibraryInfo/src/MDCLibraryInfo.m b/components/LibraryInfo/src/MDCLibraryInfo.m index 9c39b169d5b..785caea40cc 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 *MDCLibraryInfoVersionString = @"110.0.0"; +static NSString const *MDCLibraryInfoVersionString = @"110.1.0"; @implementation MDCLibraryInfo diff --git a/components/LibraryInfo/tests/unit/LibraryInfoTests.m b/components/LibraryInfo/tests/unit/LibraryInfoTests.m index 6b30378a37b..e8d9ccca340 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: "110.0.0", etc. + // Accept: "110.1.0", etc. // Reject: "0.0.0", "1.2", "1", "-1.2.3", "Hi, I'm a version 1.2.3", "1.2.3 is my version", etc. // // Note the major version must be >= 1 since "0.0.0" is used as the version when something goes diff --git a/components/NavigationDrawer/README.md b/components/NavigationDrawer/README.md index 8e0b3a446a9..209480e858e 100644 --- a/components/NavigationDrawer/README.md +++ b/components/NavigationDrawer/README.md @@ -20,7 +20,6 @@ Navigation drawers are recommended for:
  • Apps with five or more top-level destinations.
  • Apps with two or more levels of navigation hierarchy.
  • Quick navigation between unrelated destinations.
  • - ## Design & API documentation diff --git a/components/ProgressView/src/MDCProgressView.h b/components/ProgressView/src/MDCProgressView.h index 61f437eebf8..ea943c246ac 100644 --- a/components/ProgressView/src/MDCProgressView.h +++ b/components/ProgressView/src/MDCProgressView.h @@ -71,6 +71,9 @@ IB_DESIGNABLE This is not equivalent to configuring self.layer.cornerRadius; it instead configures the progress and track views directly. + Under @c MDCProgressViewModeIndeterminate mode, the progress view is fully rounded if this value + is larger than 0. + The default is 0. */ @property(nonatomic) CGFloat cornerRadius; diff --git a/components/ProgressView/src/MDCProgressView.m b/components/ProgressView/src/MDCProgressView.m index f0e9d12d9aa..49e72661f7c 100644 --- a/components/ProgressView/src/MDCProgressView.m +++ b/components/ProgressView/src/MDCProgressView.m @@ -32,15 +32,12 @@ static const NSTimeInterval MDCProgressViewAnimationDuration = 0.25; -static const NSTimeInterval kAnimationDuration = 1.8; - -static const CGFloat MDCProgressViewBarIndeterminateWidthPercentage = (CGFloat)0.52; - // The Bundle for string resources. static NSString *const kBundle = @"MaterialProgressView.bundle"; @interface MDCProgressView () @property(nonatomic, strong) MDCProgressGradientView *progressView; +@property(nonatomic, strong) MDCProgressGradientView *indeterminateProgressView; @property(nonatomic, strong) UIView *trackView; @property(nonatomic) BOOL animatingHide; // A UIProgressView to return the same format for the accessibility value. For example, when @@ -82,15 +79,19 @@ - (void)commonMDCProgressViewInit { _trackView.autoresizingMask = UIViewAutoresizingFlexibleWidth; [self addSubview:_trackView]; - CGFloat barWidth = [self indeterminateLoadingBarWidth]; - CGRect progressBarFrame = CGRectMake(-barWidth, 0, barWidth, CGRectGetHeight(self.bounds)); - - _progressView = [[MDCProgressGradientView alloc] initWithFrame:progressBarFrame]; + _progressView = [[MDCProgressGradientView alloc] initWithFrame:CGRectZero]; [self addSubview:_progressView]; + _indeterminateProgressView = [[MDCProgressGradientView alloc] initWithFrame:CGRectZero]; + _indeterminateProgressView.hidden = YES; + [self addSubview:_indeterminateProgressView]; + _progressView.colors = @[ (id)MDCProgressViewDefaultTintColor().CGColor, (id)MDCProgressViewDefaultTintColor().CGColor ]; + _indeterminateProgressView.colors = @[ + (id)MDCProgressViewDefaultTintColor().CGColor, (id)MDCProgressViewDefaultTintColor().CGColor + ]; _trackView.backgroundColor = [[self class] defaultTrackTintColorForProgressTintColor:MDCProgressViewDefaultTintColor()]; } @@ -106,13 +107,9 @@ - (void)layoutSubviews { // Don't update the views when the hide animation is in progress. if (!self.animatingHide) { [self updateProgressView]; + [self updateIndeterminateProgressView]; [self updateTrackView]; } - - if (_mode == MDCProgressViewModeIndeterminate && _animating) { - [self stopAnimating]; - [self startAnimating]; - } } - (void)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection { @@ -121,6 +118,8 @@ - (void)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection { if (self.progressTintColor) { self.progressView.colors = @[ (id)self.progressTintColor.CGColor, (id)self.progressTintColor.CGColor ]; + self.indeterminateProgressView.colors = + @[ (id)self.progressTintColor.CGColor, (id)self.progressTintColor.CGColor ]; } if (self.traitCollectionDidChangeBlock) { @@ -133,8 +132,11 @@ - (void)setProgressTintColor:(UIColor *)progressTintColor { _progressTintColors = nil; if (progressTintColor != nil) { self.progressView.colors = @[ (id)progressTintColor.CGColor, (id)progressTintColor.CGColor ]; + self.indeterminateProgressView.colors = + @[ (id)progressTintColor.CGColor, (id)progressTintColor.CGColor ]; } else { self.progressView.colors = nil; + self.indeterminateProgressView.colors = nil; } } @@ -142,6 +144,7 @@ - (void)setProgressTintColors:(NSArray *)progressTintColors { _progressTintColors = [progressTintColors copy]; _progressTintColor = nil; self.progressView.colors = _progressTintColors; + self.indeterminateProgressView.colors = _progressTintColors; } - (UIColor *)trackTintColor { @@ -158,21 +161,19 @@ - (void)setMode:(MDCProgressViewMode)mode { } _mode = mode; - // If the progress bar is animating in indeterminate mode, restart the animation. - if (_animating && _mode == MDCProgressViewModeIndeterminate) { - [self stopAnimating]; - [self startAnimating]; - } + self.indeterminateProgressView.hidden = (mode == MDCProgressViewModeDeterminate); } - (void)setCornerRadius:(CGFloat)cornerRadius { _cornerRadius = cornerRadius; _progressView.layer.cornerRadius = cornerRadius; + _indeterminateProgressView.layer.cornerRadius = cornerRadius; _trackView.layer.cornerRadius = cornerRadius; BOOL hasNonZeroCornerRadius = !MDCCGFloatIsExactlyZero(cornerRadius); _progressView.clipsToBounds = hasNonZeroCornerRadius; + _indeterminateProgressView.clipsToBounds = hasNonZeroCornerRadius; _trackView.clipsToBounds = hasNonZeroCornerRadius; } @@ -332,11 +333,16 @@ - (NSString *)defaultAccessibilityLabel { - (void)startAnimating { [self startAnimatingBar]; _animating = YES; + + [self setNeedsLayout]; } - (void)stopAnimating { _animating = NO; - [self.progressView.layer removeAllAnimations]; + [self.progressView.shapeLayer removeAllAnimations]; + [self.indeterminateProgressView.shapeLayer removeAllAnimations]; + + [self setNeedsLayout]; } #pragma mark - Resource Bundle @@ -382,20 +388,28 @@ + (UIColor *)defaultTrackTintColorForProgressTintColor:(UIColor *)progressTintCo } - (void)updateProgressView { - if (_mode == MDCProgressViewModeIndeterminate) { - return; + CGRect progressFrame = self.bounds; + if (_mode == MDCProgressViewModeDeterminate) { + // Update progressView with the current progress value. + CGFloat scale = self.window.screen.scale > 0 ? self.window.screen.scale : 1; + CGFloat pointWidth = self.progress * CGRectGetWidth(self.bounds); + CGFloat pixelAlignedWidth = MDCRound(pointWidth * scale) / scale; + progressFrame = CGRectMake(0, 0, pixelAlignedWidth, CGRectGetHeight(self.bounds)); + } else { + if (!self.animating) { + progressFrame = CGRectZero; + } } - // Update progressView with the current progress value. - CGFloat scale = self.window.screen.scale > 0 ? self.window.screen.scale : 1; - CGFloat pointWidth = self.progress * CGRectGetWidth(self.bounds); - CGFloat pixelAlignedWidth = MDCRound(pointWidth * scale) / scale; - CGRect progressFrame = CGRectMake(0, 0, pixelAlignedWidth, CGRectGetHeight(self.bounds)); if (self.mdf_effectiveUserInterfaceLayoutDirection == UIUserInterfaceLayoutDirectionRightToLeft) { progressFrame = MDFRectFlippedHorizontally(progressFrame, CGRectGetWidth(self.bounds)); } self.progressView.frame = progressFrame; } +- (void)updateIndeterminateProgressView { + self.indeterminateProgressView.frame = self.animating ? self.bounds : CGRectZero; +} + - (void)updateTrackView { const CGSize size = self.bounds.size; self.trackView.frame = self.hidden ? CGRectMake(0.0, size.height, size.width, 0.0) : self.bounds; @@ -407,23 +421,63 @@ - (void)startAnimatingBar { return; } - CGFloat barWidth = [self indeterminateLoadingBarWidth]; - CGRect progressBarStartFrame = CGRectMake(-barWidth, 0, barWidth, CGRectGetHeight(self.bounds)); - self.progressView.frame = progressBarStartFrame; - - CGPoint progressBarEndPoint = - CGPointMake(self.progressView.layer.position.x + CGRectGetWidth(self.bounds) + barWidth, - CGRectGetHeight(self.bounds) / 2); - CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"position"]; - animation.fromValue = [NSValue valueWithCGPoint:self.progressView.layer.position]; - animation.toValue = [NSValue valueWithCGPoint:progressBarEndPoint]; - animation.duration = kAnimationDuration; - animation.repeatCount = HUGE_VALF; - [self.progressView.layer addAnimation:animation forKey:@"position"]; -} - -- (CGFloat)indeterminateLoadingBarWidth { - return CGRectGetWidth(self.bounds) * MDCProgressViewBarIndeterminateWidthPercentage; + [self.progressView.shapeLayer removeAllAnimations]; + [self.indeterminateProgressView.shapeLayer removeAllAnimations]; + + // The numeric values used here conform to https://material.io/components/progress-indicators. + CABasicAnimation *progressViewHead = [CABasicAnimation animationWithKeyPath:@"strokeEnd"]; + progressViewHead.fromValue = @0; + progressViewHead.toValue = @1; + progressViewHead.duration = 0.75; + progressViewHead.timingFunction = + [[CAMediaTimingFunction alloc] initWithControlPoints:0.20f:0.00f:0.80f:1.00f]; + progressViewHead.fillMode = kCAFillModeBackwards; + + CABasicAnimation *progressViewTail = [CABasicAnimation animationWithKeyPath:@"strokeStart"]; + progressViewTail.beginTime = 0.333; + progressViewTail.fromValue = @0; + progressViewTail.toValue = @1; + progressViewTail.duration = 0.85; + progressViewTail.timingFunction = + [[CAMediaTimingFunction alloc] initWithControlPoints:0.40f:0.00f:1.00f:1.00f]; + progressViewTail.fillMode = kCAFillModeForwards; + + CAAnimationGroup *progressViewAnimationGroup = [[CAAnimationGroup alloc] init]; + progressViewAnimationGroup.animations = @[ progressViewHead, progressViewTail ]; + progressViewAnimationGroup.duration = 1.8; + progressViewAnimationGroup.repeatCount = HUGE_VALF; + + [self.progressView.shapeLayer addAnimation:progressViewAnimationGroup + forKey:@"kProgressViewAnimation"]; + + CABasicAnimation *indeterminateProgressViewHead = + [CABasicAnimation animationWithKeyPath:@"strokeEnd"]; + indeterminateProgressViewHead.fromValue = @0; + indeterminateProgressViewHead.toValue = @1; + indeterminateProgressViewHead.duration = 0.567; + indeterminateProgressViewHead.beginTime = 1; + indeterminateProgressViewHead.timingFunction = + [[CAMediaTimingFunction alloc] initWithControlPoints:0.00f:0.00f:0.65f:1.00f]; + indeterminateProgressViewHead.fillMode = kCAFillModeBackwards; + + CABasicAnimation *indeterminateProgressViewTail = + [CABasicAnimation animationWithKeyPath:@"strokeStart"]; + indeterminateProgressViewTail.beginTime = 1.267; + indeterminateProgressViewTail.fromValue = @0; + indeterminateProgressViewTail.toValue = @1; + indeterminateProgressViewTail.duration = 0.533; + indeterminateProgressViewTail.timingFunction = + [[CAMediaTimingFunction alloc] initWithControlPoints:0.10f:0.00f:0.45f:1.00f]; + indeterminateProgressViewTail.fillMode = kCAFillModeBackwards; + + CAAnimationGroup *indeterminateProgressViewAnimationGroup = [[CAAnimationGroup alloc] init]; + indeterminateProgressViewAnimationGroup.animations = + @[ indeterminateProgressViewHead, indeterminateProgressViewTail ]; + indeterminateProgressViewAnimationGroup.duration = 1.8; + indeterminateProgressViewAnimationGroup.repeatCount = HUGE_VALF; + + [self.indeterminateProgressView.shapeLayer addAnimation:indeterminateProgressViewAnimationGroup + forKey:@"kIndeterminateProgressViewAnimation"]; } @end diff --git a/components/ProgressView/src/private/MDCProgressGradientView.h b/components/ProgressView/src/private/MDCProgressGradientView.h index 76bced360c8..0eebbca1f6d 100644 --- a/components/ProgressView/src/private/MDCProgressGradientView.h +++ b/components/ProgressView/src/private/MDCProgressGradientView.h @@ -26,4 +26,9 @@ __attribute__((objc_subclassing_restricted)) @interface MDCProgressGradientView */ @property(nonatomic, nullable, copy) NSArray *colors; +/** + The shape layer used as the mask of @c MDCProgressGradientView. + */ +@property(nonatomic, nonnull, readonly) CAShapeLayer *shapeLayer; + @end diff --git a/components/ProgressView/src/private/MDCProgressGradientView.m b/components/ProgressView/src/private/MDCProgressGradientView.m index 0f4d8693d92..54166005c26 100644 --- a/components/ProgressView/src/private/MDCProgressGradientView.m +++ b/components/ProgressView/src/private/MDCProgressGradientView.m @@ -14,9 +14,12 @@ #import "MDCProgressGradientView.h" +#import + @interface MDCProgressGradientView () @property(nonatomic, readonly) CAGradientLayer *gradientLayer; +@property(nonatomic, readwrite) CAShapeLayer *shapeLayer; @end @@ -41,6 +44,32 @@ - (instancetype)initWithCoder:(NSCoder *)coder { - (void)commonMDCProgressGradientViewInit { self.gradientLayer.startPoint = CGPointMake(0.0f, 0.5f); self.gradientLayer.endPoint = CGPointMake(1.0f, 0.5f); + + self.shapeLayer = [CAShapeLayer layer]; + self.gradientLayer.mask = self.shapeLayer; +} + +- (void)layoutSubviews { + [super layoutSubviews]; + + UIBezierPath *path = [UIBezierPath bezierPath]; + CGPoint leftPoint = CGPointMake(0, CGRectGetMidY(self.gradientLayer.bounds)); + CGPoint rightPoint = CGPointMake(CGRectGetWidth(self.gradientLayer.bounds), + CGRectGetMidY(self.gradientLayer.bounds)); + if (self.mdf_effectiveUserInterfaceLayoutDirection == UIUserInterfaceLayoutDirectionRightToLeft) { + [path moveToPoint:rightPoint]; + [path addLineToPoint:leftPoint]; + } else { + [path moveToPoint:leftPoint]; + [path addLineToPoint:rightPoint]; + } + self.shapeLayer.frame = self.gradientLayer.bounds; + self.shapeLayer.strokeColor = UIColor.blackColor.CGColor; + self.shapeLayer.lineWidth = CGRectGetHeight(self.gradientLayer.bounds); + if (self.gradientLayer.cornerRadius > 0) { + self.shapeLayer.lineCap = kCALineCapRound; + } + self.shapeLayer.path = path.CGPath; } - (void)setColors:(NSArray *)colors { diff --git a/components/Snackbar/src/MDCSnackbarManager.h b/components/Snackbar/src/MDCSnackbarManager.h index 9da90facef0..cbbc5cb50e5 100644 --- a/components/Snackbar/src/MDCSnackbarManager.h +++ b/components/Snackbar/src/MDCSnackbarManager.h @@ -292,9 +292,7 @@ @protocol MDCSnackbarSuspensionToken @end -#pragma mark - To be deprecated - -@interface MDCSnackbarManager (LegacyAPI) +@interface MDCSnackbarManager (ToBeDeprecated) /** The @c alignment property of the @c defaultManager instance. diff --git a/components/Snackbar/src/MDCSnackbarManager.m b/components/Snackbar/src/MDCSnackbarManager.m index f20b7433403..2743e996082 100644 --- a/components/Snackbar/src/MDCSnackbarManager.m +++ b/components/Snackbar/src/MDCSnackbarManager.m @@ -903,7 +903,7 @@ - (void)dealloc { @end -@implementation MDCSnackbarManager (LegacyAPI) +@implementation MDCSnackbarManager (ToBeDeprecated) + (MDCSnackbarAlignment)alignment { return MDCSnackbarManager.defaultManager.alignment; diff --git a/components/TextControls/docs/README.md b/components/TextControls/docs/README.md deleted file mode 100644 index 2193b8d4205..00000000000 --- a/components/TextControls/docs/README.md +++ /dev/null @@ -1,39 +0,0 @@ -# TextControls - - - -TextControls are controls used for text input that make use of classes like UITextField and UITextView. - -
    - TextFields -
    - - - - - -- - - - -## Overview - -At this time, the only text control we offer is the text field. There are three text field classes: - -* MDCFilledTextField: A text field implementing the Material [filled style](https://material.io/components/text-fields/#filled-text-field) -* MDCOutlinedTextField: A text field implementing the Material [outlined style](https://material.io/components/text-fields/#outlined-text-field) -* MDCBaseTextField: An unstyled text field that the previous two inherit from - -## Installation - -- [Typical installation](installation.md) - -## Usage - -- [Typical use](typical-use.md) - -## Theming - -- [Theming](theming.md) - -## Examples - -- [Examples](examples.md) diff --git a/components/TextControls/docs/examples.md b/components/TextControls/docs/examples.md deleted file mode 100644 index 10cb58290ae..00000000000 --- a/components/TextControls/docs/examples.md +++ /dev/null @@ -1,26 +0,0 @@ -### Creating a text field - - -#### Swift - -```swift -let estimatedFrame = ... -let textField = MDCFilledTextField(frame: estimatedFrame) -textField.label.text = "This is the floating label" -textField.leadingAssistiveLabel.text = "This is helper text" -textField.sizeToFit() -view.addSubview(textField) -``` - -#### Objective-C - -```objc -CGRect estimatedFrame = ... -MDCOutlinedTextField *textField = [[MDCOutlinedTextField alloc] initWithFrame:estimatedFrame]; -textField.label.text = "This is the floating label"; -textField.leadingAssistiveLabel.text = "This is helper text"; -[textField sizeToFit]; -[view addSubview:textField]; -``` - - diff --git a/components/TextControls/docs/installation.md b/components/TextControls/docs/installation.md deleted file mode 100644 index e4e916f121a..00000000000 --- a/components/TextControls/docs/installation.md +++ /dev/null @@ -1,43 +0,0 @@ -### Installation with CocoaPods - -Add any of the following to your `Podfile`, depending on which TextControl target you're interested in: - -```bash -pod 'MaterialComponents/TextControls+FilledTextFields' -pod 'MaterialComponents/TextControls+FilledTextFieldsTheming' -pod 'MaterialComponents/TextControls+OutlinedTextFields' -pod 'MaterialComponents/TextControls+OutlinedTextFieldsTheming' -``` - - -Then, run the following command: - -```bash -pod install -``` - -### Importing - -To use TextControls in your code, import the appropriate MaterialTextControls umbrella header (Objective-C) or MaterialComponents module (Swift). - - -#### Swift - -```swift -import MaterialComponents.MaterialTextControls_FilledTextFields -import MaterialComponents.MaterialTextControls_FilledTextFieldsTheming -import MaterialComponents.MaterialTextControls_OutlinedTextFields -import MaterialComponents.MaterialTextControls_OutlinedTextFieldsTheming -``` - -#### Objective-C - -```objc -#import "MaterialTextControls+FilledTextFields.h" -#import "MaterialTextControls+FilledTextFieldsTheming.h" -#import "MaterialTextControls+OutlinedTextFields.h" -#import "MaterialTextControls+OutlinedTextFieldsTheming.h" -``` - - - diff --git a/components/TextControls/docs/theming.md b/components/TextControls/docs/theming.md deleted file mode 100644 index 56702e1f5d5..00000000000 --- a/components/TextControls/docs/theming.md +++ /dev/null @@ -1,41 +0,0 @@ -### Theming - -You can theme a text field to match the Material Design style by importing a theming extension. The content below assumes you have read the article on [Theming](../../../docs/theming.md). - -First, import the text field theming extension and create a text field. - - -#### Swift -```swift -import MaterialComponents.MaterialTextControls_OutlinedTextFieldsTheming - -let textField = MDCOutlinedTextField() -``` - -#### Objective-C - -```objc -#import - -MDCFilledTextField *filledTextField = [[MDCFilledTextField alloc] init]; -``` - - -Then pass a container scheme to one of the theming methods on the theming extension. - - -#### Swift -```swift -filledTextField.applyTheme(withScheme: containerScheme) -``` - -#### Objective-C -```objc -[self.filledTextField applyThemeWithScheme:self.containerScheme]; -``` - - - - - - diff --git a/components/TextControls/docs/typical-use.md b/components/TextControls/docs/typical-use.md deleted file mode 100644 index 4998988335e..00000000000 --- a/components/TextControls/docs/typical-use.md +++ /dev/null @@ -1,3 +0,0 @@ -### Text fields - -The largest difference between MDCTextControl text fields and UITextFields from a usability standpoint relates to the sizing behavior of MDCTextControl text fields. Where UITextField can be whatever height a user wants it to be, MDCTextControl text fields have heights that they need to be in order to look correct. The process for ensuring that MDCTextControl text fields have their preferred heights depends on whether one is in an Auto Layout or Manual Layout environment. In an Auto Layout environment, the text field's preferred height will be reflected in `intrinsicContentSize`, and the user will not have to do anything other than set a width constraint on the text field to ensure that the preferred height is achieved. In a Manual Layout environment, standard methods like `sizeThatFits:` or `sizeToFit` must be used to inform the frames of the text field. These methods assume that the text field already has the preferred width. diff --git a/components/TextFields/src/MDCMultilineTextField.h b/components/TextFields/src/MDCMultilineTextField.h index 6ff56b3d39c..49301dc292f 100644 --- a/components/TextFields/src/MDCMultilineTextField.h +++ b/components/TextFields/src/MDCMultilineTextField.h @@ -77,6 +77,13 @@ */ @property(nonatomic, nullable, strong) IBOutlet MDCIntrinsicHeightTextView *textView; +/** + * Whether or not the multiline text field should use its contraints to calculate its intrinsic + * content size. Default is NO, in which case the multiline text field gives an approximate + * intrinsic content size using its subviews. + */ +@property(nonatomic) BOOL useConstraintsForIntrinsicContentSize; + /** A block that is invoked when the @c MDCMultilineTextField receives a call to @c traitCollectionDidChange:. The block is called after the call to the superclass. diff --git a/components/TextFields/src/MDCMultilineTextField.m b/components/TextFields/src/MDCMultilineTextField.m index ed392985e45..485b34b1bf5 100644 --- a/components/TextFields/src/MDCMultilineTextField.m +++ b/components/TextFields/src/MDCMultilineTextField.m @@ -252,6 +252,9 @@ - (void)setupUnderlineConstraints { #pragma mark - Layout (UIView) - (CGSize)intrinsicContentSize { + if (self.useConstraintsForIntrinsicContentSize) { + return [super intrinsicContentSize]; + } CGSize boundingSize = CGSizeZero; boundingSize.width = UIViewNoIntrinsicMetric; @@ -343,10 +346,11 @@ - (void)updateConstraints { toItem:self attribute:NSLayoutAttributeBottom multiplier:1 - constant:-1 * MDCTextInputHalfPadding]; + constant:-1 * self.textInsets.bottom]; self.textViewBottomSuperviewBottom.priority = UILayoutPriorityDefaultLow; self.textViewBottomSuperviewBottom.active = YES; } + self.textViewBottomSuperviewBottom.constant = -1 * self.textInsets.bottom; if (!self.textViewTop) { self.textViewTop = [NSLayoutConstraint constraintWithItem:self.textView diff --git a/components/private/ThumbTrack/src/MDCThumbTrack.m b/components/private/ThumbTrack/src/MDCThumbTrack.m index 603a7b7e3f0..952242be4f5 100644 --- a/components/private/ThumbTrack/src/MDCThumbTrack.m +++ b/components/private/ThumbTrack/src/MDCThumbTrack.m @@ -63,96 +63,6 @@ return [[MDCTypography fontLoader] regularFontOfSize:12]; } -@implementation MDCDiscreteDotView - -- (instancetype)init { - self = [super init]; - if (self) { - self.backgroundColor = [UIColor clearColor]; - _inactiveDotColor = UIColor.blackColor; - _activeDotColor = UIColor.blackColor; - _activeDotsSegment = CGRectMake(CGFLOAT_MIN, 0, 0, 0); - } - return self; -} - -- (void)setFrame:(CGRect)frame { - [super setFrame:frame]; - [self setNeedsDisplay]; -} - -- (void)setActiveDotColor:(UIColor *)activeDotColor { - _activeDotColor = activeDotColor; - [self setNeedsDisplay]; -} - -- (void)setInactiveDotColor:(UIColor *)inactiveDotColor { - _inactiveDotColor = inactiveDotColor; - [self setNeedsDisplay]; -} - -- (void)setActiveDotsSegment:(CGRect)activeDotsSegment { - CGFloat newMinX = MAX(0, MIN(1, CGRectGetMinX(activeDotsSegment))); - CGFloat newMaxX = MIN(1, MAX(0, CGRectGetMaxX(activeDotsSegment))); - - _activeDotsSegment = CGRectMake(newMinX, 0, (newMaxX - newMinX), 0); - [self setNeedsDisplay]; -} - -- (void)drawRect:(CGRect)rect { - [super drawRect:rect]; - - if (_numDiscreteDots >= 2) { - CGContextRef contextRef = UIGraphicsGetCurrentContext(); - - // The "dot" is a circle that gradually transforms into a rounded rectangle. - // * At 1- and 2-point track heights, use a circle filling the height. - // * At 3- and 4-point track heights, use a vertically-centered circle 2 points tall. - // * At greater track heights, create a vertically-centered rounded rectangle 2-points wide - // and half the track height. - CGFloat trackHeight = CGRectGetHeight(self.bounds); - CGFloat dotHeight = MIN(2, trackHeight); - CGFloat dotWidth = MIN(2, trackHeight); - CGFloat circleOriginY = (trackHeight - dotHeight) / 2; - if (trackHeight > 4) { - dotHeight = trackHeight / 2; - circleOriginY = (trackHeight - dotHeight) / 2; - } - CGRect dotRect = CGRectMake(0, (trackHeight - dotHeight) / 2, dotWidth, dotHeight); - // Increment within the bounds - CGFloat absoluteIncrement = (CGRectGetWidth(self.bounds) - dotWidth) / (_numDiscreteDots - 1); - // Increment within 0..1 - CGFloat relativeIncrement = (CGFloat)1.0 / (_numDiscreteDots - 1); - - // Allow an extra 10% of the increment to guard against rounding errors excluding dots that - // should genuinely be within the active segment. - CGFloat minActiveX = CGRectGetMinX(self.activeDotsSegment) - relativeIncrement * (CGFloat)0.1; - CGFloat maxActiveX = CGRectGetMaxX(self.activeDotsSegment) + relativeIncrement * (CGFloat)0.1; - for (NSUInteger i = 0; i < _numDiscreteDots; i++) { - CGFloat relativePosition = i * relativeIncrement; - if (minActiveX <= relativePosition && maxActiveX >= relativePosition) { - [self.activeDotColor setFill]; - } else { - [self.inactiveDotColor setFill]; - } - dotRect.origin.x = (i * absoluteIncrement); - // Clear any previous paths from the context - CGContextBeginPath(contextRef); - CGPathRef rectPathRef = - CGPathCreateWithRoundedRect(dotRect, dotWidth / 2, dotWidth / 2, NULL); - CGContextAddPath(contextRef, rectPathRef); - CGContextFillPath(contextRef); - CGPathRelease(rectPathRef); - } - } -} - -- (void)setNumDiscreteDots:(NSUInteger)numDiscreteDots { - _numDiscreteDots = numDiscreteDots; - [self setNeedsDisplay]; -} - -@end // TODO(iangordon): Properly handle broken tgmath diff --git a/components/private/ThumbTrack/src/private/MDCDiscreteDotView.h b/components/private/ThumbTrack/src/private/MDCDiscreteDotView.h new file mode 100644 index 00000000000..85494b74a79 --- /dev/null +++ b/components/private/ThumbTrack/src/private/MDCDiscreteDotView.h @@ -0,0 +1,37 @@ +// 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 + +// Credit to the Beacon Tools iOS team for the idea for this implementations +@interface MDCDiscreteDotView : UIView + +@property(nonatomic, assign) NSUInteger numDiscreteDots; + +/** The color of dots within the @c activeDotsSegment bounds. Defaults to black. */ +@property(nonatomic, strong, nonnull) UIColor *activeDotColor; + +/** The color of dots outside the @c activeDotsSegment bounds. Defaults to black. */ +@property(nonatomic, strong, nonnull) UIColor *inactiveDotColor; + +/** + The segment of the track that uses @c activeDotColor. The horizontal dimension should be bound + to [0..1]. The vertical dimension is ignored. + + @note Only the @c origin.x and @c size.width are used to determine whether a dot is in the active + segment. + */ +@property(nonatomic, assign) CGRect activeDotsSegment; + +@end diff --git a/components/private/ThumbTrack/src/private/MDCDiscreteDotView.m b/components/private/ThumbTrack/src/private/MDCDiscreteDotView.m new file mode 100644 index 00000000000..35aacda2112 --- /dev/null +++ b/components/private/ThumbTrack/src/private/MDCDiscreteDotView.m @@ -0,0 +1,106 @@ +// 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 "MDCDiscreteDotView.h" + +@implementation MDCDiscreteDotView + +- (instancetype)init { + self = [super init]; + if (self) { + self.backgroundColor = [UIColor clearColor]; + _inactiveDotColor = UIColor.blackColor; + _activeDotColor = UIColor.blackColor; + _activeDotsSegment = CGRectMake(CGFLOAT_MIN, 0, 0, 0); + } + return self; +} + +- (void)setFrame:(CGRect)frame { + [super setFrame:frame]; + [self setNeedsDisplay]; +} + +- (void)setActiveDotColor:(UIColor *)activeDotColor { + _activeDotColor = activeDotColor; + [self setNeedsDisplay]; +} + +- (void)setInactiveDotColor:(UIColor *)inactiveDotColor { + _inactiveDotColor = inactiveDotColor; + [self setNeedsDisplay]; +} + +- (void)setActiveDotsSegment:(CGRect)activeDotsSegment { + CGFloat newMinX = MAX(0, MIN(1, CGRectGetMinX(activeDotsSegment))); + CGFloat newMaxX = MIN(1, MAX(0, CGRectGetMaxX(activeDotsSegment))); + + _activeDotsSegment = CGRectMake(newMinX, 0, (newMaxX - newMinX), 0); + [self setNeedsDisplay]; +} + +- (void)drawRect:(CGRect)rect { + [super drawRect:rect]; + + if (_numDiscreteDots >= 2) { + CGContextRef contextRef = UIGraphicsGetCurrentContext(); + + // The "dot" is a circle that gradually transforms into a rounded rectangle. + // * At 1- and 2-point track heights, use a circle filling the height. + // * At 3- and 4-point track heights, use a vertically-centered circle 2 points tall. + // * At greater track heights, create a vertically-centered rounded rectangle 2-points wide + // and half the track height. + CGFloat trackHeight = CGRectGetHeight(self.bounds); + CGFloat dotHeight = MIN(2, trackHeight); + CGFloat dotWidth = MIN(2, trackHeight); + CGFloat circleOriginY = (trackHeight - dotHeight) / 2; + if (trackHeight > 4) { + dotHeight = trackHeight / 2; + circleOriginY = (trackHeight - dotHeight) / 2; + } + CGRect dotRect = CGRectMake(0, (trackHeight - dotHeight) / 2, dotWidth, dotHeight); + // Increment within the bounds + CGFloat absoluteIncrement = (CGRectGetWidth(self.bounds) - dotWidth) / (_numDiscreteDots - 1); + // Increment within 0..1 + CGFloat relativeIncrement = (CGFloat)1.0 / (_numDiscreteDots - 1); + + // Allow an extra 10% of the increment to guard against rounding errors excluding dots that + // should genuinely be within the active segment. + CGFloat minActiveX = CGRectGetMinX(self.activeDotsSegment) - relativeIncrement * (CGFloat)0.1; + CGFloat maxActiveX = CGRectGetMaxX(self.activeDotsSegment) + relativeIncrement * (CGFloat)0.1; + for (NSUInteger i = 0; i < _numDiscreteDots; i++) { + CGFloat relativePosition = i * relativeIncrement; + if (minActiveX <= relativePosition && maxActiveX >= relativePosition) { + [self.activeDotColor setFill]; + } else { + [self.inactiveDotColor setFill]; + } + dotRect.origin.x = (i * absoluteIncrement); + // Clear any previous paths from the context + CGContextBeginPath(contextRef); + CGPathRef rectPathRef = + CGPathCreateWithRoundedRect(dotRect, dotWidth / 2, dotWidth / 2, NULL); + CGContextAddPath(contextRef, rectPathRef); + CGContextFillPath(contextRef); + CGPathRelease(rectPathRef); + } + } +} + +- (void)setNumDiscreteDots:(NSUInteger)numDiscreteDots { + _numDiscreteDots = numDiscreteDots; + [self setNeedsDisplay]; +} + +@end diff --git a/components/private/ThumbTrack/src/private/MDCThumbTrack+Private.h b/components/private/ThumbTrack/src/private/MDCThumbTrack+Private.h index 4bcd472cf39..67c6fc59659 100644 --- a/components/private/ThumbTrack/src/private/MDCThumbTrack+Private.h +++ b/components/private/ThumbTrack/src/private/MDCThumbTrack+Private.h @@ -12,31 +12,11 @@ // See the License for the specific language governing permissions and // limitations under the License. -#import "MDCNumericValueLabel.h" #import "MDCThumbTrack.h" -#import "MaterialInk.h" - -// Credit to the Beacon Tools iOS team for the idea for this implementations -@interface MDCDiscreteDotView : UIView - -@property(nonatomic, assign) NSUInteger numDiscreteDots; - -/** The color of dots within the @c activeDotsSegment bounds. Defaults to black. */ -@property(nonatomic, strong, nonnull) UIColor *activeDotColor; - -/** The color of dots outside the @c activeDotsSegment bounds. Defaults to black. */ -@property(nonatomic, strong, nonnull) UIColor *inactiveDotColor; -/** - The segment of the track that uses @c activeDotColor. The horizontal dimension should be bound - to [0..1]. The vertical dimension is ignored. - - @note Only the @c origin.x and @c size.width are used to determine whether a dot is in the active - segment. - */ -@property(nonatomic, assign) CGRect activeDotsSegment; - -@end +#import "MaterialInk.h" +#import "MDCNumericValueLabel.h" +#import "MDCDiscreteDotView.h" @interface MDCThumbTrack (Private) diff --git a/demos/supplemental/RemoteImageServiceForMDCDemos.podspec b/demos/supplemental/RemoteImageServiceForMDCDemos.podspec index 8a4626f0b0e..4df8ae8f13b 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 = "110.0.0" + s.version = "110.1.0" s.summary = "A helper image class for the MDC demos." s.description = "This spec is made for use in the MDC demos. It gets images via url." s.homepage = "https://github.com/material-components/material-components-ios"