skip to Main Content

I’m using .refreshable() on a List in SwiftUI. With the following code, I pull to refresh in an iOS 17.4 simulator, the spinner appears, and then goes away after a couple of seconds.

var body: some View {
  List() {
    // items ...
  }
  .refreshable { try? await Task.sleep(nanoseconds: 3_000_000_000) }
}

However, when I put my actual refresh logic into the refreshable function, the spinner no longer goes away, even though my refresh function completes without error (which I verified using breakpoints in Xcode). Instead, the spinner just spins forever.

var body: some View {
  List() {
    // items ...
  }
  .refreshable { await query.refetchAsync() }
}

query.refetchAsync() triggers a refetch that runs on a background Task and then waits for an update by iterating over a NotificationCenter.notifications() object returned by self.client.queryUpdatesAsync():

  public func refetchAsync() async {
    self.refetch()
    
    for await update in self.client.queryUpdatesAsync() {
      if update.key != self.key { continue }
      switch self.dataStatus {
      case .idle, .pending: continue
      default: break
      }
    }
  }

How do I fix this bug?

2

Answers


  1. The break in your switch self.dataStatus { breaks the switch, not the for loop. To fix that, you can use labeled statements:

    public func refetchAsync() async {
      self.refetch()
      
      forLoop: for await update in self.client.queryUpdatesAsync() {
        if update.key != self.key { continue }
        switch self.dataStatus {
        case .idle, .pending: continue
        default: break forLoop
        }
      }
    }
    
    Login or Signup to reply.
  2. The only possible reason for this is your function refetchAsync() never finishes. You need to fix that first.

    As we don’t know your proper use case so assuming that in your default case you want to terminate the loop itself.

    If that is so then it should work:

      public func refetchAsync() async {
        self.refetch()
        
        for await update in self.client.queryUpdatesAsync() {
          if update.key != self.key { continue }
          switch self.dataStatus {
          case .idle, .pending: continue
          default: return
          }
        }
      }
    
    

    If it is not your user case then the asyncSequence self.client.queryUpdatesAsync() is never ending due to some of your other bug in the code. Fix that also.

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