A debounce operation may be applied by ready inside a process(id:)
. Change the id
parameter in onTapGesture
to begin a brand new process. If one other faucet happens in the course of the Job.sleep
name, the duty will probably be cancelled and so Job.sleep
will throw an error.
struct DebounceTapModifier: ViewModifier {
non-public let motion: () -> Void
non-public let debounce: CGFloat
@State non-public var taskTrigger: Bool?
init(motion: @escaping () -> Void, debounce: CGFloat) {
self.motion = motion
self.debounce = debounce
}
func physique(content material: Content material) -> some View {
content material
.onTapGesture {
taskTrigger = !(taskTrigger ?? false)
}
.process(id: taskTrigger) {
guard taskTrigger != nil { return } // the consumer has not tapped but
do {
strive await Job.sleep(for: .seconds(debounce))
// if Job.sleep throws an error, motion is not going to be known as
motion()
} catch {
// one other faucet occurred in the course of the debounce period
}
}
}
}
As in your Mix method, the lifetime of DebounceViewModel
is just not being managed by SwiftUI. I’d make it an ObservableObject
and put it in a @StateObject
, in order that its lifetime is similar because the view modifier.
class Debouncer: ObservableObject {
var cancellable: (any Cancellable)?
let topic = PassthroughSubject()
deinit {
print("deinit")
}
}
struct DebounceTapModifier: ViewModifier {
non-public let motion: () -> Void
non-public let debounce: CGFloat
non-public var debouncer = Debouncer()
init(motion: @escaping () -> Void, debounce: CGFloat) {
self.motion = motion
self.debounce = debounce
}
func physique(content material: Content material) -> some View {
content material
.onTapGesture {
debouncer.topic.ship(0)
}
.onChange(of: debounce) {
// redo the writer subscription if the debounce time has modified
// if debounce is at all times the identical then you possibly can simply do that in onAppear
setupActions()
// *don't* name setupActions within the initialiser. The StateObject has not but been "put in" at the moment.
}
}
non-public func setupActions() {
debouncer.cancellable = debouncer.topic
.debounce(for: .seconds(debounce), scheduler: DispatchQueue.principal)
.sink { _ in
motion()
}
}
}