Skip to content

Instantly share code, notes, and snippets.

@rkhamilton
Last active February 22, 2024 14:10
Show Gist options
  • Save rkhamilton/86f17efb711ddb5d643202e882f939b9 to your computer and use it in GitHub Desktop.
Save rkhamilton/86f17efb711ddb5d643202e882f939b9 to your computer and use it in GitHub Desktop.
A SwiftUI View that demonstrates several 404 failures from valid WeatherKit requests
// We have an app that allows users to fetch historical weather data for their selected locations.
// We are seeing users report failures for specific latitudes and longitudes that come back as
// WeatherDaemon.WDSClient<WeatherDaemon.WeatherResource>.Errors.responseFailed
// If you change the date, the query works. If you change the location, the query works.
// It's just specific lat/long/date combos that fail. There are other specific lat/long/date combos that also fail like this.
//
// I haven't figured out how to handle these errors since the error type is internal to WeatherKit and not exposed.
//
// I ALSO have a common problem that sometimes a dayWeather query will fail with 404, but if I retry a few times,
// or the next day, it will eventually succeed. I can't see a way to distinguish those scenarios.
//
// This view demonstrates the failures above. You need to add it to a Xcode project with WeatherKit Capability,
// then tap the buttons at the bottom of the view.
import SwiftUI
import CoreLocation
import WeatherKit
struct WeatherKitTroubleshootingScreen: View {
// Pressing a button below populates the UI with the description of what is going on
@State private var problemDescription: String = "Press a button below to demonstrate a WeatherKit failure"
@State private var location: CLLocation?
@State private var startDate: Date?
@State private var endDate: Date?
// Pressing a button below attempts a WeatherKit fetch and populates the results here for display
@State private var dayWeathers: [DayWeather] = []
@State private var errorDescription: String? = nil
var body: some View {
ScrollView {
VStack(alignment: .leading) {
Text(problemDescription)
.fixedSize(horizontal: false, vertical: true)
.padding(.bottom)
Text(String("WeatherKit weather(for:including:) results")).bold()
if let location,
let startDate,
let endDate {
Text("""
Arguments to *WeatherService.shared.weather*
**location:** \(location)
**startDate:** \(startDate.formatted(.iso8601))
**endDate:** \(endDate.formatted(.iso8601))
**Function call:**
WeatherService.shared.weather(
for: location,
including: .daily(
startDate: startDate,
endDate: endDate)
)
""")
let numberOfDaysInRequest = Calendar.current.dateComponents([.day], from: startDate, to: endDate).day ?? 0
Text(String("Got \(dayWeathers.count) values for \(numberOfDaysInRequest)-day request"))
}
ForEach(Array(dayWeathers.enumerated()), id: \.offset) { index, dayWeather in
HStack {
Text(String("\(index) \(dayWeather.date.formatted(date: .numeric, time: .shortened))"))
Spacer(minLength: 0)
Text(String("\(dayWeather.condition.description)"))
Divider()
}
}
if let errorDescription {
VStack(alignment: .leading) {
Text(String("Fetch failed with error")).bold()
Text(String("\(errorDescription)"))
}
.padding(.top)
}
}
.padding()
}
.safeAreaInset(edge: .bottom) {
VStack {
Button(String("Working fetch")) {
dayWeathers = []
Task {
await fetchWeatherSuccessfully()
}
}
Button(String("Unavailable dates")) {
dayWeathers = []
Task {
await fetchWeatherThrows404()
}
}
Button(String("Silently omits data")) {
dayWeathers = []
Task {
await fetchWeatherSilentlyFailsToReturnValues()
}
}
Button(String("Intermittently unavailable dates")) {
dayWeathers = []
Task {
await fetchWeatherFailsNowButHasSucceededOtherTimes()
}
}
}
.buttonStyle(.borderedProminent)
.frame(maxWidth: .infinity)
.padding(.top)
.background(.regularMaterial).edgesIgnoringSafeArea(.bottom)
}
}
// If you request a date range that includes the unavailable dates, as seen in fetchWeatherThrows404(), the request will return all OTHER dates from the requested range and silently fail to return the requested values in the middle.
private func fetchWeatherSuccessfully() async {
problemDescription = "This implements a request that works as expected, and retrieves all 10 days from a specific location."
location = CLLocation(latitude: 40.717, longitude: -74.000)
startDate = Calendar.current.date(
from: .init(
timeZone: .init(identifier: "GMT"),
year: 2023,
month: 12,
day: 1,
hour: 5,
minute: 0,
second: 0
)
)!
endDate = Calendar.current.date(
from: .init(
timeZone: .init(identifier: "GMT"),
year: 2023,
month: 12,
day: 11,
hour: 5,
minute: 0,
second: 0
)
)!
do {
guard let location, let startDate, let endDate else { return }
let forecast = try await WeatherService.shared.weather(
for: location,
including: .daily(startDate: startDate, endDate: endDate))
dayWeathers = forecast.forecast
} catch {
errorDescription = error.localizedDescription
}
}
// there are some fetches that will not return values. If you attempt to run the fetch, you get a 404 error from WeatherKit. I can't figure out how to catch the specific error type being thrown.
private func fetchWeatherThrows404() async {
problemDescription = String("This implements a request that throws a 404 error despite being a seemingly normal request for DayWeather.")
location = CLLocation(latitude: 40.717, longitude: -74.000)
startDate = Calendar.current.date(
from: .init(
timeZone: .init(identifier: "GMT"),
year: 2023,
month: 12,
day: 29,
hour: 5,
minute: 0,
second: 0
)
)!
endDate = Calendar.current.date(
from: .init(
timeZone: .init(identifier: "GMT"),
year: 2023,
month: 12,
day: 30,
hour: 5,
minute: 0,
second: 0
)
)!
do {
guard let location, let startDate, let endDate else { return }
let forecast = try await WeatherService.shared.weather(
for: location,
including: .daily(startDate: startDate, endDate: endDate))
dayWeathers = forecast.forecast
} catch {
errorDescription = error.localizedDescription
}
}
// If you request a date range that includes the unavailable dates, as seen in fetchWeatherThrows404(), the request will return all OTHER dates from the requested range and silently fail to return the requested values in the middle.
private func fetchWeatherSilentlyFailsToReturnValues() async {
problemDescription = "This implements a request that silently fails to retrieve 2 of 10 days requested, and the missing days are in the middle of the requested range."
location = CLLocation(latitude: 40.717, longitude: -74.000)
startDate = Calendar.current.date(
from: .init(
timeZone: .init(identifier: "GMT"),
year: 2023,
month: 12,
day: 24,
hour: 5,
minute: 0,
second: 0
)
)!
endDate = Calendar.current.date(
from: .init(
timeZone: .init(identifier: "GMT"),
year: 2024,
month: 1,
day: 3,
hour: 5,
minute: 0,
second: 0
)
)!
do {
guard let location, let startDate, let endDate else { return }
let forecast = try await WeatherService.shared.weather(
for: location,
including: .daily(startDate: startDate, endDate: endDate))
dayWeathers = forecast.forecast
} catch {
errorDescription = error.localizedDescription
}
}
/// This call is failing to return any values. Throws a 404. However I have run a dayWeather fetch for this location on other occasions and gotten values. Right now the entire fetch is failing.
private func fetchWeatherFailsNowButHasSucceededOtherTimes() async {
problemDescription = "This implements a request that throws a 404 error despite being a seemingly normal request for DayWeather that used to work for this location, but it suddenly started failing in mid-Feburary 2024."
location = CLLocation(latitude: 35.15695350, longitude: -85.34106370)
startDate = Calendar.current.date(
from: .init(
timeZone: .init(identifier: "GMT"),
year: 2022,
month: 2,
day: 10,
hour: 5,
minute: 0,
second: 0
)
)!
endDate = Calendar.current.date(
from: .init(
timeZone: .init(identifier: "GMT"),
year: 2022,
month: 2,
day: 20,
hour: 5,
minute: 0,
second: 0
)
)!
do {
guard let location, let startDate, let endDate else { return }
let forecast = try await WeatherService.shared.weather(
for: location,
including: .daily(startDate: startDate, endDate: endDate))
dayWeathers = forecast.forecast
} catch {
errorDescription = error.localizedDescription
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment