DebouncedOnChange.swift (2815B)
1 // https://github.com/Tunous/DebouncedOnChange/blob/5670ea13e8ad33e9cc3197f6d13ce492dc0e46ab/Sources/DebouncedOnChange/DebouncedChangeViewModifier.swift 2 3 import SwiftUI 4 import Foundation 5 6 extension View { 7 8 /// Adds a modifier for this view that fires an action only when a time interval in seconds represented by 9 /// `debounceTime` elapses between value changes. 10 /// 11 /// Each time the value changes before `debounceTime` passes, the previous action will be cancelled and the next 12 /// action /// will be scheduled to run after that time passes again. This mean that the action will only execute 13 /// after changes to the value /// stay unmodified for the specified `debounceTime` in seconds. 14 /// 15 /// - Parameters: 16 /// - value: The value to check against when determining whether to run the closure. 17 /// - debounceTime: The time in seconds to wait after each value change before running `action` closure. 18 /// - action: A closure to run when the value changes. 19 /// - Returns: A view that fires an action after debounced time when the specified value changes. 20 public func onChange<Value>( 21 of value: Value, 22 debounceTime: TimeInterval, 23 perform action: @escaping (_ newValue: Value) -> Void 24 ) -> some View where Value: Equatable { 25 self.modifier(DebouncedChangeViewModifier(trigger: value, debounceTime: debounceTime, action: action)) 26 } 27 } 28 29 private struct DebouncedChangeViewModifier<Value>: ViewModifier where Value: Equatable { 30 let trigger: Value 31 let debounceTime: TimeInterval 32 let action: (Value) -> Void 33 34 @State private var debouncedTask: Task<Void, Never>? 35 36 func body(content: Content) -> some View { 37 content.onChange(of: trigger) { value in 38 debouncedTask?.cancel() 39 debouncedTask = Task.delayed(seconds: debounceTime) { @MainActor in 40 action(value) 41 } 42 } 43 } 44 } 45 46 extension Task { 47 48 /// Asynchronously runs the given `operation` in its own task after the specified number of `seconds`. 49 /// 50 /// The operation will be executed after specified number of `seconds` passes. You can cancel the task earlier 51 /// for the operation to be skipped. 52 /// 53 /// - Parameters: 54 /// - time: Delay time in seconds. 55 /// - operation: The operation to execute. 56 /// - Returns: Handle to the task which can be cancelled. 57 @discardableResult 58 public static func delayed( 59 seconds: TimeInterval, 60 operation: @escaping @Sendable () async -> Void 61 ) -> Self where Success == Void, Failure == Never { 62 Self { 63 do { 64 try await Task<Never, Never>.sleep(nanoseconds: UInt64(seconds * 1_000_000_000)) 65 await operation() 66 } catch {} 67 } 68 } 69 }