import SwiftUI private let WEATHER_STATUS_MAP: [String: String] = [ "Rain": "дождливо", "Clouds": "облачно", "Clear": "солнечно", "Thunderstorm": "гроза", "Snow": "снег", "Drizzle": "мелкий дождь", "Fog": "туман" ] struct FormattedWeather { let temperature: Int let status: String let precipitation: Int? let windSpeed: Double? let dayOfWeek: String? } struct WeatherView: View { @State private var todayWeather: FormattedWeather? @State private var forecast: [FormattedWeather] = [] var body: some View { HStack(alignment: .center, spacing: 4) { if let today = todayWeather { // ЛЕВЫЙ СТОЛБЕЦ: Иконка, температура и статус VStack(alignment: .leading, spacing: 2) { Image(getWeatherIconName(for: today.status)) .resizable() .aspectRatio(contentMode: .fit) .frame(width: 36, height: 36) .padding(.bottom, 5) Text("\(today.temperature)°") .font(.system(size: 30, weight: .bold)) .foregroundColor(.white) Text(today.status) .font(.system(size: 12)) .foregroundColor(.white) } // ПРАВЫЙ СТОЛБЕЦ: Прогноз с иконками VStack(alignment: .leading, spacing: 10) { // Прогноз на 3 дня ForEach(forecast.prefix(3).indices, id: \.self) { index in let day = forecast[index] HStack(spacing: 5) { Image(getWeatherIconName(for: day.status)) .resizable() .aspectRatio(contentMode: .fit) .frame(width: 14, height: 14) if let dayName = day.dayOfWeek { Text(dayName) .font(.system(size: 12)) .foregroundColor(.white) } Text("\(day.temperature)°") .font(.system(size: 12)) .foregroundColor(.white) .fontWeight(.bold) } } // Влажность и скорость ветра if let precipitation = today.precipitation { HStack(spacing: 5) { Image("det_humidity") .resizable() .aspectRatio(contentMode: .fit) .frame(width: 14, height: 14) Text("\(precipitation)%") .font(.system(size: 12)) .foregroundColor(.white) } } if let windSpeed = today.windSpeed { HStack(spacing: 5) { Image("det_wind_speed") .resizable() .aspectRatio(contentMode: .fit) .frame(width: 14, height: 14) Text("\(Int(windSpeed)) м/с") .font(.system(size: 12)) .foregroundColor(.white) } } } } else { Text("Загрузка погоды...") .foregroundColor(.white) .padding() } } .padding(.horizontal, 10) .frame(maxWidth: .infinity, maxHeight: .infinity) .aspectRatio(1, contentMode: .fit) .background( ZStack { Color(hex: 0x806C59) LinearGradient( stops: [ .init(color: .white.opacity(0.0), location: 0.0871), .init(color: .white.opacity(0.16), location: 0.6969) ], startPoint: .bottomLeading, endPoint: .topTrailing ) } ) .cornerRadius(25) .shadow(color: Color.black.opacity(0.10), radius: 8, x: 0, y: 2) .task { await fetchAndFormatWeather() } } private func fetchAndFormatWeather() async { let lat = 59.938784 let lng = 30.314997 guard let url = URL(string: "https://white-nights.krbl.ru/services/weather") else { return } var request = URLRequest(url: url) request.httpMethod = "POST" request.setValue("application/json", forHTTPHeaderField: "Content-Type") let parameters: [String: Any] = [ "coordinates": ["latitude": lat, "longitude": lng] ] do { request.httpBody = try JSONSerialization.data(withJSONObject: parameters) let (data, _) = try await URLSession.shared.data(for: request) // 💡 Отладка: Печатаем полученные данные для проверки // print("Received Data (String): \(String(data: data, encoding: .utf8) ?? "N/A")") let weatherResponse = try JSONDecoder().decode(WeatherResponse.self, from: data) formatWeatherData(data: weatherResponse) } catch { print("Ошибка загрузки погоды: \(error)") } } private func formatWeatherData(data: WeatherResponse) { let dateFormatter = DateFormatter() dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss" dateFormatter.timeZone = TimeZone(secondsFromGMT: 0) // UTC dateFormatter.locale = Locale(identifier: "en_US_POSIX") // Добавление POSIX-локали для надежного парсинга var calendar = Calendar.current calendar.timeZone = TimeZone(secondsFromGMT: 0)! // UTC // 1. Фильтруем все записи, которые соответствуют 12:00 UTC let middayForecast = data.forecast.filter { item in guard let date = dateFormatter.date(from: item.date) else { // 💡 Отладка: если здесь вы увидите сообщения, значит, формат даты API не совпадает с форматом dateFormatter // print("Парсинг даты провален для: \(item.date)") return false } return calendar.component(.hour, from: date) == 12 } // 💡 Отладка: Проверяем, сколько записей найдено // print("Найдено записей на 12:00 UTC: \(middayForecast.count)") // Сегодня if let today = data.currentWeather { self.todayWeather = FormattedWeather( temperature: Int(today.temperatureCelsius.rounded()), status: WEATHER_STATUS_MAP[today.description] ?? today.description, precipitation: today.humidity, windSpeed: today.windSpeed, dayOfWeek: nil ) } // 🚀 ИСПРАВЛЕНИЕ: Прогноз на 3 дня. Берем первые 3 доступные записи с 12:00. var formattedForecast: [FormattedWeather] = [] // Перебираем первые 3 записи с 12:00 UTC for item in middayForecast.prefix(3) { guard let date = dateFormatter.date(from: item.date) else { continue } let averageTemp = (item.minTemperatureCelsius + item.maxTemperatureCelsius) / 2 let dayOfWeekString = getDayOfWeek(from: date) // 💡 Отладка: Печатаем найденный прогноз // print("Прогноз найден: \(dayOfWeekString) - \(Int(averageTemp.rounded()))°") formattedForecast.append(FormattedWeather( temperature: Int(averageTemp.rounded()), status: WEATHER_STATUS_MAP[item.description] ?? item.description, precipitation: item.humidity, windSpeed: item.windSpeed, dayOfWeek: dayOfWeekString )) } // Если данных меньше 3, заполняем оставшиеся места нулями (N/A) let daysToFill = 3 - formattedForecast.count if daysToFill > 0 { let baseDate = Date() for i in 1...daysToFill { guard let nextDayForNA = Calendar.current.date(byAdding: .day, value: formattedForecast.count + i, to: baseDate) else { continue } let dayOfWeekString = getDayOfWeek(from: nextDayForNA) formattedForecast.append(FormattedWeather( temperature: 0, status: "N/A", precipitation: nil, windSpeed: nil, dayOfWeek: dayOfWeekString )) } } self.forecast = formattedForecast } private func getDayOfWeek(from date: Date) -> String { let formatter = DateFormatter() formatter.locale = Locale(identifier: "ru_RU") formatter.dateFormat = "E" return formatter.string(from: date).capitalized } private func getWeatherIconName(for status: String) -> String { let normalizedStatus = status.lowercased() switch normalizedStatus { case "солнечно": return "cond_sunny" case "облачно": return "cond_cloudy" case "дождливо", "мелкий дождь": return "cond_rainy" case "снег": return "cond_snowy" case "гроза": return "cond_thunder" case "туман": return "det_humidity" default: return "cond_sunny" } } }