{"id":21198235,"url":"https://github.com/b3ll/decomposed","last_synced_at":"2025-04-09T22:15:49.427Z","repository":{"id":45354604,"uuid":"264374348","full_name":"b3ll/Decomposed","owner":"b3ll","description":"CATransform3D manipulation made easy.","archived":false,"fork":false,"pushed_at":"2022-12-12T02:48:11.000Z","size":2496,"stargazers_count":262,"open_issues_count":1,"forks_count":9,"subscribers_count":8,"default_branch":"main","last_synced_at":"2025-04-09T22:15:36.727Z","etag":null,"topics":["animation","calayer","catransform3d","coreanimation","gestures","ios","macos","matrix","simd","transformation"],"latest_commit_sha":null,"homepage":"","language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"bsd-2-clause","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/b3ll.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null},"funding":{"github":"b3ll"}},"created_at":"2020-05-16T06:28:02.000Z","updated_at":"2025-04-08T10:15:59.000Z","dependencies_parsed_at":"2022-07-16T20:00:48.102Z","dependency_job_id":null,"html_url":"https://github.com/b3ll/Decomposed","commit_stats":null,"previous_names":[],"tags_count":10,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/b3ll%2FDecomposed","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/b3ll%2FDecomposed/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/b3ll%2FDecomposed/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/b3ll%2FDecomposed/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/b3ll","download_url":"https://codeload.github.com/b3ll/Decomposed/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248119288,"owners_count":21050755,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["animation","calayer","catransform3d","coreanimation","gestures","ios","macos","matrix","simd","transformation"],"created_at":"2024-11-20T19:49:36.403Z","updated_at":"2025-04-09T22:15:49.336Z","avatar_url":"https://github.com/b3ll.png","language":"Swift","readme":"# Decomposed\n\n![Tests](https://github.com/b3ll/Decomposed/workflows/Tests/badge.svg)\n![Docs](https://github.com/b3ll/Decomposed/workflows/Docs/badge.svg)\n\nManipulating and using `CATransform3D` for animations and interactions is pretty challenging… Decomposed makes `CATransform3D`, `matrix_double4x4`, and `matrix_float4x4` much easier to work with.\n\n\u003cp align=\"center\"\u003e\n  \u003cimg width=\"240\" height=\"519\" src=\"https://github.com/b3ll/Decomposed/blob/main/Resources/Decomposed.gif?raw=true\"\u003e\n  \u003cimg width=\"240\" height=\"519\" src=\"https://github.com/b3ll/Decomposed/blob/main/Resources/Decomposed2.gif?raw=true\"\u003e\n\u003c/p\u003e\n\n**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.\n\nSpecifically, 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.\n\n- [Introduction](#introduction)\n- [Usage](#usage)\n  - [Transform Modifications](#transform-modifications)\n  - [CALayer Extensions](#calayer-extensions)\n  - [DecomposedTransform](#decomposedtransform)\n  - [CGVector3 / CGVector4 / CGQuaternion](#cgvector3--cgvector4--cgquaternion)\n  - [Interpolatable](#interpolatable)\n- [Installation](#installation)\n- [Other Recommendations](#other-recommendations)\n  - [Advance](#advance)\n- [License](#license)\n- [Contact Info](#contact-info)\n\n# Introduction\n\nTypically on iOS if you wanted to transform a `CALayer` you'd do something like:\n\n```swift\nlet layer: CAlayer = ...\nlayer.transform = CATransform3DMakeScale(0.5, 0.5, 1.0)\n```\n\nHowever, 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!\n\nDecomposed aims to simplify this by allowing for `CATransform3D`, `matrix_double4x4`, and `matrix_float4x4`, to be decomposed, recomposed, and mutated without complex math.\n\nDecomposition 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:\n\n- Translation\n- Scale\n- Rotation (using Quaternions or Euler Angles)\n- Skew\n- Perspective\n\nIt's also powered by Accelerate, so it should introduce relatively low overhead for matrix manipulations.\n\n# Usage\n\nAPI Documentation is [here](https://b3ll.github.io/Decomposed/).\n\n## Transform Modifications\n\n### Swift\n\nCreate a transform with a translation of 44pts on the Y-axis, rotated by .pi / 4.0 on the X-axis\n\n```swift\nvar transform: CATransform3D = CATransform3DIdentity\n  .translatedBy(y: 44.0)\n  .rotatedBy(angle: .pi / 4.0, x: 1.0)\n```\n\n### Objective-C\n\nCreate a transform with a translation of 44pts on the Y-axis, rotated by .pi / 40 on the X-axis.\n\n```objectivec\nCATransform3DDecomposed *decomposed = [DEDecomposedCATransform3D decomposedTransformWithTransform:CATransform3DIdentity];\ndecomposed.translation = CGPoint(0.0, 44.0);\ndecomposed.rotation = simd_quaternion(M_PI / 4.0, simd_make_double3(1.0, 0.0, 0.0));\ntransform = [decomposed recompose];\n```\n\n## CALayer Extensions\n\nTypically 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.\n\n### Swift\n\n```swift\n// In some UIPanGestureRecognizer handling method\nlayer.translation = panGestureRecognizer.translation(in: self)\nlayer.scale = CGPoint(x: 0.75, y: 0.75)\n```\n\n### Objective-C\n\nSince 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.\n\n```objectivec\n// In some UIPanGestureRecognizer handling method\nlayer.transformProxy.translation = [panGestureRecognizer translationInView:self];\nlayer.transformProxy.scale = CGPoint(x: 0.75, y: 0.75);\n```\n\n## DecomposedTransform\n\nAnytime 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.\n\n### Swift\n\n```swift\nvar decomposed = transform.decomposed()\ndecomposed.translation = Translation(44.0, 44.0, 0.0)\ndecomposed.scale = Scale(0.75, 0.75, 0.0)\ndecomposed.rotation = CGQuaternion(angle: .pi / 4.0, axis: CGVector3(1.0, 0.0, 0.0))\n\nlet changedTransform = decomposed.recomposed()\n```\n\n### Objective-C\n\n```objectivec\nDEDecomposedCATransform3D *decomposed = [DEDecomposedCATransform3D decomposedTransformWithTransform:transform];\ndecomposed.translation = CGPointMake(44.0, 44.0);\ndecomposed.scale = CGPointMake(0.75, 0.75);\ndecomposed.rotation = simd_quaternion(M_PI / 4.0, simd_make_double3(1.0, 0.0, 0.0));\n\nCATransform3D changedTransform = [decomposed recomposed];\n```\n\n## CGVector3 / CGVector4 / CGQuaternion\n\nSadly, `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.\n\n`Translation`, `Scale`, etc. are all type aliased (i.e. `CGVector3` or `CGVector4`), and they all conform to `ArrayLiteralRepresentable` so you can use `Array\u003cCGFloat\u003e` to initialize them.\n\n```swift\nlayer.translation = Translation(44.0, 44.0, 0.0)\nlayer.scale = Scale(0.5, 0.75, 0.0)\n```\n\n**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!\n\n## Interpolatable\n\nIt 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.\n\n```swift\nlet transform: CATransform3D = CATransform3DIdentity\n  .translatedBy(x: 44.0, y: 44.0)\n\nlet transform2 = CATransform3DIdentity\n  .translatedBy(x: 120.0, y: 240.0)\n  .scaled(by: [0.5, 0.75, 1.0])\n  .rotatedBy(angle: .pi / 4.0, x: 1.0)\n\nlet interpolatedTransform = transform.lerp(to: transform2, fraction: 0.5)\n```\n\n# Installation\n\n## Requirements\n\n- iOS 13+, macOS 10.15+\n- Swift 5.0 or higher\n\nCurrently 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!\n\n## Swift Package Manager\n\nAdd the following to your `Package.swift` (or add it via Xcode's GUI):\n\n```swift\n.package(url: \"https://github.com/b3ll/Decomposed\", from: \"0.0.1\")\n```\n\n## CocoaPods\n\nAdd the following to your `Podfile`:\n\n`pod 'Decomposed'`\n\n## xcframework\n\nA built xcframework is available for each tagged release.\n\n#### Notes\n\nFor some reason, when using CocoaPods and the Objective-C parts of this library, you may see an error like:\n\n`Declaration of 'DEDecomposedCATransform3D' must be imported from module 'Decomposed.Swift' before it is required`.\n\nTo fix this, you'll need to use Objective-C++ files (i.e. rename from `.m` to `.mm`) or change your imports to:\n\n```objective\n#import \u003cDecomposed/Decomposed.h\u003e\n#import \u003cDecomposed/Decomposed-Swift.h\u003e\n```\n\nI don't have any other workarounds at the moment, but if I do, I'll make an update.\n\n## Carthage\n\nAdd the following to your `Cartfile`:\n\n`\"b3ll/Decomposed\"`\n\n## Xcode Subproject\n\n- Add `Decomposed.xcodeproj` to your project\n- Add `Decomposed.framework` as an embedded framework (it's a static library, so it should not be embedded).\n\n#### Objective-C Notes\n\n- Objective-C support is **not** available through Swift Package Manager. Please use the manual Xcode subproject instead.\n- You'll want to use `#import \u003cDecomposed/Decomposed.h\u003e` as this contains both the generated Swift interfaces for the Objective-C classes and CALayer categories.\n- Not all of the API is available due to limitations of how Swift / Objective-C interop. Sadly the API can't be as nice, but `CATransform3DDecomposed` will allow for decomposition and recomposition of `CATransform3D` (similar classes exist for `matrix_double4x4` and `matrix_float4x4` and are wrappers around their Swift counterparts) as well as convenience categories on `CALayer`.\n\n# Other Recommendations\n\n## Motion\n\nThis library pairs very nicely with [Motion](https://github.com/b3ll/Motion), an animation engine for gesturally-driven user interfaces, animations, and interactions on iOS, macOS, and tvOS.\n\nAnimating a layer on a spring by modifying its transform has never been easier! Usually you'd have to manually wrap things in a `CATransaction` with actions disabled, and usage of `CATransform3DTranslate` gets pretty cumbersome.\n\nWith Decomposed + Motion this is super easy.\n\n```swift\nlet layer = ...\nlet springAnimation = SpringAnimation\u003cCGPoint\u003e(initialValue: .zero)\nspringAnimation.onValueChanged(disableActions: true) { [layer] translation in\n  layer.translation = translation\n}\n\n// In your pan gesture recognizer callback\n\nlet translation = panGestureRecognizer.translation(in: self.view)\nlet velocity = panGestureRecognizer.velocity(in: self.view)\n\nswitch panGestureRecognizer.state {\n  case .began:\n    springAnimation.stop()\n    springAnimation.updateValue(to: .zero)\n  case .changed:\n    springAnimation.updateValue(to: translation)\n    layer.translation = translation\n  case .ended:\n    springAnimation.velocity = velocity\n    springAnimation.toValue = CGPoint(x: 200.0, y: 200.0) // wherever you want it to go\n    springAnimation.start()\n  default:\n    break\n}\n```\n\nSee the DraggingCard demo for a good example of this :)\n\n# License\n\nDecomposed is licensed under the [BSD 2-clause license](https://github.com/b3ll/Decomposed/blob/main/LICENSE).\n\n# Contact Info\n\nFeel free to follow me on twitter: [@b3ll](https://www.twitter.com/b3ll)!\n","funding_links":["https://github.com/sponsors/b3ll"],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fb3ll%2Fdecomposed","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fb3ll%2Fdecomposed","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fb3ll%2Fdecomposed/lists"}