【问题标题】:How to update isLoading state from within URLSession.dataTask() completion handler in Swift?如何从 Swift 中的 URLSession.dataTask() 完成处理程序中更新 isLoading 状态?
【发布时间】:2023-01-26 19:51:04
【问题描述】:

我在 Swift 中创建了一个视图(称为AddressInputView),它应该执行以下操作:

  1. 从用户输入中获取地址
  2. 当用户点击提交时,启动 ProgressView 动画并将地址发送到后端
  3. 调用返回后,切换到 ResultView 并显示结果

    我的问题是,一旦用户点击提交,视图就会立即切换到 ResultView,而无需等待 API 调用返回。因此,ProgressView 动画仅在一瞬间可见。

    这是我的代码:

    地址输入视图

    struct AddressInputView: View {
        @State var buttonSelected = false
        @State var radius = 10_000 // In meters
        @State var isLoading = false
        @State private var address: String = ""
        @State private var results: [Result] = []
    
        func onSubmit() {
            if !address.isEmpty {
                fetch()
            }
        }
    
        func fetch() {
            results.removeAll()
            isLoading = true
    
            let backendUrl = Bundle.main.object(forInfoDictionaryKey: "BACKEND_URL") as? String ?? ""
    
            let escapedAddress = address.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed) ?? ""
            let params = "address=\(escapedAddress)&radius=\(radius)"
            let fullUrl = "\(backendUrl)/results?\(params)"
    
            var request = URLRequest(url: URL(string: fullUrl)!)
            request.httpMethod = "GET"
    
            let session = URLSession.shared
            let task = session.dataTask(with: request, completionHandler: { data, _, _ in
                if data != nil {
                    do {
                        let serviceResponse = try JSONDecoder().decode(ResultsServiceResponse.self, from: data!)
                        self.results = serviceResponse.results
                    } catch let jsonError as NSError {
                        print("JSON decode failed: ", String(describing: jsonError))
                    }
                }
                isLoading = false
            })
    
            buttonSelected = true
            task.resume()
        }
    
        var body: some View {
            NavigationStack {
                if isLoading {
                    ProgressView()
                } else {
                    VStack {
                        TextField(
                            "",
                            text: $address,
                            prompt: Text("Search address").foregroundColor(.gray)
                        )
                        .onSubmit {
                            onSubmit()
                        }
    
                        Button(action: onSubmit) {
                            Text("Submit")
                        }
                        .navigationDestination(
                            isPresented: $buttonSelected,
                            destination: { ResultView(
                                address: $address,
                                results: $results
                            )
                            }
                        )
                    }
                }
            }
        }
    }
    

    因此,我尝试在 session.dataTask 的完成处理程序中将 buttonSelected = true 移动到 isLoading = false 旁边,但如果我这样做,将不会显示 ResultView。难道不能从completionHandler 内部更新状态吗?如果是,为什么会这样,解决方法是什么?

    主要问题:我如何更改上面的代码,以便在 API 调用完成之前不显示 ResultView? (虽然 API 调用尚未完成,但我希望显示 ProgressView)。

【问题讨论】:

    标签: ios swift swiftui


    【解决方案1】:

    我认为问题在于 URLSession 的完成处理程序是在后台线程上执行的。您必须将与 UI 相关的 API 分派到主线程。

    但我建议利用 async/await,而不是使用 URLComponents/URLQueryItem 使用字符串插值构建 URL。它代表您处理必要的百分比编码

    func fetch() {
        results.removeAll()
        isLoading = true
    
        let backendUrlString = Bundle.main.object(forInfoDictionaryKey: "BACKEND_URL") as! String
        var components = URLComponents(string: backendUrlString)!
        components.path = "/results"
        components.queryItems = [
            URLQueryItem(name: "address", value: address),
            URLQueryItem(name: "radius", value: radius)
        ]
        Task {
            do {
                let (data, _ ) = try await URLSession.shared.data(from: components.url!)
                let serviceResponse = try JSONDecoder().decode(ResultsServiceResponse.self, from: data)
                self.results = serviceResponse.results
                isLoading = false
                buttonSelected = true
            } catch {
                print(error)
                // show something to the user
            }
        }
    }
    

    URLRequest 不是必需的,GET 是默认值。
    您可以强制解包 Info.plist 字典的值。如果它不存在,你就犯了设计错误。

    【讨论】:

      【解决方案2】:

      fetch() 函数调用一个异步函数 session.dataTask,它在数据任务完成之前立即返回。

      这些天解决这个问题的最简单方法是切换到使用async函数,例如

      func onSubmit() {
          if !address.isEmpty {
              Task {
                  do {
                      try await fetch()
                  } catch {
                      print("Error (error.localizedDescription)")
                  }
              }
          }
      }
      
      func fetch() async throws {
          results.removeAll()
          isLoading = true
      
          let backendUrl = Bundle.main.object(forInfoDictionaryKey: "BACKEND_URL") as? String ?? ""
      
          let escapedAddress = address.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed) ?? ""
          let params = "address=(escapedAddress)&radius=(radius)"
          let fullUrl = "(backendUrl)/results?(params)"
      
          var request = URLRequest(url: URL(string: fullUrl)!)
          request.httpMethod = "GET"
      
          let session = URLSession.shared
          let (data, _) = try await session.data(for: request)
          let serviceResponse = try JSONDecoder().decode(ResultsServiceResponse.self, from: data)
          self.results = serviceResponse.results
          isLoading = false
          buttonSelected = true
      }
      

      在上面的代码中,fetch() 函数在调用 session.data(for: request) 时暂停,只有在完成后才会恢复。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多