如何在呈现SwiftUI工作表视图之前等待URLSession请求完成?

如何在呈现SwiftUI工作表视图之前等待URLSession请求完成?,swift,api,rest,swiftui,urlsession,Swift,Api,Rest,Swiftui,Urlsession,这里没有 我正在制作一个歌词搜索应用程序,它只使用一个API来接收歌曲名和艺术家名,然后简单地返回歌词。我基本上有两个问题: 第一个是:我无法显示包含API信息的新工作表。因此,我的代码如下所示:在视图中,按下一个按钮,如果用户连接到internet,则该按钮调用一个执行整个API调用的方法,使用该歌曲的所有信息(姓名、艺术家和歌词)创建一个SongDetails对象,并将其添加到@Published searchedSongs数组中(之前检查同一首歌之前没有被搜索过)。完成后,我希望该表显示该

这里没有

我正在制作一个歌词搜索应用程序,它只使用一个API来接收歌曲名和艺术家名,然后简单地返回歌词。我基本上有两个问题:

第一个是:我无法显示包含API信息的新工作表。因此,我的代码如下所示:在视图中,按下一个按钮,如果用户连接到internet,则该按钮调用一个执行整个API调用的方法,使用该歌曲的所有信息(姓名、艺术家和歌词)创建一个SongDetails对象,并将其添加到@Published searchedSongs数组中(之前检查同一首歌之前没有被搜索过)。完成后,我希望该表显示该数组中的歌词。 我的问题是,当我想从视图访问searchedSongs数组时,应用程序因IndexAutoFrange错误而崩溃,因为在呈现工作表之前,它似乎并没有真正等待SongDetails对象完全添加到数组中。我想这可能是某种并发性问题。有没有办法只显示工作表是否已将SongDetails对象添加到数组?我当前的代码是:

HomeView.swift

HStack {
                        Spacer()
                        
                        Button(action: {
                            
                            if(!NetworkMonitor.shared.isConnected) {
                                self.noConnectionAlert.toggle()
                            } else {
                                viewModel.loadApiSongData(songName: songName, artistName: artistName)
                                self.showingLyricsSheet = true
                            }
                        }, label: {
                            CustomButton(sfSymbolName: "music.note", text: "Search Lyrics!")
                        })
                       
                        .alert(isPresented: $noConnectionAlert) {
                                    Alert(title: Text("No internet connection"), message: Text("Oops! It seems you arent connected to the internet. Please connect and try again!"), dismissButton: .default(Text("Got it!")))
                                }
                        Spacer()
                    }
                    .padding(.top, 20)
                    .sheet(isPresented: $showingLyricsSheet) {
                        LyricsView(vm: self.viewModel, songName: songName, artistName: artistName)
                    } 
class ViewModel : ObservableObject {
    @Published var searchedSongs = [SongDetails]()
    func loadApiSongData(songName: String, artistName: String) {
        let rawUrl = "https://api.lyrics.ovh/v1/\(artistName)/\(songName)"
        let fixedUrl = rawUrl.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)
        print("Old url: \(rawUrl)")
        print("New url: \(fixedUrl!)")
        
        guard let url = URL(string: fixedUrl!) else {
            print("Invalid URL")
            return
        }
        
        let request = URLRequest(url: url)
        
        URLSession.shared.dataTask(with: request) { data, response, error in
            if let data = data {
                if let decodedResponse = try? JSONDecoder().decode(Song.self, from: data) {
                    // we have good data – go back to the main thread
                    DispatchQueue.main.async {
                        // update our UI
                        print("Good. Lyrics:")
                        if(!self.songAlreadySearched(songName: songName)) {
                            let song = SongDetails(songName: songName, artistName: artistName, lyrics: decodedResponse.lyrics)
                            self.searchedSongs.append(song)
                        }
                    }
                    // everything is good, so we can exit
                    return
                }
            }

            // if we're still here it means there was a problem
            print("Fetch failed: \(error?.localizedDescription ?? "Unknown error")")
            
        }.resume()
    }
ScrollView {
                    Text(vm.searchedSongs[0].lyrics)
                        .foregroundColor(.white)
                        .multilineTextAlignment(.center)
                }
ViewModel.swift

HStack {
                        Spacer()
                        
                        Button(action: {
                            
                            if(!NetworkMonitor.shared.isConnected) {
                                self.noConnectionAlert.toggle()
                            } else {
                                viewModel.loadApiSongData(songName: songName, artistName: artistName)
                                self.showingLyricsSheet = true
                            }
                        }, label: {
                            CustomButton(sfSymbolName: "music.note", text: "Search Lyrics!")
                        })
                       
                        .alert(isPresented: $noConnectionAlert) {
                                    Alert(title: Text("No internet connection"), message: Text("Oops! It seems you arent connected to the internet. Please connect and try again!"), dismissButton: .default(Text("Got it!")))
                                }
                        Spacer()
                    }
                    .padding(.top, 20)
                    .sheet(isPresented: $showingLyricsSheet) {
                        LyricsView(vm: self.viewModel, songName: songName, artistName: artistName)
                    } 
class ViewModel : ObservableObject {
    @Published var searchedSongs = [SongDetails]()
    func loadApiSongData(songName: String, artistName: String) {
        let rawUrl = "https://api.lyrics.ovh/v1/\(artistName)/\(songName)"
        let fixedUrl = rawUrl.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)
        print("Old url: \(rawUrl)")
        print("New url: \(fixedUrl!)")
        
        guard let url = URL(string: fixedUrl!) else {
            print("Invalid URL")
            return
        }
        
        let request = URLRequest(url: url)
        
        URLSession.shared.dataTask(with: request) { data, response, error in
            if let data = data {
                if let decodedResponse = try? JSONDecoder().decode(Song.self, from: data) {
                    // we have good data – go back to the main thread
                    DispatchQueue.main.async {
                        // update our UI
                        print("Good. Lyrics:")
                        if(!self.songAlreadySearched(songName: songName)) {
                            let song = SongDetails(songName: songName, artistName: artistName, lyrics: decodedResponse.lyrics)
                            self.searchedSongs.append(song)
                        }
                    }
                    // everything is good, so we can exit
                    return
                }
            }

            // if we're still here it means there was a problem
            print("Fetch failed: \(error?.localizedDescription ?? "Unknown error")")
            
        }.resume()
    }
ScrollView {
                    Text(vm.searchedSongs[0].lyrics)
                        .foregroundColor(.white)
                        .multilineTextAlignment(.center)
                }
LyricsView.swift

HStack {
                        Spacer()
                        
                        Button(action: {
                            
                            if(!NetworkMonitor.shared.isConnected) {
                                self.noConnectionAlert.toggle()
                            } else {
                                viewModel.loadApiSongData(songName: songName, artistName: artistName)
                                self.showingLyricsSheet = true
                            }
                        }, label: {
                            CustomButton(sfSymbolName: "music.note", text: "Search Lyrics!")
                        })
                       
                        .alert(isPresented: $noConnectionAlert) {
                                    Alert(title: Text("No internet connection"), message: Text("Oops! It seems you arent connected to the internet. Please connect and try again!"), dismissButton: .default(Text("Got it!")))
                                }
                        Spacer()
                    }
                    .padding(.top, 20)
                    .sheet(isPresented: $showingLyricsSheet) {
                        LyricsView(vm: self.viewModel, songName: songName, artistName: artistName)
                    } 
class ViewModel : ObservableObject {
    @Published var searchedSongs = [SongDetails]()
    func loadApiSongData(songName: String, artistName: String) {
        let rawUrl = "https://api.lyrics.ovh/v1/\(artistName)/\(songName)"
        let fixedUrl = rawUrl.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)
        print("Old url: \(rawUrl)")
        print("New url: \(fixedUrl!)")
        
        guard let url = URL(string: fixedUrl!) else {
            print("Invalid URL")
            return
        }
        
        let request = URLRequest(url: url)
        
        URLSession.shared.dataTask(with: request) { data, response, error in
            if let data = data {
                if let decodedResponse = try? JSONDecoder().decode(Song.self, from: data) {
                    // we have good data – go back to the main thread
                    DispatchQueue.main.async {
                        // update our UI
                        print("Good. Lyrics:")
                        if(!self.songAlreadySearched(songName: songName)) {
                            let song = SongDetails(songName: songName, artistName: artistName, lyrics: decodedResponse.lyrics)
                            self.searchedSongs.append(song)
                        }
                    }
                    // everything is good, so we can exit
                    return
                }
            }

            // if we're still here it means there was a problem
            print("Fetch failed: \(error?.localizedDescription ?? "Unknown error")")
            
        }.resume()
    }
ScrollView {
                    Text(vm.searchedSongs[0].lyrics)
                        .foregroundColor(.white)
                        .multilineTextAlignment(.center)
                }
第二个:我很难理解URLSession如何处理错误案例。如果出于任何原因(比如我提交“asd”作为歌曲名,“fds”作为艺术家名)api检索不到歌词,我怎么能从视图中知道这一点,并且一开始就不能显示歌词表,因为根本没有任何歌词可以显示


非常感谢您的帮助。谢谢!

您的问题没有包含足够的代码,我无法向您详细说明如何操作,但我可以为您提供一般步骤

  • 不要在调用
    loadApiSongData
    后直接设置
    showingLyricsSheet
    loadApiSongData
    是异步的,因此这实际上可以保证在加载API调用之前显示工作表。相反,将工作表的表示绑定到视图模型上的一个变量,该变量仅在API请求一次被设置时才被设置est已完成。我建议使用
    工作表(项目:)
    表单,而不是
    工作表(显示:)
    ,以避免在工作表中获取最新更新值时常见的陷阱

  • 与其使用
    LyricsView
    access
    vm.searchedSongs
    ,不如直接将歌曲作为参数传递给
    LyricsView
    。同样,使用#1中的策略(包括使用
    工作表(项目:)
    )也很容易

  • 下面是一个简单的模型,演示了#1和#2中的概念:


    您的URLSession问题可能应该分解为自己的问题。在这样做时,它还可以使用一些更详细的信息,例如,如果您指的是错误案例,例如HTTP错误,或者如果您从API端点获得某些响应(即不同的JSON值)这代表了错误。谢谢!我提出了一个单独的问题,以防您想快速查看一下!非常感谢!工作表现在按预期工作,只有在请求完成后才会呈现!谢谢!我还有另一个问题,因为我缺少字符,我将详细说明以下粘贴库:谢谢!很高兴它对您有效。你可以使用绿色的复选标记来标记答案是否正确?如果有帮助,你也可以使用箭头来投票。关于你在巴斯丁的问题,我建议你使用同一张纸(项目:)我上面使用的策略。它应该解决空白歌词视图问题。绝对!考虑它完成了。顺便说一下,使用表(项目:)再次解决它。非常感谢:)。只要你有2分钟的时间,请随时研究我的另一个问题!看起来你已经得到了回复,但是如果它不符合你的需要,请告诉我,我可以尝试提出其他的建议。