We will start from scratch to develop application.

First of all we will openweather website from this link: https://openweathermap.org/api

Click to Current Weather Data API doc.

We will use that weather data url, https://api.openweathermap.org/data/2.5/weather?lat={lat}&lon={lon}&appid={API key}

At here we will add latitude and longitude from google maps. I will use Istanbul latitude: 41.0053702 and longitude: 28.6825429 for the example.

Below that page there is response JSON:

                          
{
   "coord": {
      "lon": 7.367,
      "lat": 45.133
   },
   "weather": [
      {
         "id": 501,
         "main": "Rain",
         "description": "moderate rain",
         "icon": "10d"
      }
   ],
   "base": "stations",
   "main": {
      "temp": 284.2,
      "feels_like": 282.93,
      "temp_min": 283.06,
      "temp_max": 286.82,
      "pressure": 1021,
      "humidity": 60,
      "sea_level": 1021,
      "grnd_level": 910
   },
   "visibility": 10000,
   "wind": {
      "speed": 4.09,
      "deg": 121,
      "gust": 3.47
   },
   "rain": {
      "1h": 2.73
   },
   "clouds": {
      "all": 83
   },
   "dt": 1726660758,
   "sys": {
      "type": 1,
      "id": 6736,
      "country": "IT",
      "sunrise": 1726636384,
      "sunset": 1726680975
   },
   "timezone": 7200,
   "id": 3165523,
   "name": "Province of Turin",
   "cod": 200
}

After we open the quicktype.io to easily create model. we copy JSON response and paste to quicktype website: and it gives us response model.

Don't forget to name JSON data as WeatherResponse in quicktype.io website.

Open Xcode and paste response model from quicktype.io webpage.

WeatherResponse Model:

import Foundation

// MARK: - WeatherResponse
struct WeatherResponse: Codable {
    let coord: Coord
    let weather: [Weather]
    let base: String
    let main: Main
    let visibility: Int
    let wind: Wind
    let rain: Rain
    let clouds: Clouds
    let dt: Int
    let sys: Sys
    let timezone, id: Int
    let name: String
    let cod: Int
}

// MARK: - Clouds
struct Clouds: Codable {
    let all: Int
}

// MARK: - Coord
struct Coord: Codable {
    let lon, lat: Double
}

// MARK: - Main
struct Main: Codable {
    let temp, feelsLike, tempMin, tempMax: Double
    let pressure, humidity, seaLevel, grndLevel: Int

    enum CodingKeys: String, CodingKey {
        case temp
        case feelsLike = "feels_like"
        case tempMin = "temp_min"
        case tempMax = "temp_max"
        case pressure, humidity
        case seaLevel = "sea_level"
        case grndLevel = "grnd_level"
    }
}

// MARK: - Rain
struct Rain: Codable {
    let the1H: Double

    enum CodingKeys: String, CodingKey {
        case the1H = "1h"
    }
}

// MARK: - Sys
struct Sys: Codable {
    let type, id: Int
    let country: String
    let sunrise, sunset: Int
}

// MARK: - Weather
struct Weather: Codable {
    let id: Int
    let main, description, icon: String
}

// MARK: - Wind
struct Wind: Codable {
    let speed: Double
    let deg: Int
    let gust: Double
}

We finish creating response models.

WeatherRequest Model:

// MARK: - WeatherRequest
struct WeatherRequest {
    var temperature: Double
    var cityName: String
}

We create Constants to keep API key and url.

Constants:

struct Constants {
   static let api_key = "cb9460bd4572175d0b37df1e3bdc78d7"
    
    static let url = "https://api.openweathermap.org/data/2.5/weather?lat=41.0053702&lon=28.6825429&appid=\(api_key)"
}

API Manager:

We will create client request to server with URLSession.shared.dataTask object. To handle success and failures we will use Result enum in completionHandler.

// URL requests send to server and server response JSON data 
class APIManager {
    // APIManager instance 
    static let shared = APIManager()

    // Adding security with private init
    private init() { }
    
    // Full path URL 
    let url: String = Constants.url
    
    func fetchData(completion: @escaping (Result<WeatherResponse, Error>) -> Void) {
   // Putting url to URL(string: ) object and check url object is valid or not
        guard let url = URL(string: url) else { return }
     
// URL request with 
// func dataTask(with: URL, completionHandler: (Data?, URLResponse?, (any Error)?) -> Void) -> URLSessionDataTask
        URLSession.shared.dataTask(with: url) { data, response, error in
// Checking data is valid or not
            guard let data else {
                completion(.failure(DataError.invalidData))
                return
            }
// Checking response status code is between 200 and 299
            guard let response = response as? HTTPURLResponse,  200 ... 299  ~= response.statusCode else {
                completion(.failure(DataError.invalidResponse))
                return
            }
            
// Decoding JSON data and sharing data to completion success value 
// else to .failure()
            do {
                let weathers = try JSONDecoder().decode(WeatherResponse.self, from: data)
                completion(.success(weathers))
            } catch {
                completion(.failure(DataError.message(error)))
            }
        }.resume()
    }
}

// Creating natural DataError to track network errors
enum DataError: Error {
    case invalidData
    case invalidResponse
    case message(_ error: Error?)
}

We create WeatherViewModel to to fetch response to WeatherRequest model structure.

WeatherViewModel:

import Foundation


final class WeatherViewModel: ObservableObject {
// To change values by UI we use @Published 
    @Published var weatherRequest: WeatherRequest?
    
    func fetchWeatherService() {
// Calling APIManager fetchData callback 

        APIManager.shared.fetchData { [weak self] response in
// Removing optional self guard let self
            guard let self else { return }
            switch response {
            case .success(let weather):
                let temp = weather.main.temp - 273.15
// Assigning weather data to  weatherRequest property
                weatherRequest = WeatherRequest(temperature: temp, cityName: weather.name)
            case .failure(let error):
                print(error)
            }
        }
    }
}

WeatherView:

struct WeatherView: View {
    // Creating observable object with StateObject
    @StateObject private var weatherViewModel = WeatherViewModel()
    
    var body: some View {
        ZStack {
    // Linear Gradient Background 
            LinearGradient(colors: [.black.opacity(0.67), .white.opacity(0.5)], startPoint: .topLeading, endPoint: .bottomTrailing)
                .ignoresSafeArea()
            
            if let weather = weatherViewModel.weatherRequest  {
                Rectangle()
                    .foregroundStyle(.thinMaterial)
                    .frame(width: 400, height: 400)
                    .overlay {
                        VStack(spacing: 20) {
                            Text(weather.cityName)
                                .font(.system(size: 40, weight: .semibold, design: .rounded))

                            Text("\(Int(weather.temperature))")
                                .font(.system(size: 60, weight: .semibold))

                            Image(systemName: "cloud.sun.fill")
                                .renderingMode(.original)
                                .resizable()
                                .aspectRatio(contentMode: .fit)
                                .frame(width: 140, height: 140)
                        }
                        .padding()
                    }
                    .clipShape(.rect(cornerRadius: 16))
            } else {
                ProgressView()
            }
        }
        .onAppear {
     // Calling fetchWeatherService() method create request to server 
            weatherViewModel.fetchWeatherService()
        }
    }
}

#Preview {
    WeatherView()
}

Resources:

URLSesssion

JSON Response Model

OpenWeatherMap

Project Source Code

Happy Coding. 🙂