---
# System prepended metadata

title: Shape Component Proposal

---

# Shape Component Proposal

## Use cases
  - Developers should be able to apply `Shape` metrics to views.
      - Metrics:
          - `square`
          - `borderRadiusSmall`
          - `borderRadiusMedium`
          - `borderRadiusLarge`
      - We should provide a UIView extension to apply these `Shape` metrics
      - (!) We should ask designers to change the `Shape` metric naming in the Cookbook documentation to be more generic (`borderRadius8` -> `borderRadiusSmall`)
  - Developers should be able to build views with fully rounded corners and circular views.
      - We should provide UIView subclasses as components to accomplish this. Using a subclass approach (as opposed to applying a metric) will make it easier to support views that resize or animate
        - `FullyRoundedCornerView`
        - `CircleView`
      - We should support custom layout margins on these UIView subclasses. This will make it easier to layout content inside of the view.
      - Circular views will always maintain a circular shape, regardless of the view's dimensions
  - Developers should be able to apply `Shape` to specific corners of a view. 
  - Developers should be able to apply a corner radius curve on iOS 13 and above.
      - `CALayer.cornerCurve`
  - Objc will not be explicitly supported.
  - (Nice to have) Developers should be able to instantiate `Shape` instances with JSON
  - (Nice to have) Render images from `Shape` to support `UIImage`-only interfaces in UIKit

## States & Transitions

The `Shape` component has some simple state transitions and lifecycle events.
![shape_statetransitions](https://user-images.githubusercontent.com/44575795/67024569-a398a880-f0d2-11e9-885a-c32015b8d054.png)

### Applying Metrics
view has no shape -> apply shape -> remove shape

### `FullyRoundedCornerView` States
  - can be resized
  - content can be constrainted to layout margins

### `CircleView` States
  - can be resized, but doesn't change aspect ratio of circle
  ![resizing_circleview](https://user-images.githubusercontent.com/44575795/67024571-a4313f00-f0d2-11e9-88dc-496711acec79.png)
  - content can be constrainted to layout margins

### `FullyRoundedCornerView` & `CircleView` Lifecycle
![fullyroundedcornerview_circleview_lifecycle](https://user-images.githubusercontent.com/44575795/67024573-a4c9d580-f0d2-11e9-915a-7fdb3dc27cf1.png)

## Demonstration Plans
To demonstrate the `Shape` component, we will display all of the shape metrics that can be applied, and the `UIView` subclasses that will be provided.

iOS 13 Support
  - For all of the examples, have a toggle to switch between corner curve types.

Metrics
  - For each metric, show a view that has the metric applied
  - For at least one metric, show a view that has the metric applied to specific corners
  - For at least one pair of metrics, show that the metrics can be animated.

FullyRoundedCornerView
  - Show a rounded corner view with some content pinned to its custom layout margins.
  - Show an example of animating the bounds of the rounded corner view. 

CircleView
  - Show a circle view with some content pinned to its custom layout margins.
  - Show an example of animating the bounds of the circle view.
      - Emphasis on the fact that the circle does not change aspect ratio, even as its containing view does

## Accessibility
There are no substantial accessibility concerns for `Shape`.

## Architecture
The architecture for the `Shape` component will closely follow the use cases.

### Applying Metrics
  - We will provide a `Shape` object to hold shape metric data.
    (!) Talk to designers about why this is called borderRadius and not cornerRadius (confusing for iOS)
    
      ```swift
      struct Shape {
          let square: ShapeMetric
          let borderRadiusSmall: ShapeMetric
          let borderRadiusMedium: ShapeMetric
          let borderRadiusLarge: ShapeMetric
      }
      
      struct ShapeMetric {
          let cornerRadius: CGFloat
          let cornerCurve: CALayerCornerCurve?
      }
      ```
    
  - `ShapeMetrics` can be copied with changes for specific use cases
      ```swift
      extension ShapeMetric {
          @available(iOS 13, *)
          func metric(with cornerCurve: CALayerCornerCurve) -> ShapeMetric { }
      }
      ```
      
  - We will provide an extension for UIView/CALayer to apply metrics to specific corners of a view. 

    ```swift    
    extension UIView {
        func applyShape(_ metric: ShapeMetric, usingCorners corners: UIRectCorner) { ... }   
    }
    
    extension CALayer {
        func applyShape(...)
    }
    
    // example
    someView.applyShape(shape.borderRadiusSmall, usingCorners: [.topLeft, .topRight])
    ```

  - Users will be able to remove shape metrics by applying the `.square` metric to a view.
    ```swift
    UIView.applyShape(shape.square)
    ```  

  - We will support the iOS 13 `CALayer.cornerCurve` property. 
    ```swift    
    if #available(iOS 13.0, *) {
        layer.cornerCurve = shape.cornerCurve
    }
    ```

### FullyRoundedCornerView and CircleView
Developers will be able to use the `FullyRoundedCornerView` and `CircleView` to create custom user interfaces (either by subclasses or by including compositionally).

```swift
class FullyRoundedCornerView : UIView {

    public var roundedCorners: UIRectCorner { ... }

    override func layoutSubviews() {
        // cornerRadius will be updated to maintain fully rounded corners
        cornerRadius = ... 
        
        // layoutMargins will be updated to match corner radius
        layoutMargins = ...
        layoutMarginsDidChange()
    }
}

// Developers will access instances of FullyRoundedCornerView
extension Shape {
    func makeRoundedCornerView() -> FullyRoundedCornerView
}

// For iOS 13 support, cornerCurve is observed and applied by the view
let roundedCornerView = FullyRoundedCornerView()
roundedCornerView.layer.cornerCurve = .continuous
```

```swift
class CircularView : UIView {

    override func layoutSubviews() {
        // cornerRadius will be updated to maintain a perfect circle shape
        cornerRadius = ... 
        
        // layoutMargins will be updated to match corner radius
        layoutMargins = ...
        layoutMarginsDidChange()
    }
}
```


#### Layout Margins

Custom layout margins will be provided by `FullyRoundedCornerView` and `CircleView`, so that developers can constraint subviews to those margins using `UIView.layoutMargins` or `UIView.layoutMarginsGuide`. 

Layout margins will be calculated such that content added to one of these views will not enter the "rounded" area near the corners. 

![layoutmargins](https://user-images.githubusercontent.com/44575795/67024572-a4313f00-f0d2-11e9-8c11-38c03ec8a630.png)

  - (!) Need to investigate how `UIView.layoutMarginsDidChange` (and other methods) can be used to properly react to a change in the views size, and to integrate with other margin related methods.