# Decomposed

Manipulating and using `CATransform3D` for animations and interactions is pretty challenging… Decomposed makes `CATransform3D`, `matrix_double4x4`, and `matrix_float4x4` much easier to work with.

Note: The API for Decomposed is still heavily being changed / optimized, so please feel free to give feedback and expect breaking changes as time moves on.

Specifically, I really want to figure out what to do about the Vector types introduced in this repository. If there are any issues with them please let me know; I'm actively sourcing ways to make it better.

# Introduction

Typically on iOS if you wanted to transform a `CALayer` you'd do something like:

```let layer: CAlayer = ...
layer.transform = CATransform3DMakeScale(0.5, 0.5, 1.0)
```

However, what if you were given a transform from somewhere else? How would you know what the scale of the layer is? What if you wanted to set the scale or translation of a transform? Without lots of complex linear algebra, it's not easy to do!

Decomposed aims to simplify this by allowing for `CATransform3D`, `matrix_double4x4`, and `matrix_float4x4`, to be decomposed, recomposed, and mutated without complex math.

Decomposition is the act of breaking something down into smaller components, in this case transformation matrices into things like translation, scale, etc. in a way that they can all be individually changed or reset. The following are supported:

• Translation
• Scale
• Rotation (using Quaternions or Euler Angles)
• Skew
• Perspective

It's also powered by Accelerate, so it should introduce relatively low overhead for matrix manipulations.

# Usage

API Documentation is here.

## Transform Modifications

### Swift

Create a transform with a translation of 44pts on the Y-axis, rotated by .pi / 4.0 on the X-axis

```var transform: CATransform3D = CATransform3DIdentity
.translatedBy(y: 44.0)
.rotatedBy(angle: .pi / 4.0, x: 1.0)
```

### Objective-C

Create a transform with a translation of 44pts on the Y-axis, rotated by .pi / 40 on the X-axis.

``````CATransform3DDecomposed *decomposed = [DEDecomposedCATransform3D decomposedTransformWithTransform:CATransform3DIdentity];
decomposed.translation = CGPoint(0.0, 44.0);
decomposed.rotation = simd_quaternion(M_PI / 4.0, simd_make_double3(1.0, 0.0, 0.0));
transform = [decomposed recompose];
``````

## CALayer Extensions

Typically when doing interactive gestures with `UIView` and `CALayer` you'll wind up dealing with implicit actions (animations) when changing transforms. Instead of wrapping your code in `CATransactions`'s and disabling actions, Decomposed does this automatically for you.

### Swift

```// In some UIPanGestureRecognizer handling method
layer.translation = panGestureRecognizer.translation(in: self)
layer.scale = CGPoint(x: 0.75, y: 0.75)
```

### Objective-C

Since namespace collision happens in Objective-C, you're able to do similar changes via the `transformProxy` property. Changes to this proxy object will be applied to the layer's transform with implicit animations disabled.

``````// In some UIPanGestureRecognizer handling method
layer.transformProxy.translation = [panGestureRecognizer translationInView:self];
layer.transformProxy.scale = CGPoint(x: 0.75, y: 0.75);
``````

## DecomposedTransform

Anytime you change a property on a `CATransform3D` or `matrix_double4x4`, it needs to be decomposed, changed, and then recomposed. This can be expensive if done a lot, so it should be limited. If you're making multiple changes at once, it's better to change the `DecomposedTransform` and then call its `recomposed()` function to get a recomposed transform.

### Swift

```var decomposed = transform.decomposed()
decomposed.translation = Translation(44.0, 44.0, 0.0)
decomposed.scale = Scale(0.75, 0.75, 0.0)
decomposed.rotation = CGQuaternion(angle: .pi / 4.0, axis: CGVector3(1.0, 0.0, 0.0))

let changedTransform = decomposed.recomposed()
```

### Objective-C

``````DEDecomposedCATransform3D *decomposed = [DEDecomposedCATransform3D decomposedTransformWithTransform:transform];
decomposed.translation = CGPointMake(44.0, 44.0);
decomposed.scale = CGPointMake(0.75, 0.75);
decomposed.rotation = simd_quaternion(M_PI / 4.0, simd_make_double3(1.0, 0.0, 0.0));

CATransform3D changedTransform = [decomposed recomposed];
``````

## CGVector3 / CGVector4 / CGQuaternion

Sadly, `simd` doesn't support storing `CGFloat` (even when they're `Double`). To make this library easier to use (i.e. without casting everything to doubles all the time `Double(some CGFloat)` you'll find `CGVector3`, `CGVector4`, and `CGQuaternion`, which wrap `simd` counterparts: `simd_double3`, `simd_double4`, and `simd_quatd`, respectively.

`Translation`, `Scale`, etc. are all type aliased (i.e. `CGVector3` or `CGVector4`), and they all conform to `ArrayLiteralRepresentable` so you can use `Array<CGFloat>` to initialize them.

```layer.translation = Translation(44.0, 44.0, 0.0)
layer.scale = Scale(0.5, 0.75, 0.0)
```

Note: This API is questionable in its current form as it collides with Swift's `Vector` types (which are just simd types and part of me thinks everything should be exposed as `simd` types), so I'm happy to take feedback!

## Interpolatable

It also provides functionality to linearly interpolate from any transform to any transform via the `Interpolatable` protocol. This lets you easily animate / transition between transforms in a controlled manner.

```let transform: CATransform3D = CATransform3DIdentity
.translatedBy(x: 44.0, y: 44.0)

let transform2 = CATransform3DIdentity
.translatedBy(x: 120.0, y: 240.0)
.scaled(by: [0.5, 0.75, 1.0])
.rotatedBy(angle: .pi / 4.0, x: 1.0)

let interpolatedTransform = transform.lerp(to: transform2, fraction: 0.5)
```

# Installation

## Requirements

• iOS 13+, macOS 10.15+
• Swift 5.0 or higher

Currently Decomposed supports Swift Package Manager, CocoaPods, Carthage, being used as an xcframework, and being used manually as an Xcode subproject. Pull requests for other dependency systems / build systems are welcome!

## Swift Package Manager

Add the following to your `Package.swift` (or add it via Xcode's GUI):

```.package(url: "https://github.com/b3ll/Decomposed", from: "0.0.1")
```

## CocoaPods

Add the following to your `Podfile`:

`pod 'Decomposed'`

## xcframework

A built xcframework is available for each tagged release.

#### Notes

Get A Weekly Email With Trending Projects For These Topics
No Spam. Unsubscribe easily at any time.
swift (7,619
ios (3,544
macos (1,701
animation (1,041
matrix (136
simd (93
transformation (25
coreanimation (21
gestures (19