在 SwiftUI 中推送空查询后,如何使警报而不是永恒的 ProgressView 出现?

How to make alert instead of eternal ProgressView appear after pushing an empty query in SwiftUI?

我正在尝试制作一个应用程序,允许您通过提供用户的登录名来查看用户的回购列表,但我遇到了一个相当大的错误,我不知道如何摆脱它。

如何让bug出现?

  1. 通过在 TextField 中键入正确的用户登录名(例如 jacobtoye)从初始视图前进到 ListView
  2. 删除 ListView 中 TextField 的内容,然后按“查看 repos”
  3. 至少我当时没有收到错误警报,而是一个永恒的 ProgressView()

如何使从 ListView 推送的空查询显示“无效用户名错误”警报而不是 Eternal ProgressView()?

代码:

CONTENTVIEW 文件:

       import SwiftUI
  
  struct ContentView: View {
      @StateObject var viewModel = ContentViewModel()
      
      var body: some View {
          
          if viewModel.repos.isEmpty {

//Initial View code here
              
              NavigationView{
                  VStack(alignment: .leading){
                      HStack{
                              TextField("Enter GitHub username", text: $viewModel.userName)
                              Button("View repos") {
                                      viewModel.fetchRepos()
                              }
                              .alert("This user doesn't own any repositories", isPresented: $viewModel.showingSecondAlert){
                                  Button("Try again", action: viewModel.flushRepos)
                              }
                              .padding()
                          }
                      .alert("Invalid GitHub username", isPresented: $viewModel.showingMainAlert){
                          Button("Try again", action: viewModel.flushRepos)
                  }
                  .navigationTitle("GH Repo Language")
                  
                      Spacer()
                  }
                  }
              
              }
          
              else if viewModel.reposAreLoading {
                      ProgressView()
                  
              } else {

//ListView code here
                  
              NavigationView{
                  VStack{
                      HStack{
                              TextField("Enter GitHub username", text: $viewModel.userName)
                              Button("View repos") {
                                      viewModel.fetchRepos()
                              }
                              .alert("This user doesn't own any repositories", isPresented: $viewModel.showingSecondAlert){
                                  Button("Try again", action: viewModel.flushRepos)
                              }
                              .padding()
                      }
                      .alert("Invalid GitHub username", isPresented: $viewModel.showingMainAlert){
                          Button("Try again", action: viewModel.flushRepos)
                          }
                    
                      List {
                              ForEach(viewModel.repos) { repo in
                                VStack {
                                    Text("\(repo.fullName)")
                                    }
                                }
                          }
                      }
                  
                  }
                  .navigationTitle("\(viewModel.repos.first?.owner.login ?? "no name")'s repos")
                  }
              }
          }

CONTENTVIEW 模型:

import Foundation

final class ContentViewModel: ObservableObject {
    @Published var repos = [Repository]()
    @Published var reposAreLoading = false
    @Published var userName = ""
    @Published var showingMainAlert = false
    @Published var showingSecondAlert = false
    
    
    func fetchRepos() {
        
        reposAreLoading = true
        
        guard let url = URL(string: "https://api.github.com/users/\(userName)/repos") else { return }
        URLSession.shared.dataTask(with: url) {(data, _, _) in
            guard let data = data else { return }
            DispatchQueue.main.async {
                do {
                    self.repos = try JSONDecoder().decode([Repository].self, from: data)
                    self.reposAreLoading = false
                    if self.repos.isEmpty {
                        self.showingSecondAlert = true
                    }
                } catch {
                    self.showingMainAlert = true
                }
            }
        }.resume()
        
    }
    
    func flushRepos() {
        self.repos = [Repository]()
    }
}

JSON 结构:

import Foundation

struct Repository: Decodable, Identifiable {
    let id: Int
    let name: String
    let fullName: String
    let owner: Owner
    
    enum CodingKeys: String, CodingKey {
        case id, owner
        case name
        case fullName = "full_name"
    }
}

struct Owner : Decodable, Identifiable {
    let id: Int
    let login: String
    let reposUrl : String

    enum CodingKeys: String, CodingKey, CaseIterable {
        case id
        case login
        case reposUrl = "repos_url"
    }
}

对我来说,似乎 Xcode 在从 ListView 推送一个空查询后无法实现 fetchRepos() 函数,但我不知道如何找出要解决的问题。请帮忙。

现在,您不测试 URL 响应中的错误代码。无效的用户名 returns 和 404,因此您需要检查一下(请注意,您还可以在尝试之前检查 用户名获取结果)。

URLSession.shared.dataTask(with: url) {(data, response, error) in
    DispatchQueue.main.async {
        self.reposAreLoading = false
        
        // may want to check `error` here, too

        if let response = response as? HTTPURLResponse, response.statusCode == 404 {
            self.showingMainAlert = true
            return
        }

        //consider handling other error codes?

        guard let data = data else { return }
        
        do {
            self.repos = try JSONDecoder().decode([Repository].self, from: data)
            if self.repos.isEmpty {
                self.showingSecondAlert = true
            }
        } catch {
            self.showingMainAlert = true
        }
    }
}.resume()

查看内联评论以了解其他一些注释

还可能值得注意的是,您可以使用 async/await 对其进行现代化改造,这将简化您的代码。参见 https://www.hackingwithswift.com/books/ios-swiftui/sending-and-receiving-codable-data-with-urlsession-and-swiftui