skip to Main Content

So I need to update a UILabel text value after X seconds have passed after the view appears on screen, the initial label text value comes from an API endpoint which is refreshed everytime the view appears.

Im currently doing the following:

 func updateLabelAfterAPICall(initialValue: String) {
    lastValue = initialValue //this is a local variable so I can use it to set the text once the view dissappears.
    label.text = lastValue
        Task {
            try? await Task.sleep(nanoseconds: 5_500_000_000)
            label.text = "New Value after 5.5 seconds passed"
        }
 }

Once the view dissappears, I need to set the label back to its initial value so I reset it again in a viewDidDissappear (if I dont do this, everytime the view is shown I see the "new text" for a brief second until the API call finishes, this is unwanted):

override func viewDidDisappear(_ animated: Bool) {
    super.viewDidDisappear(animated)
    label.text = lastValue
}

This seems to be working OK for the most part but I feel like there’s some edge case Im missing where I might need to cancel the Task or something similar? Maybe if I make the view appear and dissappear a bunch of times before 5.5 seconds have passed, would that create a bunch of different Tasks?

I ask this since I cant really replicate it exactly every time, but while testing, I’ve encountered some glitches such as the text not resetting to lastValue once I return to the view (the majority of times it seems to work fine though, which makes testing and debugging a pain).

Any tip for improvement is welcomed. Thanks!

2

Answers


  1. It may not be safe to put an action you are actively performing to sleep. You can do this on the main thread.
    Try this:

    func updateLabelAfterAPICall(initialValue: String) {
        lastValue = initialValue //this is a local variable so I can use it to set the text once the view dissappears.
        label.text = lastValue
        
        DispatchQueue.main.asyncAfter(deadline: .now() + 5.5) {
            label.text = "New Value after 5.5 seconds passed"
        }
     }
    
    Login or Signup to reply.
  2. It seems you might be concerned about what happens if the view disappears before the label has a chance to change. In that case you’d presumably like to cancel the whole Task operation. To do so, retain the Task in an instance property so that you can cancel it if the view disappears prematurely, something like this:

    var task = Task<Void, Never> {}
    func updateLabelAfterAPICall(initialValue: String) {
        task.cancel() // just in case
        task = Task { [weak self] in
            do {
                try await Task.sleep(nanoseconds: 5_500_000_000)
                guard let self = self else { return }
                self.label.text = "New Value after 5.5 seconds passed"
            } catch {}
        }
    }
    override func viewWillDisappear(_ animated: Bool) {
        super.viewWillDisappear(animated)
        task.cancel()
    }
    

    The bulk of the Task is the call to Task.sleep, which is cancellable, so the whole operation will be cancelled in good order because the Task.sleep call will throw when cancelled and the whole Task operation will abort.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search