{"id":20389630,"url":"https://github.com/c-villain/animatable","last_synced_at":"2025-04-12T10:52:20.769Z","repository":{"id":65182764,"uuid":"585528456","full_name":"c-villain/Animatable","owner":"c-villain","description":"Yet another animation modifiers (like fireworks, live comments or explosion) on SwiftUI ","archived":false,"fork":false,"pushed_at":"2024-06-03T15:50:14.000Z","size":9596,"stargazers_count":55,"open_issues_count":0,"forks_count":4,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-04-06T12:03:51.582Z","etag":null,"topics":["blinking","ios","shimmers","swift-package","swiftui","swiftui-animation","swiftui-animations","swiftui-example"],"latest_commit_sha":null,"homepage":"https://t.me/swiftui_dev","language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/c-villain.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2023-01-05T12:01:54.000Z","updated_at":"2025-01-04T06:00:40.000Z","dependencies_parsed_at":"2023-02-12T00:45:52.966Z","dependency_job_id":null,"html_url":"https://github.com/c-villain/Animatable","commit_stats":null,"previous_names":[],"tags_count":1,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/c-villain%2FAnimatable","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/c-villain%2FAnimatable/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/c-villain%2FAnimatable/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/c-villain%2FAnimatable/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/c-villain","download_url":"https://codeload.github.com/c-villain/Animatable/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248557844,"owners_count":21124165,"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":["blinking","ios","shimmers","swift-package","swiftui","swiftui-animation","swiftui-animations","swiftui-example"],"created_at":"2024-11-15T03:18:50.522Z","updated_at":"2025-04-12T10:52:20.749Z","avatar_url":"https://github.com/c-villain.png","language":"Swift","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Animatable \n\n[![Latest release](https://img.shields.io/github/v/release/c-villain/Animatable?color=brightgreen\u0026label=version)](https://github.com/c-villain/Animatable/releases/latest)\n[![](https://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2Fc-villain%2FAnimatable%2Fbadge%3Ftype%3Dswift-versions)](https://swiftpackageindex.com/c-villain/Animatable)\n[![](https://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2Fc-villain%2FAnimatable%2Fbadge%3Ftype%3Dplatforms)](https://swiftpackageindex.com/c-villain/Animatable)\n[![](https://img.shields.io/badge/SPM-supported-DE5C43.svg?color=brightgreen)](https://swift.org/package-manager/)\n![](https://img.shields.io/github/license/c-villain/Animatable)\n\n[![contact: @lexkraev](https://img.shields.io/badge/contact-%40lexkraev-blue.svg?style=flat)](https://t.me/lexkraev)\n[![Telegram Group](https://img.shields.io/endpoint?color=neon\u0026style=flat-square\u0026url=https%3A%2F%2Ftg.sumanjay.workers.dev%2Fswiftui_dev)](https://telegram.dog/swiftui_dev)\n\nYet another animation modifiers for buttons, skeletons and other views on pure SwiftUI.\n\n\u003cp align=\"left\"\u003e\n  \u003cimg src=\"Gifs/quick_single.gif\" alt=\"\" width=\"300\"\u003e\n  \u003c/p\u003e\n\n\u003cp align=\"left\"\u003e\n  \u003cimg src=\"Gifs/skeletons.gif\" alt=\"\" width=\"300\"\u003e\n  \u003c/p\u003e\n\n  ### Demo example for card skeletons\n\n\u003cp align=\"left\"\u003e\n  \u003cimg src=\"Gifs/card_skeleton_example.gif\" alt=\"\" width=\"300\"\u003e\n  \u003c/p\u003e\n  \n🔍 Full demo video you can find [here](https://t.me/swiftui_dev/222).\n    \n👨🏻‍💻 Feel free to subscribe to channel **[SwiftUI dev](https://t.me/swiftui_dev)** in telegram.\n\n## Requirements\n\n- iOS 13.0 or macOS 10.15\n\n## Installation\n\n#### Swift Package Manager\n\nTo integrate ```Animatable``` into your project using SwiftPM add the following to your `Package.swift`:\n\n```swift\ndependencies: [\n    .package(url: \"https://github.com/c-villain/Animatable\", from: \"0.1.0\"),\n],\n```\nor via [XcodeGen](https://github.com/yonaskolb/XcodeGen) insert into your `project.yml`:\n\n```yaml\nname: YourProjectName\noptions:\n  deploymentTarget:\n    iOS: 13.0\npackages:\n  Animatable:\n    url: https://github.com/c-villain/Animatable\n    from: 0.1.0\ntargets:\n  YourTarget:\n    type: application\n    ...\n    dependencies:\n       - package: Animatable\n```\n\n## Quick start\n\nAll examples you can find in demo project inside package.\n\nThere are different custom animation types provided by `Animatable`.\n\n👇🏻 Tap on its name to see description and example of using.\n\n\u003cdetails\u003e\n  \u003csummary\u003eLive comments effect\u003c/summary\u003e\n\n\u003cp align=\"left\"\u003e\n\u003cimg src=\"Gifs/liveComments.gif\" alt=\"live comments\"\u003e\n\u003c/p\u003e\n\nUse `.animate(.liveComments(stamps:),animate:)` where `stamps` is number of prints in animation activity, `animate` is flag to start animation.\n                     \n```swift\n@State var animate: Bool = false\n...\nButton {\n    animate.toggle()\n} label: {\n    HStack(spacing: 8)  {\n        Image(systemName: animate ? \"heart.fill\" : \"heart\")\n            .resizable()\n            .scaledToFit()\n            .animate(.liveComments(stamps: 4),\n                     animate: animate)\n            .frame(width: 24, height: 24)\n            .foregroundColor(.white)\n\n        Text(\"Like\")\n            .font(.body)\n            .fontWeight(.medium)\n            .foregroundColor(.white)\n    }\n    .padding(12)\n    .background(\n        Rectangle()\n            .fill(.pink.opacity(0.8))\n            .cornerRadius(12)\n    )\n}\n```\n\u003c/details\u003e\n\n\u003cdetails\u003e\n  \u003csummary\u003eExplosion effect\u003c/summary\u003e\n  \n  \u003cp align=\"left\"\u003e\n\u003cimg src=\"Gifs/explosion.gif\" alt=\"explosion\"\u003e\n\u003c/p\u003e\n\nUse `.animate(.explosive(color:),animate:)` where `color` is color of explosion in animation activity, `animate` is flag to start animation.\n\n  ```swift\n  @State var animate: Bool = false\n...\n  Button {\n    animate.toggle()\n} label: {\n    HStack(spacing: 8)  {\n        Image(systemName: animate ? \"power\" : \"poweroff\")\n            .resizable()\n            .scaledToFit()\n            .animate(.explosive(color: .white),\n                     animate: animate)\n            .frame(width: 24, height: 24)\n            .foregroundColor(.white)\n\n        Text(animate ? \"On\" : \"Off\")\n            .font(.body)\n            .fontWeight(.medium)\n            .foregroundColor(.white)\n    }\n    .padding(12)\n    .background(\n        Rectangle()\n            .fill(.gray.opacity(0.8))\n            .cornerRadius(12)\n    )\n}\n  ```\n  \u003c/details\u003e\n  \n  \u003cdetails\u003e\n  \u003csummary\u003eTweak effect\u003c/summary\u003e\n  \n  \u003cp align=\"left\"\u003e\n\u003cimg src=\"Gifs/tweak.gif\" alt=\"tweak\"\u003e\n\u003c/p\u003e\n\nUse `.animate(.tweaking(amount:,shakesPerUnit:),animate:)` where `amount` is tweak offset, `shakesPerUnit` is number of shakes in tweking, `animate` is flag to start animation.\n\n  ```swift\n  @State var animate: Bool = false\n...\nButton {\n    animate.toggle()\n} label: {\n    HStack(spacing: 8)  {\n        Image(systemName: animate ? \"hand.thumbsup.fill\" : \"hand.thumbsup\")\n            .resizable()\n            .scaledToFit()\n            .animate(.tweaking(),\n                     animate: animate)\n            .frame(width: 24, height: 24)\n            .foregroundColor(.white)\n\n        Text(\"Like\")\n            .font(.body)\n            .fontWeight(.medium)\n            .foregroundColor(.white)\n    }\n    .padding(12)\n    .background(\n        Rectangle()\n            .fill(.blue.opacity(0.8))\n            .cornerRadius(12)\n    )\n}\n  ```\n  \u003c/details\u003e\n  \n  \u003cdetails\u003e\n  \u003csummary\u003eScaling effect\u003c/summary\u003e\n  \n  \u003cp align=\"left\"\u003e\n\u003cimg src=\"Gifs/scaling.gif\" alt=\"scaling\"\u003e\n\u003c/p\u003e\n\nUse `.animate(.scaling(scaling:),animate:)` where `scaling` is scaling factor, `animate` is flag to start animation.\n\n  ```swift\n  @State var animate: Bool = false\n...\n Button {\n    animate.toggle()\n} label: {\n    HStack(spacing: 8)  {\n        Image(systemName: animate ? \"plus.app.fill\" : \"plus.app\")\n            .resizable()\n            .scaledToFit()\n            .animate(.scaling(),\n                     animate: animate)\n            .frame(width: 24, height: 24)\n            .foregroundColor(.white)\n\n        Text(\"Add\")\n            .font(.body)\n            .fontWeight(.medium)\n            .foregroundColor(.white)\n    }\n    .padding(12)\n    .background(\n        Rectangle()\n            .fill(.yellow.opacity(0.8))\n            .cornerRadius(12)\n    )\n}\n  ```\n  \u003c/details\u003e\n  \n\u003cdetails\u003e\n  \u003csummary\u003eRotating effect\u003c/summary\u003e\n  \n  \u003cp align=\"left\"\u003e\n\u003cimg src=\"Gifs/rotating.gif\" alt=\"scaling\"\u003e\n\u003c/p\u003e\n\nUse `.animate(.rotating,animate:)` where `animate` is flag to start animation.\n\n  ```swift\n  @State var animate: Bool = false\n...\nButton {\n    animate.toggle()\n} label: {\n    HStack(spacing: 8)  {\n        Image(systemName: animate ? \"arrow.triangle.2.circlepath.circle.fill\" : \"arrow.triangle.2.circlepath.circle\")\n            .resizable()\n            .scaledToFit()\n            .animate(.rotating,\n                     animate: animate)\n            .frame(width: 24, height: 24)\n            .foregroundColor(.white)\n\n        Text(\"Sync\")\n            .font(.body)\n            .fontWeight(.medium)\n            .foregroundColor(.white)\n    }\n    .padding(12)\n    .background(\n        Rectangle()\n            .fill(.blue.opacity(0.8))\n            .cornerRadius(12)\n    )\n}\n  ```\n  \u003c/details\u003e\n \n\u003cdetails\u003e\n  \u003csummary\u003eFireworks effect\u003c/summary\u003e\n  \n  \u003cp align=\"left\"\u003e\n\u003cimg src=\"Gifs/fireworks.gif\" alt=\"fireworks\"\u003e\n\u003c/p\u003e\n\nUse `.animate(.fireworks(color:),animate:)` where `color` is color of animation, `animate` is flag to start animation.\n\n  ```swift\n  @State var animate: Bool = false\n...\nButton {\n    animate.toggle()\n} label: {\n    HStack(spacing: 8)  {\n        Image(systemName: animate ? \"sun.max.fill\" : \"sun.max\")\n            .resizable()\n            .scaledToFit()\n            .animate(.fireworks(color: .white),\n                     animate: animate)\n            .frame(width: 24, height: 24)\n            .foregroundColor(.white)\n\n        Text(\"Weather\")\n            .font(.body)\n            .fontWeight(.medium)\n            .foregroundColor(.white)\n    }\n    .padding(12)\n    .background(\n        Rectangle()\n            .fill(.blue.opacity(0.8))\n            .cornerRadius(12)\n    )\n}\n  ```\n  \u003c/details\u003e\n  \n  \n  👇🏻 You can easily join them together to combine animation.\n  \n  \u003cdetails\u003e\n  \u003csummary\u003eCombining animation\u003c/summary\u003e\n  \n  \u003cp align=\"left\"\u003e\n\u003cimg src=\"Gifs/combined.gif\" alt=\"combined\"\u003e\n\u003c/p\u003e\n\nUse sequence of `.animate(type:,animate:)` to get multiple animation effect.\n\n  ```swift\n  @State var animate: Bool = false\n...\nButton {\n    animate.toggle()\n} label: {\n    HStack(spacing: 8)  {\n        Image(systemName: animate ? \"sun.max.fill\" : \"sun.max\")\n            .resizable()\n            .scaledToFit()\n            .animate(.rotating,\n                     animate: animate)\n            .animate(.explosive(color: .red, factor: 2.0),\n                     animate: animate)\n            .animate(.explosive(color: .blue, factor: 1.4),\n                     animate: animate)\n            .animate(.fireworks(color: .yellow, factor: 3.5),\n                     animate: animate)\n            .frame(width: 24, height: 24)\n            .foregroundColor(.red)\n\n        Text(\"Combined\")\n            .font(.body)\n            .fontWeight(.medium)\n            .foregroundColor(.white)\n    }\n    .padding(12)\n    .background(\n        Rectangle()\n            .fill(.blue.opacity(0.6))\n            .cornerRadius(12)\n    )\n}\n  ```\n  \u003c/details\u003e\n\n👇🏻 Animation for skeletons and other views.\n\n  \u003cdetails\u003e\n  \u003csummary\u003eShimmers\u003c/summary\u003e\n  \n  \u003cp align=\"left\"\u003e\n\u003cimg src=\"Gifs/shimmer.gif\" alt=\"combined\"\u003e\n\u003c/p\u003e\n\n\nUse sequence of `.shimmerable(configuration:)` where `configuration` is settings for for shimmers or `.shimmerable()` for defaults\n\n```swift\nRectangle()\n  .fill(.pink.opacity(0.8))\n  .frame(height: 40)\n  .frame(maxWidth: .infinity)\n  .shimmerable()\n  .cornerRadius(12)\n```\n  \u003c/details\u003e\n\n\u003cdetails\u003e\n  \u003csummary\u003eBlinking\u003c/summary\u003e\n  \n  \u003cp align=\"left\"\u003e\n\u003cimg src=\"Gifs/blinking.gif\" alt=\"combined\"\u003e\n\u003c/p\u003e\n\n\nUse sequence of `.blinking(configuration:)` where `configuration` is settings for for blinking animation or `.blinking()` for defaults\n\n```swift\nRectangle()\n  .fill(.pink.opacity(0.8))\n  .frame(height: 40)\n  .frame(maxWidth: .infinity)\n  .blinking()\n  .cornerRadius(12)\n```\n  \u003c/details\u003e\n\n  \u003cdetails\u003e\n  \u003csummary\u003eCombining blinking and shimmerable\u003c/summary\u003e\n  \n  \u003cp align=\"left\"\u003e\n\u003cimg src=\"Gifs/combined_skeleton.gif\" alt=\"combined\"\u003e\n\u003c/p\u003e\n\nUse sequence of `.blinking()` and `.shimmerable()` to get multiple animation effect.\n\n\n```swift\nRectangle()\n  .fill(.pink.opacity(0.8))\n  .frame(height: 40)\n  .frame(maxWidth: .infinity)\n  .blinking()\n  .shimmerable()\n  .cornerRadius(12)\n```\n  \u003c/details\u003e\n  \n### ⚠️ Recommendations for use\n\n\u003cdetails\u003e\n  \u003csummary\u003ewith combining animation\u003c/summary\u003e\n  \n  Order in sequence of `.animate(type:,animate:)` is really important!\n  \n  Feel the difference in the next example:\n\n```swift\n@State var animate: Bool = false\n...\n Button {\n    animate.toggle()\n} label: {\n    HStack(spacing: 8)  {\n        Image(systemName: multiple ? \"sun.max.fill\" : \"sun.max\")\n            .resizable()\n            .scaledToFit()\n            .animate(.liveComments(stamps: 4),\n                     animate: animate)\n            .animate(.rotating,\n                     animate: animate)\n            .animate(.explosive(color: .red, factor: 2.0),\n                     animate: animate)\n            .animate(.explosive(color: .blue, factor: 1.4),\n                     animate: animate)\n            .animate(.fireworks(color: .yellow, factor: 3.0),\n                     animate: animate)\n            .frame(width: 24, height: 24)\n            .foregroundColor(.red)\n\n        Text(\"Weather\")\n            .font(.body)\n            .fontWeight(.medium)\n            .foregroundColor(.white)\n    }\n    .padding(12)\n    .background(\n        Rectangle()\n            .fill(.blue.opacity(0.6))\n            .cornerRadius(12)\n    )\n}\n```\n\nUsing this sequence of `.animate(...)` leads to such behaviour:\n\n  \u003cp align=\"left\"\u003e\n\u003cimg src=\"Gifs/problem1.gif\" alt=\"problem1\"\u003e\n\u003c/p\u003e\n\nTo get expected behaviour this we should change the order in chain:\n\n```swift\n@State var animate: Bool = false\n...\n Button {\n    animate.toggle()\n} label: {\n    HStack(spacing: 8)  {\n        Image(systemName: multiple ? \"sun.max.fill\" : \"sun.max\")\n            .resizable()\n            .scaledToFit()\n            .animate(.rotating,                // \u003c== Look here!\n                     animate: animate)\n            .animate(.liveComments(stamps: 4), // \u003c== Look here!\n                     animate: animate)\n            .animate(.explosive(color: .red, factor: 2.0),\n                     animate: animate)\n            .animate(.explosive(color: .blue, factor: 1.4),\n                     animate: animate)\n            .animate(.fireworks(color: .yellow, factor: 3.0),\n                     animate: animate)\n            .frame(width: 24, height: 24)\n            .foregroundColor(.red)\n\n        Text(\"Weather\")\n            .font(.body)\n            .fontWeight(.medium)\n            .foregroundColor(.white)\n    }\n    .padding(12)\n    .background(\n        Rectangle()\n            .fill(.blue.opacity(0.6))\n            .cornerRadius(12)\n    )\n}\n```\nThe result:\n\n  \u003cp align=\"left\"\u003e\n\u003cimg src=\"Gifs/fix1.gif\" alt=\"erro1\"\u003e\n\u003c/p\u003e\n\n\u003c/details\u003e\n\n  \u003cdetails\u003e\n  \u003csummary\u003ewith group of views\u003c/summary\u003e\n\nUse can use `.animate(...)` not only for one view but for group of views\n\n  ```swift\n  @State var animate: Bool = false\n...\nButton {\n    animate.toggle()\n} label: {\n    HStack(spacing: 8)  {\n        Image(systemName: animate ? \"heart.fill\" : \"heart\")\n            .resizable()\n            .scaledToFit()\n            .frame(width: 24, height: 24)\n            .foregroundColor(.red)\n\n        Text(\"Like\")\n            .font(.body)\n            .fontWeight(.medium)\n            .foregroundColor(.red)\n    }\n    .animate(.liveComments(stamps: 4), // \u003c== Look here!\n             animate: animate)\n    .padding(12)\n    .background(\n        Rectangle()\n            .fill(.blue.opacity(0.8))\n            .cornerRadius(12)\n    )\n}\n  ```\n  \n  The result:\n  \n   \u003cp align=\"left\"\u003e\n\u003cimg src=\"Gifs/example1.gif\" alt=\"example1\"\u003e\n\u003c/p\u003e\n\n  \u003c/details\u003e\n\n\u003c/details\u003e\n\n  \u003cdetails\u003e\n  \u003csummary\u003ewith standart SUI modifiers\u003c/summary\u003e\n\nBe careful with standard SUI modifiers. It may cause different side effects.\n\nFor example [`cornerRadius`](https://developer.apple.com/documentation/quartzcore/calayer/1410818-cornerradius) crops the modified view. \n\n  ```swift\n  @State var animate: Bool = false\n...\nButton {\n    animate.toggle()\n} label: {\n    HStack(spacing: 8)  {\n        Image(systemName: liveComments ? \"heart.fill\" : \"heart\")\n            .resizable()\n            .scaledToFit()\n            .animate(.liveComments(stamps: 4),\n                     animate: animate)\n            .frame(width: 24, height: 24)\n            .foregroundColor(.red)\n\n        Text(\"Like\")\n            .font(.body)\n            .fontWeight(.medium)\n            .foregroundColor(.white)\n    }\n    .padding(12)\n    .background (\n        Color.blue.opacity(0.8)\n    )\n    .cornerRadius(12) // \u003c== Look here!\n}\n  ```\n  \n  The result:\n  \n   \u003cp align=\"left\"\u003e\n\u003cimg src=\"Gifs/problem2.gif\" alt=\"problem2\"\u003e\n\u003c/p\u003e\n\nI recommend you to use it in the `.background` to get expected behaviour:\n\n  ```swift\n.background ( // \u003c== Look here\n    Rectangle()\n        .fill(.blue.opacity(0.8))\n        .cornerRadius(12) // \u003c== Look here\n)\n  ```\n  \n  \u003c/details\u003e\n\n## Communication\n\n- If you like this repository, please do :star: to make this useful for others.\n- If you **found a bug**, open an issue or submit a fix via a pull request.\n- If you **have a feature request**, open an issue or submit a implementation via a pull request or hit me up on **lexkraev@gmail.com** or **[telegram](https://t.me/lexkraev)**.\n- If you **want to contribute**, submit a pull request onto the master branch.\n\n## License\n\nAnimatable package is released under an MIT license.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fc-villain%2Fanimatable","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fc-villain%2Fanimatable","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fc-villain%2Fanimatable/lists"}