UIKit Dynamicsで物理演算を使った自然なアニメーション

# UIKit Dynamics
Viewに物理演算を用いたアニメーションを設定できます。
ドキュメントは[こちら](https://developer.apple.com/documentation/uikit/animation_and_haptics/uikit_dynamics
)

# UIDynamicAnimator
Viewに物理に基づいたアニメーションを提供する根幹となるもの

```swift:
    var dynamicAnimator: UIDynamicAnimator!
    override func viewDidLoad() {
        super.viewDidLoad()
        dynamicAnimator = UIDynamicAnimator(referenceView: self.view)
    }
} 
```
# 重力を与える
storyboard上に配置した赤いViewに条件を追加します。
重力を与えるにはUIGravityBehaviorを使用します。

```swift:
        let gravityBehavior = UIGravityBehavior(items: [redView])
        dynamicAnimator.addBehavior(gravityBehavior)  
```
ここでデフォルトの重力の大きさは1.0で、大きさ1.0は1000 point/second^2の加速度を意味します。
デフォルトの重力の向きは(0.0, 1.0)です。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/432519/9868c17c-01da-1d33-7a32-79b996d37fdf.gif" width="200">


### パラメータ
パラメータとして次のようなものが設定できます。

| プロパティ・メソッド名       | 型                 | 説明                     |
|:-----------------|:------------------|:-------------------------|
| gravityDirection | CGVector          | 重力ベクトル(大きさと方向)   |
| angle            | CGFloat           | 重力ベクトルの方向（ラジアン）|
| magnitude        | CGFloat           | 重力ベクトルの大きさ        |
| setAngle(_:, magnitude:)| (CGFloat, CGFloat) -> Void | 重力ベクトルの方向（ラジアン）と大きさ|
他にsetAngle(_ angle: CGFloat, magnitude: CGFloat)メソッドを使用しても設定できます。
例えばaddBehavior(_:)する前に以下のように設定すると、

```swift:
        gravityBehavior.gravityDirection = CGVector(dx: 2.0, dy: 2.0)
        gravityBehavior.magnitude = 1.0
```
一行目は右下に向かって重力の大きさ2.828..($2\sqrt{2} $)で動くような設定になります。
二行目でmagnitudeを1.0にすると、その方向を保ったままベクトルの大きさが1.0になるので最終的に重力ベクトルは(0.707..., 0.707...)となります。


# 衝突境界
衝突境界を追加したい場合にはUICollisionBehaviorを使用します。
### デバイスの枠を境界とする

translatesReferenceBoundsIntoBoundaryはreferenceViewを境界として扱うかどうかというBool値です。
referenceViewは最初にUIDynamicAnimatorを初期化した時に設定したViewです。
先ほどこれにself.viewを設定したので、この場合デバイスの画面の端が境界になると考えられます。

```swift:
        let collisionBehavior = UICollisionBehavior(items: [redView])
        collisionBehavior.translatesReferenceBoundsIntoBoundary = true
        dynamicAnimator.addBehavior(collisionBehavior)
```
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/432519/52127d78-abd0-c0d0-6ee1-3c07f0bdadd9.gif" width="200">

### 自由に境界を設定
CGPointでどこからどこまでを境界とみなすかを指定します。
第一引数で指定するIDは後でこの境界だけ削除する場合などに使います。

```swift:
        let collisionBehavior = UICollisionBehavior(items: [redView])
        collisionBehavior.addBoundary(withIdentifier: "floor" as NSCopying,
                              from: CGPoint(x: self.view.bounds.width/2 - 20,y: self.view.bounds.height/2 + 200),
                              to: CGPoint(x: self.view.bounds.width/2 + 20, y: self.view.bounds.height/2 + 200))
        dynamicAnimator.addBehavior(collisionBehavior)
```
この他にUIBezierPathを使用して境界を設定することもできます。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/432519/f1fc1f0b-074d-b893-d82c-d823d16f0aab.gif", width="200">

### パラメータ

collisionModeで何を衝突対象とするのかを指定できる。
CollisionBehavior.Modeで指定できるのは以下。

| CollisionBehavior.Mode      | 説明                     |動作 |
|:-----------------|:-------------------------|:-------------:|
| .items           | UICollisionBehaviorに紐付いているアイテム同士だけが衝突する|<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/432519/fdf56f46-35df-6600-07bb-56fbceb12184.gif", width="200">|
| .boundaries      | 設定した境界のみが衝突対象となり、アイテム同士は衝突しない|<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/432519/d70f75f0-21f9-12c9-ae43-8e7d75e718bc.gif", width="200">|
| .everything      | アイテム同士も境界も衝突する|<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/432519/6312631e-178d-1222-25cb-a7fe0953440d.gif", width="200">|

デフォルトは.everything。


# 弾性係数・抵抗・摩擦
ある物体に対する弾性係数や抵抗など初期条件になるようなものを設定するにはUIDynamicItemBehaviorを使用します。

``` swift:
        let dynamicItemBehavior = UIDynamicItemBehavior(items: [redView])
        dynamicItemBehavior.elasticity = 1.0
        dynamicItemBehavior.resistance = 0
        dynamicItemBehavior.friction = 0
        dynamicAnimator.addBehavior(dynamicItemBehavior)
```

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/432519/f35fb5fe-01e0-7180-4c1a-95712e02a904.gif", width="200">

### パラメータ
設定できる項目には以下のようなものがあります。

| プロパティ・メソッド名    | 型                 | 説明                     |
|:-------------------------|:------------------|:-------------------------|
| density               | CGFloat            | 相対的な質量密度(1.0の密度を持つ100 x 100ポイントのitemに、大きさ1.0の力を加えると、100 point/secondで加速する)   |
| elasticity            | CGFloat           | 弾性率の大きさ(1.0で完全弾性衝突)|
| friction              | CGFloat           | スライドする時にかかる摩擦の大きさ（1.0で強い摩擦、それ以上の値を指定することも可）|
| resistance            | CGFloat           | 抵抗の大きさ(CGFLOAT_MAXが最高値、1.0を設定した時は力が加えられなくなるとすぐに停止する)                  |
| allowsRotation           | Bool           | 回転を許可するか(デフォルトでtrue)|
| angularResistance        | CGFloat       | 角抵抗の大きさ|
| isAnchored        | Bool       | アイテムの位置が固定されているか|
| addLinearVelocity(_:, for:) |(CGPoint, UIDynamicItem) -> Void |速度を与える(1秒あたりに動くpoint数を指定)|
| addAngulerVelocity(_:, for:) |(CGFloat, UIDynamicItem) -> Void |角速度を与える(1秒あたりに動くラジアンを指定)|


# 外力
アイテムに外力を加えるにはUIPushBehaviorを使用します。
鉛直投げ上げをするとき、プログラムは以下のようになります。

``` swift:
        let pushBehavior = UIPushBehavior(items: [redView], mode: UIPushBehavior.Mode.instantaneous)
        pushBehavior.pushDirection = CGVector(dx: 0.0, dy: -5.0)
        dynamicAnimator.addBehavior(pushBehavior)
```
ここで1.0の力とは、連続で1.0の力を与えたとき、密度値が1.0の100ポイントx 100ポイントのビューが100 point/second^2の加速度を持つような大きさをいいます。この値をUIKit Newtonと呼びます。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/432519/10025e66-e954-0ee6-1d4d-04db6ee0ef0e.gif", width="200">

### パラメータ
| プロパティ名・メソッド名       | 型                 | 説明                     |
|:---------------------------|:------------------|:-------------------------|
| mode                       | UIPushBehavior.Mode| 力を加えるのが連続的(.continuous)か1度だけ(instantaneous)なのか|
| pushDirection               | CGFloat           | 力のベクトル（大きさと方向） |
| angle                      | CGFloat           | 力のベクトルの方向（ラジアン）|
| magnitude                  | CGFloat           | 力のベクトルの大きさ        |
| setAngle(_:, magnitude:)| (CGFloat, CGFloat) -> Void | 力のベクトルの方向（ラジアン）と大きさ|
| setTargetOffsetFromCenter(_:for:)| (UIOffset, UIdynamicItem) -> Void | 力が物体のどこにかかるか。指定しない場合は中心にかかる|

# その他
他にもバネのような動きを実現するUISnapBehaviorや2物体の関係を扱うUIAttachimentBehavior、電場や磁場を設定できるUIFieldBehaviorがあります。

# 備考

先ほどの自由落下させて完全弾性衝突するプログラムのredViewの開始位置に印をつけました。
誤差があり線を超えたり、線まで届かなかったりします。
大まかな動きを再現するには良いですが、ぴったり数ポイント分の動きを実現したい場合この誤差は無視できないように思います。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/432519/c022e808-4719-6cfd-ebc4-2be429c4a764.gif", width="200">