Last active
March 14, 2024 22:32
-
-
Save JetForMe/a49c663a1394637755fae4599390b964 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import AsyncAlgorithms | |
import SwiftUI | |
/** | |
This example shows one possible way to debounce changes from a user typing into a field. | |
*/ | |
struct | |
DebouncedTextView : View | |
{ | |
var | |
body: some View | |
{ | |
TextField("Username", text: self.$username) | |
.onChange(of: self.username) | |
{ _, inNew in | |
self.usernameContinuation?.yield(inNew) | |
} | |
.task | |
{ | |
self.usernameChanges = AsyncStream { self.usernameContinuation = $0 } | |
for await username in self.usernameChanges!.debounce(for: .seconds(0.25)) | |
{ | |
print("username: \(username)") | |
} | |
} | |
} | |
@State private var username : String = "" | |
@State private var usernameChanges : AsyncStream<String>? | |
@State private var usernameContinuation : AsyncStream<String>.Continuation? | |
} | |
/** | |
This version uses `AsyncStream.makeStream` to halve the number of properties required. | |
*/ | |
struct | |
DebouncedTextView2 : View | |
{ | |
var | |
body: some View | |
{ | |
TextField("Username", text: self.$username) | |
.onChange(of: self.username) | |
{ _, inNew in | |
self.usernameChanges.1.yield(inNew) | |
} | |
.task | |
{ | |
for await username in self.usernameChanges.0.debounce(for: .seconds(0.25)) | |
{ | |
print("username: \(username)") | |
} | |
} | |
} | |
@State private var username = "" | |
@State private var usernameChanges = AsyncStream.makeStream(of: String.self) | |
} | |
/** | |
Like ``DebouncedTextView2``, but uses the view modifier below. | |
Note that this version loses the `async` context in the handler, and the reason I | |
needed all of this was to throttle network requests. | |
*/ | |
struct | |
DebouncedTextView3 : View | |
{ | |
var | |
body: some View | |
{ | |
TextField("Username", text: self.$username) | |
.onChange(of: self.username, debounceFor: .seconds(0.3)) | |
{ inOld, inNew in | |
print("username: \(inOld), \(inNew)") | |
} | |
} | |
@State private var username = "" | |
} | |
struct | |
DebounceChangeOf<V> : ViewModifier | |
where V : Equatable, V : Sendable | |
{ | |
let value : V | |
let initial : Bool | |
let debounceFor : Duration | |
let action : (_ oldValue: V, _ newValue: V) -> Void | |
func | |
body(content: Content) | |
-> some View | |
{ | |
return content | |
.onChange(of: value, initial: initial) | |
{ inOld, inNew in | |
self.valueChanges.continuation.yield((inOld, inNew)) | |
} | |
.task | |
{ | |
for await value in self.valueChanges.stream.debounce(for: .seconds(0.25)) | |
{ | |
self.action(value.0, value.1) | |
} | |
} | |
} | |
typealias ElementType = (V, V) | |
@State private var valueChanges = AsyncStream.makeStream(of: ElementType.self) | |
} | |
extension | |
View | |
{ | |
public | |
func | |
onChange<V>(of value: V, initial: Bool = false, debounceFor: Duration, _ action: @escaping (_ oldValue: V, _ newValue: V) -> Void) | |
-> some View where V : Equatable, V : Sendable | |
{ | |
return self.modifier(DebounceChangeOf(value: value, initial: initial, debounceFor: debounceFor, action: action)) | |
} | |
public | |
func | |
onChange<V>(of value: V, initial: Bool = false, debounceFor: Duration, _ action: @escaping (_ oldValue: V, _ newValue: V) async -> Void) | |
-> some View where V : Equatable, V : Sendable | |
{ | |
return self.modifier(DebounceChangeOf(value: value, initial: initial, debounceFor: debounceFor, action: action)) | |
} | |
} |
I didn't see your comment until now, so my reply is kinda moot after the long thread on Mastodon, but yeah, I think it's a good idea. Allows the client to arbitrarily process the stream of changes. Assuming none of the issues raised on Mastodon are deal breakers.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
I was playing around with this, because I think it is a cool idea. What do you think?