Skip to content

Commit 14ace47

Browse files
authored
[Shapes] Added shapes documentation and linkage (#3666)
Pivotal: https://www.pivotaltracker.com/story/show/157013940
1 parent c166652 commit 14ace47

9 files changed

+266
-1
lines changed

docs/README.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ Apple's standard development tool chain.
1818
<li class="icon-list-item icon-list-item--guide"><a href="tutorial/">Tutorial</a></li>
1919
<li class="icon-list-item icon-list-item--guide"><a href="build-env/">Build environment</a></li>
2020
<li class="icon-list-item icon-list-item--guide"><a href="theming/">Theming</a></li>
21+
<li class="icon-list-item icon-list-item--guide"><a href="supporting-shapes/">Supporting Shapes</a></li>
2122
<li class="icon-list-item icon-list-item--guide"><a href="faq/">FAQ</a></li>
2223
</ul>
2324

@@ -30,4 +31,4 @@ Apple's standard development tool chain.
3031
- [Contributing](../contributing/)
3132
- [MDC-iOS on Stack Overflow](https://www.stackoverflow.com/questions/tagged/material-components+ios)
3233
- [Material.io](https://material.io)
33-
- [Material Design Guidelines](https://material.io/guidelines)
34+
- [Material Design Guidelines](https://material.io/guidelines)

docs/supporting-shapes/README.md

+264
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,264 @@
1+
<!--docs:
2+
title: "Shapes"
3+
layout: landing
4+
section: docs
5+
path: /docs/supporting-shapes/
6+
-->
7+
8+
# Shapes
9+
10+
The Shapes library offers a way to customize the shape of existing components, or expand your own view to have a custom shape.
11+
12+
Currently we offer built-in functionality for shapes within our <a href="../../components/Buttons">Buttons</a> and <a href="../../components/Cards">Cards</a> components.
13+
14+
### Shapes Library Preview
15+
16+
Our shapes library consists of different APIs and classes you can use to build a shape.
17+
18+
As you will see throughout the doc, we will be talking plentifully about the `shapeGenerator` object as part of our view/component. This object holds the generated shape and must conform to the `MDCShapeGenerating` protocol. `MDCShapeGenerating` consists of one method that returns the shape’s `CGPath` for the expected size.
19+
20+
```objc
21+
(CGPath *)pathForSize:(CGSize)size
22+
```
23+
24+
For our convenience we have a few classes under the hood that help us generate a shape. At the core we have `MDCPathGenerator` that has a few helper methods to build your own custom `CGPath` that will serve as the path for your shape.
25+
We also have an `MDCShapedShadowLayer` class that has to be used as the base layer of your view instead of `MDCShadowLayer` to allow shapes to work well with shadows, borders, and also background color. This is needed because such attributes must follow the shape's path rather than the normal bounds of the view.
26+
27+
`MDCShapedView` is a base `UIView` that already incorporates `MDCShapedShadowLayer`, a `shapeGenerator` object, and elevation support to provide a minimal view that has full shape support. This can be used as a basic building block to build on top of when building new components that need shape support from the get go.
28+
29+
`MDCCornerTreatment` and `MDCEdgeTreatment` are both classes that provide a more modular approach for defining specifically the `CGPath` for a specific edge or corner.
30+
31+
The last (but not least) class is `MDCRectangleShapeGenerator`. It acts as a `shapeGenerator` on its own (meaning it implements `MDCShapeGenerating`), and generates a `CGPath` but allows good customization as part of its implementation. It allows us to set each of its corners and edges by using its `MDCCornerTreatments` and `MDCEdgeTreatments`. With this class we can theoretically build any shape we want.
32+
33+
### Shapes Convenience Generators
34+
35+
We also have at our disposal convenience classes under the ShapeLibrary/ folder that generate specific shapes:
36+
37+
#### Corner and Edge generators
38+
39+
- **MDCCurvedCornerTreatment** Generates an `MDCCornerTreatment` that is curved and receives a size to define the size of the curve.
40+
- **MDCCutCornerTreatment** Generates an `MDCCornerTreatment` that is a cut corner and receives a cut to define by how much to cut.
41+
- **MDCRoundedCornerTreatment** Generates an `MDCCornerTreatment` that is rounded and receives a radius to define the radius of the rounding.
42+
- **MDCTriangleEdgeTreatment** Generates an `MDCEdgeTreatment` that consists of a triangle of a settable size and style.
43+
44+
#### Pre-made shape generators
45+
46+
- **MDCCurvedRectShapeGenerator** This generates a shape using `MDCRectangleShapeGenerator` with `MDCCurvedCornerTreatment` for its corners.
47+
48+
<img src="assets/MDCCurvedRectShapeGenerator.png" alt="MDCCurvedRectShapeGenerator" width="115">
49+
50+
- **MDCPillShapeGenerator** This generates a shape using `MDCRectangleShapeGenerator` with `MDCRoundedCornerTreatment` for its corners.
51+
52+
<img src="assets/MDCPillShapeGenerator.png" alt="MDCPillShapeGenerator" width="115">
53+
54+
- **MDCSlantedRectShapeGenerator** This generates a shape using `MDCRectangleShapeGenerator` and adds a slant to its corners using a simple offset to its corners.
55+
56+
<img src="assets/MDCSlantedRectShapeGenerator.png" alt="MDCSlantedRectShapeGenerator" width="115">
57+
58+
### Adding Shapes to your components
59+
60+
#### Exposing the shape generator
61+
62+
You will need to expose the shape generator in your component’s API to be able to set the shape. You will need to add a property in your .h file that is of the type `id<MDCShapeGenerating>`. A client can use any of the convenience pre-made shape generators noted above, including the base `MDCRectangleShapeGenerator`. Alternatively the developer can build his own shape by creating an object that implements `MDCShapeGenerating` and creating a path with the help of `MDCPathGenerator`.
63+
64+
```objc
65+
@property(nullable, nonatomic, strong) id<MDCShapeGenerating> shapeGenerator;
66+
```
67+
68+
#### Adding proper shadow, border, and background color support
69+
70+
If your component needs support either for shadow, border width/color customization, or even background color, then your component will need to use `MDCShapedShadowLayer` as its `CALayer`. This is a must as a shape is different than the frame/bounds of your view, and hence these properties need to abide by the shape's path.
71+
You will need to implement the `layerClass` in your view as follows:
72+
73+
```objc
74+
+ (Class)layerClass {
75+
return [MDCShapedShadowLayer class];
76+
}
77+
```
78+
79+
You then must also make sure that when setting your layer’s properties you must call the corresponding `shapedBorderColor`, `shapedBorderWidth`, and `shapedBackgroundColor` rather than the default `CALayer` properties.
80+
81+
#### Being conscious of setting layer’s properties such as shadowPath and cornerRadius
82+
83+
Because shapes work with a different path than the view’s default layer, you need to be conscious of places in your code where these layer's properties are set, as that might cause unexpected behaviors or override existing behaviors to support shapes. When setting such properties, it is advised to check if `(shapeGenerator != nil)` before applying them.
84+
85+
#### Supporting touch events
86+
87+
Shapes change our view’s default layer and therefore the layer no longer imitates the bounds. Due to that, we will now have cases where there are places in the bounds where the layer isn’t drawn, or places outside the bounds where the layer is drawn. Therefore we will need a way to support proper touch that would correspond to where the shape is and isn’t and act accordingly.
88+
To achieve this we can override the hitTest method of our UIView so we can check if the hitTest should return the view if the touch is part of our layer’s shape or not.
89+
90+
```objc
91+
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
92+
if (self.layer.shapeGenerator) {
93+
if (CGPathContainsPoint(self.layer.shapeLayer.path, nil, point, true)) {
94+
return self;
95+
} else {
96+
return nil;
97+
}
98+
}
99+
return [super hitTest:point withEvent:event];
100+
}
101+
```
102+
103+
#### Ink Ripple Support
104+
105+
If the component uses ink ripples, we will need to add shapes support for it. For ink ripples there are two things we must update, firstly is the `maxRippleRadius` and secondly is the masking to bounds. The `maxRippleRadius` must be updated in cases where the shape is either smaller or bigger than the bounds. In these cases we can’t rely on the bounds because for smaller shapes the ink will ripple too fast, and for bigger shapes the ripple won’t cover the entire shape. The ink layer’s `maskToBounds` needs to also be set to NO so we can allow the ink to spread outside of the bounds when the shape goes outside of the bounds.
106+
107+
```objc
108+
- (void)updateInkForShape {
109+
CGRect boundingBox = CGPathGetBoundingBox(self.layer.shapeLayer.path);
110+
self.inkView.maxRippleRadius =
111+
(CGFloat)(MDCHypot(CGRectGetHeight(boundingBox), CGRectGetWidth(boundingBox)) / 2 + 10.f);
112+
self.inkView.layer.masksToBounds = NO;
113+
}
114+
```
115+
116+
#### Content Margins
117+
118+
Now that our component can come in all different sizes and shapes, you will need to set a margin for the content so that the shape won't truncate the images/text etc. For that using the built-in UIView’s `layoutMargins` is recommended.
119+
120+
### Using Shapes in the already shape-supported components (Buttons / Cards)
121+
122+
If the component already supports shapes, it then already has an accessible id<MDCShapeGenerating> shapeGenerator property.
123+
In that case you only need to set the shapeGenerator to a shape of your choice and the component will be contained in that shape. There are available examples here:
124+
* <a href="../../components/Buttons/examples/ButtonsShapesExampleViewController.m">Shaped Buttons</a>
125+
* <a href="../../components/Cards/examples/ShapedCardViewController.swift ">Shaped Cards</a>
126+
127+
### Examples
128+
129+
#### Diamond FAB
130+
131+
<img src="assets/diamondfab.gif" alt="Diamond FAB.">
132+
133+
<!--<div class="material-code-render" markdown="1">-->
134+
##### Swift
135+
```swift
136+
let floatingButton = MDCFloatingButton()
137+
floatingButton.setImage(plusImage for:.normal)
138+
floatingButton.sizeToFit()
139+
140+
let floatingShapeGenerator = MDCRectangleShapeGenerator()
141+
floatingShapeGenerator.setCorners(MDCCutCornerTreatment(cut: floatingButton.bounds.width / 2))
142+
floatingButton.shapeGenerator = floatingShapeGenerator
143+
self.view.addSubview(floatingButton)
144+
```
145+
146+
##### Objective-C
147+
```objc
148+
self.floatingButton = [[MDCFloatingButton alloc] init];
149+
[self.floatingButton setImage:plusImage forState:UIControlStateNormal];
150+
[self.floatingButton sizeToFit];
151+
152+
MDCRectangleShapeGenerator *floatingShapeGenerator = [[MDCRectangleShapeGenerator alloc] init];
153+
[floatingShapeGenerator setCorners:
154+
[[MDCCutCornerTreatment alloc] initWithCut:CGRectGetWidth(self.floatingButton.bounds) / 2.f]];
155+
self.floatingButton.shapeGenerator = floatingShapeGenerator;
156+
[self.view addSubview:self.floatingButton];
157+
```
158+
<!--</div>-->
159+
160+
#### Cut Corners Contained Button
161+
162+
<img src="assets/cutcornersbutton.gif" alt="Cut Corners Button.">
163+
164+
<!--<div class="material-code-render" markdown="1">-->
165+
##### Swift
166+
```swift
167+
let containedButton = MDCButton()
168+
containedButton.setTitle("Add To Cart" for:.normal)
169+
containedButton.setImage(plusImage for:.normal)
170+
171+
let raisedShapeGenerator = MDCRectangleShapeGenerator()
172+
raisedShapeGenerator.setCorners(MDCCutCornerTreatment(cut: 8))
173+
containedButton.shapeGenerator = raisedShapeGenerator
174+
175+
containedButton.sizeToFit()
176+
self.view.addSubview(containedButton)
177+
```
178+
179+
##### Objective-C
180+
```objc
181+
MDCButton *containedButton = [[MDCButton alloc] init];
182+
[containedButton setTitle:@"Add To Cart" forState:UIControlStateNormal];
183+
[containedButton setImage:plusImage forState:UIControlStateNormal];
184+
185+
MDCRectangleShapeGenerator *raisedShapeGenerator =
186+
[[MDCRectangleShapeGenerator alloc] init];
187+
[raisedShapeGenerator setCorners:[[MDCCutCornerTreatment alloc] initWithCut:8.f]];
188+
containedButton.shapeGenerator = raisedShapeGenerator;
189+
190+
[containedButton sizeToFit];
191+
[self.view addSubview:containedButton];
192+
```
193+
<!--</div>-->
194+
195+
#### Top Left Cut Corner Card Cell
196+
197+
<img src="assets/cardcellcutcorner.gif" alt="Card Cell Cut Corner.">
198+
199+
<!--<div class="material-code-render" markdown="1">-->
200+
##### Swift
201+
```swift
202+
func collectionView(_ collectionView: UICollectionView,
203+
cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
204+
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: kReusableIdentifierItem,
205+
for: indexPath) as! MDCCardCollectionCell
206+
let shapeGenerator = MDCRectangleShapeGenerator()
207+
shapeGenerator.topLeftCorner = MDCCutCornerTreatment(cut: 20)
208+
cell.shapeGenerator = shapeGenerator
209+
return cell
210+
```
211+
212+
##### Objective-C
213+
```objc
214+
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView
215+
cellForItemAtIndexPath:(NSIndexPath *)indexPath {
216+
MDCCardCollectionCell *cell =
217+
[collectionView dequeueReusableCellWithReuseIdentifier:kReusableIdentifierItem
218+
forIndexPath:indexPath];
219+
MDCRectangleShapeGenerator *shapeGenerator =
220+
[[MDCRectangleShapeGenerator alloc] init];
221+
shapeGenerator.topLeftCorner = [[MDCCutCornerTreatment alloc] initWithCut:20];
222+
cell.shapeGenerator = shapeGenerator
223+
return cell;
224+
}
225+
```
226+
<!--</div>-->
227+
228+
#### Card with different corners
229+
230+
<img src="assets/cardwithdiffcorners.gif" alt="Card With Different Corners.">
231+
232+
<!--<div class="material-code-render" markdown="1">-->
233+
##### Swift
234+
```swift
235+
let card = MDCCard()
236+
let shapeGenerator = MDCRectangleShapeGenerator()
237+
let cutCorner = MDCCutCornerTreatment(cut: 20)
238+
let roundedCorner = MDCRoundedCornerTreatment(radius: 20)
239+
let curvedCorner = MDCCurvedCornerTreatment(size: CGSize(width: 20, height: 60))
240+
shapeGenerator.topLeftCorner = cutCorner
241+
shapeGenerator.topRightCorner = roundedCorner
242+
shapeGenerator.bottomLeftCorner = roundedCorner
243+
shapeGenerator.bottomRightCorner = curvedCorner
244+
card.shapeGenerator = shapeGenerator
245+
```
246+
247+
##### Objective-C
248+
```objc
249+
MDCCard *card = [[MDCCard alloc] init];
250+
MDCRectangleShapeGenerator *shapeGenerator =
251+
[[MDCRectangleShapeGenerator alloc] init];
252+
MDCCutCornerTreatment *curCorner = [[MDCCutCornerTreatment alloc] initWithCut:20];
253+
MDCRoundedCornerTreatment *roundedCorner =
254+
[[MDCRoundedCornerTreatment alloc] initWithRadius:20];
255+
MDCCurvedCornerTreatment *curvedCorner =
256+
[[MDCCurvedCornerTreatment alloc] initWithSize:CGSizeMake(20, 60)];
257+
shapeGenerator.topLeftCorner = cutCorner;
258+
shapeGenerator.topRightCorner = roundedCorner;
259+
shapeGenerator.bottomLeftCorner = roundedCorner;
260+
shapeGenerator.bottomRightCorner = curvedCorner;
261+
card.shapeGenerator = shapeGenerator;
262+
```
263+
<!--</div>-->
264+
Loading
Loading
Loading
Loading
Loading
Loading
13 KB
Loading

0 commit comments

Comments
 (0)