This delegate method allows clients to have one ripple touch controller control multiple ripple views. Also gives them more flexibility on the creation and reusing of ripple views.
PiperOrigin-RevId: 317614179
Adding a respondsToSelector check for "performAsCurrentTraitCollection" as there are cases where iOS 13+ devices do not recognize that API despite it being an available API since iOS 13.
Tested this with iOS 13+ and iOS 11.2 simulators to see it is working as expected.
PiperOrigin-RevId: 306215948
Follow-up to https://github.com/material-components/material-components-ios/pull/8808
Part of https://github.com/material-components/material-components-ios/issues/6913
From the docs:
tl;dr: If you are adding ripples to views with custom `layer.shadowPath` values, please disable
`usesSuperviewShadowLayerAsMask` and assign an explicit layer mask to the ripple view if needed
instead. usesSuperviewShadowLayerAsMask will eventually be disabled by default and then deleted.
MDCRippleView currently implements a convenience behavior that will inherit its parent view's
`layer.shadowPath` as the mask of the ripple view itself. This works for the general case where the
ripple view's frame equals the bounds of its super view, but behaves unexpectedly for any other
frame of the ripple view.
Due to the brittleness of this behavior, a new migration property, `usesSuperviewShadowLayerAsMask`,
has been added that will allow you to disable this behavior in favor of a more explicit
determination of the ripple's layer mask.
Example usage:
<!--<div class="material-code-render" markdown="1">-->
#### Swift
```swift
// During initialization:
rippleView.usesSuperviewShadowLayerAsMask = false
// Simple example of applying a mask to the ripple view using the ripple view's bounds:
let rippleViewMask = CAShapeLayer()
rippleViewMask.path = UIBezierPath(rect: rippleView.bounds).cgPath
rippleView.layer.mask = rippleViewMask
```
#### Objective-C
```objc
// During initialization:
rippleView.usesSuperviewShadowLayerAsMask = NO;
// Simple example of applying a mask to the ripple view using the ripple view's bounds:
CAShapeLayer *rippleViewMask = [[CAShapeLayer alloc] init];
rippleViewMask.path = [UIBezierPath bezierPathWithRect:rippleView.bounds].CGPath;
rippleView.layer.mask = rippleViewMask;
```
<!--</div>-->
Voice Over and Switch Control users can activate the Card to show Ripple.
The card has an accessibility label and an accessibility trait to indicate that it is interactive.
Closes#8914
All views in example are accessible with Voice Over and can be activated.
All views have a relevant accessibility label and the appropriate traits.
Closes#8913
These tests demonstrate how a ripple view added as a subview can end up with an incorrect layer mask if the ripple view's frame does not match the bounds of the parent view and the parent view's layer has a shadowPath. All of the tests intentionally pass, with the final test demonstrating the incorrect behavior.
In a follow-up change, I will be adding a behavioral flag to turn off this unexpected behavior in favor of a more explicit approach to configuring the ripple view's layer mask.
Part of https://github.com/material-components/material-components-ios/issues/6913
Changes:
- Create new initializer that allows to defer adding `MDCRippleView` to the a provided view
- Add `MDCRippleView` on first touch down if YES was passed in as deferred parameter
- Lazy getter for `rippleView`
- As `rippleView` property is not supposed to be overwritten always use `_rippleView` instance variable within touch controller
- Cache all delegate methods for faster lookup if a delegate responds to a selector
Determined improvements:
- Reduce memory overhead and safes a couple of cycles due to creation of `MDCRippleView` lazily
- Reduce overhead for adding / removing `MDCRippleView` to view hierarchies
- Reduce overhead of having the `MDCRippleView` that most of the time is hidden within view hierarchies as it will only get's added and immediately removed if needed
Currently in certain methods within MDCRippleView and MDCRippleLayer that are taking in an animated and a callback parameter if NO is passed in for the animated parameter the callback will not be called. This can get to problems if the consumer passes in different animated arguments based on some state and expects the callback is called.
This call is needed in cases where the ripple is added and stays, but then a re-layout occurs. This was found to be needed in internal testing, and seems to resolve those issues.
To improve performance when Ripple is being initialized, I have tried to look at what things are not needed, or can be delayed to a later stage, like the ripple invocation and not init.
self.backgroundColor is initially nil, which defaults to clearColor, hence we can remove that setting.
self.layer.maskToBounds = YES is redundant because maskToBounds is dependent on the rippleStyle which is set anyway in the method updateRippleStyle that is invoked.
updateRippleStyle does not need to be called in every layout of the view/subviews, because the masking of the ripple based on the shadowPath (if exists) is only important if there is an actual ripple layer being created, otherwise it is masking the "empty case"
The initialization of the maskLayer and setting the delegate only needs to be set when the maskLayer is actually used, so it is then set up if needed only if the ripple is bounded and there is a shadowPath, and the maskLayer is going to be used.
I've run instrumentation of Counters using an iPhone 5c 8GB which uses an A6 chip.
During instrumentation on high frequency, I had gathered that updateRippleStyle takes 0.1ms, and setting backgroundColor 0.2ms every time.
Updates the BUILD file to use more Starlark macros. Also drops the `includes`
update from the private target and fixes the imports in unit tests.
Part of #8150
Instead of reallocating new colors every time a default is required, using a static color can reduce memory use and allocations.
Impact: “memory watermark would be dropped in terms of KB. Memory fragmentation, cache performance, malloc contention will also be reduced slightly.”
Closes#8141
PiperOrigin-RevId: 260767570
All components should have a top-level umbrella header for their includes.
This allows easier refactoring of classes and files within the component.
Creating an umbrella for Color and using it outside the component.
Part of #8086
Because activeRippleColor was not a property prior, it had no way to keep its storage. Therefore, when setting it in layoutSubviews, we may not be setting the current value but rather an older value. This turns it into a property with storage.
Quoting issue found by @wenyuzhang666 :
"rippleColor was overwriting the color value of activeRippleLayer because there was no way to store the real active ripple color."