import SwiftUI
struct Joke: Decodable {
let setup: String
let punchline: String
}
struct ContentView: View {
@State private var jokeText = "Press the button to fetch a joke."
@State private var isLoading = false
@State private var showAlert = false
@State private var alertMessage = ""
var body: some View {
VStack {
Spacer()
Text(jokeText)
.multilineTextAlignment(.center)
.padding()
Spacer()
if isLoading {
ProgressView()
.padding()
}
Button("Fetch Joke") {
Task {
await fetchJoke()
}
}
.padding()
}
.alert(isPresented: $showAlert) {
Alert(title: Text("Error"), message: Text(alertMessage), dismissButton: .default(Text("OK")))
}
}
func fetchJoke() async {
isLoading = true
defer { isLoading = false }
guard let url = URL(string: "https://official-joke-api.appspot.com/random_joke") else {
alertMessage = "Invalid URL."
showAlert = true
return
}
do {
let (data, response) = try await URLSession.shared.data(from: url)
guard let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200 else {
alertMessage = "Server error."
showAlert = true
return
}
let joke = try JSONDecoder().decode(Joke.self, from: data)
jokeText = "\(joke.setup)\n\n\(joke.punchline)"
} catch {
alertMessage = "Failed to fetch joke. Please try again."
showAlert = true
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
This solution uses SwiftUI with async/await to fetch a joke from the API.
We define a Joke struct matching the JSON fields setup and punchline.
The fetchJoke() function is marked async and uses URLSession.shared.data(from:) with await to perform the GET request.
We check the HTTP response code to ensure success, then decode the JSON data into a Joke instance.
The joke text is updated on the main thread by changing the jokeText state variable.
Loading state is managed with isLoading to show a spinner while fetching.
Errors show an alert with a friendly message.
The button triggers the async fetch inside a Task to run asynchronously.