{"id":17448315,"url":"https://github.com/mesqueeb/debouncify","last_synced_at":"2026-05-02T14:35:09.985Z","repository":{"id":248267237,"uuid":"828123057","full_name":"mesqueeb/Debouncify","owner":"mesqueeb","description":"🔂 Swift actor to debounce anything \u0026 SwiftUI View modifier `onChange` with debounce","archived":false,"fork":false,"pushed_at":"2024-10-24T23:47:38.000Z","size":31,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-03-27T21:02:04.688Z","etag":null,"topics":["actor","debounce","debounce-input","debouncer","debouncify","onchange","onchange-debounced","swift","swift-actor","swift-debounce","swiftui","swiftui-onchange"],"latest_commit_sha":null,"homepage":"https://swiftpackageindex.com/mesqueeb/Debouncify/documentation/debouncify","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/mesqueeb.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,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null},"funding":{"github":"mesqueeb","patreon":null,"open_collective":null,"ko_fi":null,"tidelift":null,"community_bridge":null,"liberapay":null,"issuehunt":null,"otechie":null,"lfx_crowdfunding":null,"custom":null}},"created_at":"2024-07-13T07:25:16.000Z","updated_at":"2024-12-03T05:16:55.000Z","dependencies_parsed_at":"2024-07-13T15:32:46.176Z","dependency_job_id":"bc4b5008-5cc0-411d-bcf0-8c6a9b8b1fbf","html_url":"https://github.com/mesqueeb/Debouncify","commit_stats":null,"previous_names":["mesqueeb/debouncify"],"tags_count":2,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mesqueeb%2FDebouncify","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mesqueeb%2FDebouncify/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mesqueeb%2FDebouncify/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mesqueeb%2FDebouncify/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mesqueeb","download_url":"https://codeload.github.com/mesqueeb/Debouncify/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":245966929,"owners_count":20701759,"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":["actor","debounce","debounce-input","debouncer","debouncify","onchange","onchange-debounced","swift","swift-actor","swift-debounce","swiftui","swiftui-onchange"],"created_at":"2024-10-17T20:06:58.207Z","updated_at":"2026-05-02T14:35:09.976Z","avatar_url":"https://github.com/mesqueeb.png","language":"Swift","funding_links":["https://github.com/sponsors/mesqueeb"],"categories":[],"sub_categories":[],"readme":"# Debouncify 🔂\n\n[![](https://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2Fmesqueeb%2FDebouncify%2Fbadge%3Ftype%3Dswift-versions)](https://swiftpackageindex.com/mesqueeb/Debouncify)\n[![](https://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2Fmesqueeb%2FDebouncify%2Fbadge%3Ftype%3Dplatforms)](https://swiftpackageindex.com/mesqueeb/Debouncify)\n\n```\n.package(url: \"https://github.com/mesqueeb/Debouncify\", from: \"0.1.1\")\n```\n\n- `onChangeDebounced` is a SwiftUI View modifier like `onChange` but makes it debounce on every call by a specified duration\n- `Debouncify` is a Swift actor to easily wrap a function and make it debounce on every call by a specified duration\n\n\"Debouncing\" is to execute a function after a short delay. If the function is called twice within this time, the function will only be executed once after the specified duration has passed after the last call.\n\n## SwiftUI `onChangeDebounced` View modifier\n\nSuppose you have a function that you want to execute on every keystroke, but debounce it by 300ms to only execute it after the user has stopped typing for a certain amount of time.\n\nExample:\n\n```swift\n@State private var query: String = \"\"\n\nfunc search(_ query: String) async {\n    print(\"searching!\")\n    // your search API call logic...\n}\n\nvar body: some View {\n    TextField(\"Search...\", text: $query)\n        .onChangeDebounced(of: query, after: .milliseconds(300)) { _oldValue, newValue in\n            search(newValue)\n        }\n}\n```\n\n### Canceling the debounced Task\n\nIf you need to cancel the debounced execution of your search function, eg. when the user hits ESC, you can pass a binding with a `Task` which you can then cancel.\n\nExample:\n\n```swift\n@State private var query: String = \"\"\n\nfunc search(_ query: String) async {\n    print(\"searching!\")\n    // your search API call logic...\n}\n\n/// The search Task is added by `onChangeDebounced` below\n@State private var searchTask: Task\u003cVoid, never\u003e? = nil\n\nvar body: some View {\n    TextField(\"Search...\", text: $query)\n    .onChangeDebounced(of: query, after: .milliseconds(300), task: $searchTask) { _oldValue, newValue in\n        search(newValue)\n    }\n    .onKeyPress(.return) {\n        searchTask?.cancel()\n        search(query)\n        return .handled\n    }\n    .onKeyPress(.escape) {\n        searchTask?.cancel()\n        return .handled\n    }\n}\n```\n\n## Swift `Debouncify` Actor\n\nUse `Debouncify` to wrap a function and it will automatically get debounced each subsequent call.\n\nExample:\n\n```swift\n// Example debounced function\nfunc search() async {\n    print(\"searching!\")\n    // your search API call logic...\n}\n\n// Using Debouncify to wrap the function\nlet searchAfter300ms = Debouncify(call: search, after: .milliseconds(300))\n\n// Usage\nTask {\n    Task { await searchAfter300ms() }\n    try await Task.sleep(for: .milliseconds(100))\n    Task { await searchAfter300ms() }\n    try await Task.sleep(for: .milliseconds(100))\n    Task { await searchAfter300ms() }\n}\n// it will only print \"searching!\" once after 300ms\n```\n\n### Canceling the debounced Task\n\nYou can cancel the debounced task by calling the `cancel` method on the `Debouncify` instance.\n\nExample:\n\n```swift\n// Example debounced function\nfunc search() async {}\n\nlet searchAfter300ms = Debouncify(by: .milliseconds(300), search)\nvar task: Task\u003cAny, Any\u003e? = nil\n\nTask { await searchAfter300ms() }\n// if the search needs to be cancelled before the Task above finishes\nTask { await searchAfter300ms.cancel() }\n```\n\n## Documentation\n\nSee the [documentation](https://swiftpackageindex.com/mesqueeb/Debouncify/main/documentation/Debouncify) for more info.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmesqueeb%2Fdebouncify","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmesqueeb%2Fdebouncify","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmesqueeb%2Fdebouncify/lists"}