import SwiftUI
struct AsyncDataLoaderView: View {
@State private var joke: String = ""
@State private var isLoading = false
@State private var errorMessage: String? = nil
var body: some View {
VStack(spacing: 20) {
if isLoading {
ProgressView("Loading...")
.progressViewStyle(CircularProgressViewStyle())
.accessibilityLabel("Loading joke")
} else if let error = errorMessage {
Text("Error: \(error)")
.foregroundColor(.red)
.multilineTextAlignment(.center)
.accessibilityLabel("Error message")
} else if !joke.isEmpty {
Text(joke)
.font(.title3)
.multilineTextAlignment(.center)
.accessibilityLabel("Joke")
} else {
Text("No joke loaded.")
.foregroundColor(.gray)
}
}
.padding()
.task {
await fetchJoke()
}
}
func fetchJoke() async {
isLoading = true
errorMessage = nil
joke = ""
guard let url = URL(string: "https://official-joke-api.appspot.com/random_joke") else {
errorMessage = "Invalid URL"
isLoading = false
return
}
do {
let (data, _) = try await URLSession.shared.data(from: url)
if let decoded = try? JSONDecoder().decode(Joke.self, from: data) {
joke = "\(decoded.setup)\n\n\(decoded.punchline)"
} else {
errorMessage = "Failed to decode joke"
}
} catch {
errorMessage = error.localizedDescription
}
isLoading = false
}
}
struct Joke: Decodable {
let setup: String
let punchline: String
}
struct AsyncDataLoaderView_Previews: PreviewProvider {
static var previews: some View {
AsyncDataLoaderView()
}
}This solution uses SwiftUI's .task modifier to start fetching the joke asynchronously when the view appears. The fetchJoke() function uses Swift's async/await with URLSession.shared.data(from:) to get data from the joke API.
While loading, a ProgressView spinner is shown. If the fetch succeeds, the joke's setup and punchline are displayed. If an error occurs, an error message is shown in red.
Accessibility labels are added for screen readers. The UI updates reactively based on the state variables isLoading, joke, and errorMessage.