如何在呈现SwiftUI工作表视图之前等待URLSession请求完成?
这里没有 我正在制作一个歌词搜索应用程序,它只使用一个API来接收歌曲名和艺术家名,然后简单地返回歌词。我基本上有两个问题: 第一个是:我无法显示包含API信息的新工作表。因此,我的代码如下所示:在视图中,按下一个按钮,如果用户连接到internet,则该按钮调用一个执行整个API调用的方法,使用该歌曲的所有信息(姓名、艺术家和歌词)创建一个SongDetails对象,并将其添加到@Published searchedSongs数组中(之前检查同一首歌之前没有被搜索过)。完成后,我希望该表显示该数组中的歌词。 我的问题是,当我想从视图访问searchedSongs数组时,应用程序因IndexAutoFrange错误而崩溃,因为在呈现工作表之前,它似乎并没有真正等待SongDetails对象完全添加到数组中。我想这可能是某种并发性问题。有没有办法只显示工作表是否已将SongDetails对象添加到数组?我当前的代码是: HomeView.swift如何在呈现SwiftUI工作表视图之前等待URLSession请求完成?,swift,api,rest,swiftui,urlsession,Swift,Api,Rest,Swiftui,Urlsession,这里没有 我正在制作一个歌词搜索应用程序,它只使用一个API来接收歌曲名和艺术家名,然后简单地返回歌词。我基本上有两个问题: 第一个是:我无法显示包含API信息的新工作表。因此,我的代码如下所示:在视图中,按下一个按钮,如果用户连接到internet,则该按钮调用一个执行整个API调用的方法,使用该歌曲的所有信息(姓名、艺术家和歌词)创建一个SongDetails对象,并将其添加到@Published searchedSongs数组中(之前检查同一首歌之前没有被搜索过)。完成后,我希望该表显示该
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
accessvm.searchedSongs
,不如直接将歌曲作为参数传递给LyricsView
。同样,使用#1中的策略(包括使用工作表(项目:)
)也很容易
您的URLSession问题可能应该分解为自己的问题。在这样做时,它还可以使用一些更详细的信息,例如,如果您指的是错误案例,例如HTTP错误,或者如果您从API端点获得某些响应(即不同的JSON值)这代表了错误。谢谢!我提出了一个单独的问题,以防您想快速查看一下!非常感谢!工作表现在按预期工作,只有在请求完成后才会呈现!谢谢!我还有另一个问题,因为我缺少字符,我将详细说明以下粘贴库:谢谢!很高兴它对您有效。你可以使用绿色的复选标记来标记答案是否正确?如果有帮助,你也可以使用箭头来投票。关于你在巴斯丁的问题,我建议你使用同一张纸(项目:)我上面使用的策略。它应该解决空白歌词视图问题。绝对!考虑它完成了。顺便说一下,使用表(项目:)再次解决它。非常感谢:)。只要你有2分钟的时间,请随时研究我的另一个问题!看起来你已经得到了回复,但是如果它不符合你的需要,请告诉我,我可以尝试提出其他的建议。