{"id":21198237,"url":"https://github.com/b3ll/adjustable","last_synced_at":"2025-07-10T05:32:35.416Z","repository":{"id":242575639,"uuid":"803145568","full_name":"b3ll/Adjustable","owner":"b3ll","description":"Swift property wrapper to automatically add sliders to adjust values and aid in refining user interfaces, animations, and interactions without the need to recompile.","archived":false,"fork":false,"pushed_at":"2024-06-03T18:41:48.000Z","size":12948,"stargazers_count":47,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2024-06-04T21:56:02.275Z","etag":null,"topics":[],"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,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null},"funding":{"github":"b3ll"}},"created_at":"2024-05-20T06:47:30.000Z","updated_at":"2024-06-04T21:49:14.000Z","dependencies_parsed_at":"2024-06-03T21:41:52.827Z","dependency_job_id":"20721753-8f52-41a9-8417-ed1e62ae88c5","html_url":"https://github.com/b3ll/Adjustable","commit_stats":null,"previous_names":["b3ll/adjustable"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/b3ll%2FAdjustable","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/b3ll%2FAdjustable/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/b3ll%2FAdjustable/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/b3ll%2FAdjustable/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/b3ll","download_url":"https://codeload.github.com/b3ll/Adjustable/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":225622929,"owners_count":17498168,"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":[],"created_at":"2024-11-20T19:49:47.737Z","updated_at":"2024-11-20T19:49:49.684Z","avatar_url":"https://github.com/b3ll.png","language":"Swift","funding_links":["https://github.com/sponsors/b3ll"],"categories":[],"sub_categories":[],"readme":"\u003cp align=\"center\"\u003e\n    \u003cimg width=\"640pt\" src=\"https://github.com/b3ll/Adjustable/blob/main/Resources/AdjustableLogo.png?raw=true#gh-light-mode-only\"\u003e\n\u003c/p\u003e\n\u003cp align=\"center\"\u003e\n    \u003cimg width=\"640pt\" src=\"https://github.com/b3ll/Adjustable/blob/main/Resources/AdjustableLogo-Dark.png?raw=true#gh-dark-mode-only\"\u003e\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n    \u003cimg src=\"https://github.com/b3ll/Adjustable/blob/main/Resources/DemoVideo-Cropped.gif?raw=true\"\u003e\n\u003c/p\u003e\n\nThis package provides property wrappers that can be used on properties for any value conforming to `ClosedRange` to allow for super fast iteration of UIs and interactions without the need to wait to recompile / relaunch an application. It does so by automatically adding an interactive slider for any property that you annotate with `@Adjustable` that sits on top of your app's window so you can dynamically adjust properties and see them update live. It's powered by a lot of the stuff I experimented with to make [SetNeedsDisplay](https://github.com/b3ll/SetNeedsDisplay).\n\nThis is *perfect* for adjusting animation parameters on-the-fly, tweaking constants, or refining things until they feel just right without needing to constantly recompile your app and allowing you to focus on making things feel great.\n\nIt uses a lot of neat runtime tricks to pull out the name of the variable so it can appropriately name the relevant slider and also features a collapsible menu that you can tuck away when you don't need it (powered by [Motion](https://www.github.com/b3ll/Motion)!). \n\n\u003e [!Note]\n\u003e This is designed to only be used when developing applications and shouldn't be left in production builds. I would also not recommend annotating everything in your codebase with `@Adjustable` as this project isn't really built to scale (yet).\n\n\u003e [!Warning]\n\u003e This code contains some private Swift API stuff that powers `@Published` so there's a strong likelihood this will break in the future. I'd like to figure out ways to make the API a lot nicer in general, so if you have any ideas, let me know!\n\n- [Usage](#usage)\n- [Installation](#installation)\n  - [Requirements](#requirements)\n  - [Swift Package Manager](#swift-package-manager)\n- [License](#license)\n- [Thanks](#thanks)\n- [Contact Info](#contact-info)\n\n# Usage\n\nAnnotate your property of a type that conforms to `ClosedRange` like so:\n\n```swift\nclass MyView: UIView {\n\n    // A slider from `0.0` to `100.0` starting at `20.0` will be created.\n    // Anytime the slider is changed, `invalidateForAdjustable()` is called on the enclosing class.\n    @Adjustable(0.0...100.0) var someCustomProperty: Double = 20.0\n\n    // A slider from `0.0` to `100.0` starting at `20.0` will be created.\n    // Anytime the slider is changed, the `valueChanged` block is called with an instance of `self` that you can reference as well as the new value.\n    @Adjustable(0.0...100.0, valueChanged: { enclosingSelf, newValue in\n      // access `self` via `enclosingSelf`\n      // do what you want with `newValue\n    }) var someOtherCustomProperty: Double = 20.0\n\n    override func invalidateForAdjustable() {\n      print(\"someCustomProperty: \\(someCustomProperty)\")\n      print(\"someOtherCustomProperty: \\(someOtherCustomProperty)\")\n    }\n\n}\n```\n\nYou can also use `@Published`!\n\n```swift\nclass MyCoolClass {\n  \n  @Adjustable(0.0...100.0) var somePublishedProperty: Double = 20.0\n\n  var publishedCancellable: AnyCancellable?\n\n  init() {\n    self.publishedCancellable = $someProperty.sink { newValue in \n      print(\"somePublishedProperty: \\(somePublishedProperty)\")\n    }\n  }\n  \n}\n```\n\nIt also works in SwiftUI!\n\n```swift\nclass Model: ObservableObject {\n\n  @Adjustable(0.0...255.0) var somePublishedProperty: Double = 20.0\n\n}\n\nstruct MyView: View {\n  \n  @StateObject var model = Model()\n\n  var body: some View {\n    Rectangle()\n      .foregroundColor(Color(hue: model.colourOffset / 255.0, saturation: 1.0, brightness: 1.0))\n  }\n  \n}\n```\n\nAnytime any slider is adjusted, `invalidateForAdjustable` will be called on the enclosing class. This by default calls `setNeedsLayout` on `UIView` and `UIViewController`'s view, but this can be overridden and used to update the view's state or perform any action, really. There's also an inline block that can be supplied that contains an instance of the enclosing class (`enclosingSelf`) as well as the new value from the slider. `ObservableObject` invalidation is also supported and it will automatically call the correct `objectWillChange` event.\n\nIf you wish to override the default invalidation behaviour of `UIView` or `UIViewController` you can adopt the `AdjustableInvalidation` protocol:\n\n```swift\nimport Motion\n\nclass MyView: UIView {\n\n  @Adjustable(0.0...1.0) private var springResponse: Double = 0.5\n\n  var springAnimation = SpringAnimation\u003cCGFloat\u003e(response: 0.5, damping: 1.0)\n\n  func invalidateForAdjustable() {\n      springAnimation.configure(response: springResponse, damping: 1.0)\n  }\n\n}\n```\n\nI've saved a lot of time using `Adjustable` in conjunction with [Motion](https://github.com/b3ll/Motion) for rapid iteration of animation constants (e.g. springs). I'll try an animation, tweak it, try it again, and tweak it some more etc. Once finished, I'll update the constant inline and remove the `@Adjustable`. This saves *so* much time and allows you to focus on adjusting interactions to feel as best they can be (instead of waiting for Xcode to build and run again).\n\nAnother thing I use it for is layout constants that are referenced in `layoutSubviews`. Adjusting the slider will change the value, invalidate layout automatically (via `setNeedsLayout`), and call `layoutSubviews` which makes iteration really easy.\n\n\u003e [!Note]\n\u003e Similar ideas for \"hot-reloading\" projects exist: [InjectionIII](https://github.com/johnno1962/InjectionIII) / [Inject](https://github.com/krzysztofzablocki/Inject), as well as SwiftUI Previews, and while they're great tools, I felt like adjusting via sliders allows you to really focus on the feel of things moreso than waiting for delta compilations / injections (as well as more stable than relying on injection of dylibs to not break things haha).\n\n# Installation\n\n## Requirements\n\n- iOS 17+\n- Swift 5.9 or higher\n\nCurrently Adjustable supports Swift Package Manager.\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/Adjustable\", from: \"0.0.1\")\n```\n\n# License\n\nAdjustable is licensed under the [BSD 2-clause license](https://github.com/b3ll/Adjustable/blob/master/LICENSE).\n\n# Thanks\n\nThanks to [@harlanhaskins](https://twitter.com/harlanhaskins) and [@hollyborla](https://twitter.com/hollyborla) for helping point me in the right direction and explain the complexity that this sort of solution entails.\n\nMore info [here](https://forums.swift.org/t/property-wrappers-access-to-both-enclosing-self-and-wrapper-instance/32526).\n\n# Contact Info\n\nFeel free to follow me on Mastodon: [@b3ll](https://www.mastodon.social/@b3ll)!\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fb3ll%2Fadjustable","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fb3ll%2Fadjustable","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fb3ll%2Fadjustable/lists"}