0

I'm trying to save the value entered in a textField 2 seconds after the user stops typing. I found this example that uses .debounce from the Combine framework which works fine but for some reason, it feels like I overcomplicated things and I would rather use a simple timer.

The issue with the following code is that I cannot pass the value from the logInputValue(inputValue:) method to the doSomething() method when the timer is done. Right now it calls the doSomething() method but it doesn't pass any parameter.

How can I pass a string parameter from the logInputValue(inputValue:) method to the doSomething() method when the timer is done?

Service Model

class ServiceModel{
    var myTimer = Timer()
    
    func logInputValue(inputValue:String){
        if myTimer.isValid{
            myTimer.invalidate()
        }
        myTimer = Timer.scheduledTimer(timeInterval: 2, target: self, selector: #selector(doSomething), userInfo: nil, repeats: false)
    }
    @objc func doSomething(){
        print("Timer is done")
        // save the value currenlty in the textField
    }
}

SwiftUI

TextField('enter number', text: $myTextField)
    .onChange(of: myTextField) { value in
        serivceModel.logInputValue(inputValue: value)
    }

I also tried using the timer as follows but couldn't make it work. It saves each character typed in the field not just what's in the field after the 2 seconds after the user stops typing.

func logInputValue(inputValue:String){
    self.timer = Timer.scheduledTimer(withTimeInterval: 2, repeats: false, block: { _ in
        if timer.isValid{
            timer.invalidate()
        }
        // save the value currenlty in the myTextField
    })
}
fs_tigre
  • 10,650
  • 13
  • 73
  • 146
  • 2
    Actually your attempt is much more complicated than a `Combine` publisher with the `debounce` operator. – vadian Aug 12 '23 at 16:20
  • I feel that with my current code structure, it makes more sense to use the timer since I'm not using `ViewModels` to hold my TextFields and I have a few more fields, and having just one in a ViewModel feels wrong. FYI - I'm using more of a `ServiceModel` approach in this project. Thanks. – fs_tigre Aug 12 '23 at 16:37
  • IMHO, using Combine in the ServiceModel would in fact yield the most comprehensible code. Especially, since you already have a function that feeds the input PassthroughSubject, i.e. `input.send(inputValue)`. You may give Combine a try ;) – CouchDeveloper Aug 13 '23 at 07:35

2 Answers2

1

There's a version of Timer that runs with a closure instead of a selector:

myTimer = Timer.scheduledTimer(withTimeInterval: 2.0, repeats: false) { [weak self] _ in
    self?.doSomething(inputValue)
}

// ...

func doSomething(_ value: String) {
  // ...
} 

https://developer.apple.com/documentation/foundation/timer/2091889-scheduledtimer

jnpdx
  • 45,847
  • 6
  • 64
  • 94
  • Shoot, it works, as I stated in my original comment, it didn't work when I tried the closure. Thanks. – fs_tigre Aug 12 '23 at 16:32
0

I want to provide a solution using Combine to let you see the difference to the other suggested solutions. You can judge for yourself whether this is "overcomplicated things" ;)

import Combine

class ServiceModel {
    private let input = PassthroughSubject<String, Never>()
    private var cancellable: AnyCancellable!

    init() {
        self.cancellable = input
        .debounce(for: .seconds(2), scheduler: DispatchQueue.main)
        .sink { [weak self] value in 
            // ...
        }
    }
    
    func logInputValue(inputValue:String) {
        input.send(inputValue)
    }
}
CouchDeveloper
  • 18,174
  • 3
  • 45
  • 67