# WeatherKit: Build a Simple iOS Weather App

[WeatherKit](https://developer.apple.com/weatherkit/) framework was one of the amazing Apple announcements during [WWDC22](https://developer.apple.com/wwdc22/).

In this article, we'll create a simple iOS weather app that shows the current weather by using the WeatherKit and SwiftUI frameworks. Also, some additional information will be provided regarding the framework.

But before we start, you should know that there are some requirements you should meet:

- **Apple Developer Program membership**: for accessing WeatherKit's REST APIs by registering the app's bundle ID.
- **Xcode 14**: due to the API availability for iOS/iPadOS 16 SDKs and higher.

Additionally, you should consider these requirements by Apple if you're planning to use it in your app(s) in production:
- **It's not fully free**, only 500,000 API calls are comes for free with your membership per month. You should pay if you want to get more.  **Here's the pricing**:
500,000 calls/month: Included with membership.
1 million calls/month: US$ 49.99.
2 million calls/month: US$ 99.99.
5 million calls/month: US$ 249.99.
10 million calls/month: US$ 499.99.
20 million calls/month: US$ 999.99.
- You must put the [Apple Weather attribute](https://developer.apple.com/weatherkit/get-started/#attribution-requirements) in your app.

If you're all set, let's start. 🚀

After you set up the project on Xcode by selecting SwiftUI as the interface, you need to head over to the [Certificates, Identifiers & Profiles](https://developer.apple.com/account/resources/identifiers/list) section in App Store Connect, click on Identifiers and register your app's bundle ID. In the top left, click the add button (+), select App IDs, then click Continue. After that, register your app ID by enabling the ```WeatherKit``` checkbox in the Capabilities tab.


![Screenshot 2022-07-23 at 12.39.43 AM.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1658525989847/St95WwPbt.png align="left")

You can go back and select the Services tab if you want to manage your ```WeatherKit``` usage.

![Screenshot 2022-07-23 at 12.42.47 AM.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1658526229565/aHXXRXnZi.png align="left")

Now, get back to Xcode. Select the ```xcodeproj``` file in the Project Navigator and click on the Targets tab. In the top-left of that tab, click on the Capability and add ```WeatherKit```.

![Screenshot 2022-07-23 at 12.46.06 AM.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1658526420162/KylbUHyyX.png align="left")


That was all for the configuration, let's build the app.

As you see on the Project Navigator in Xcode, we only have the ```ContentView``` file which is the app's main view and is empty, let's leave it as it is for now.

Before dealing with ```WeatherKit```, we need to get the user's current location to retrieve the weather information. Hit ⌘+N on your keyboard and create a new file named ```LocationManager``` to create a class for the purpose. We basically ask for the user's location permission there. And then, we'll get the last updated location.

```
import CoreLocation

public class LocationManager: NSObject,
                              CLLocationManagerDelegate,
                              ObservableObject {
    
    static let shared = LocationManager()
    
    let locationManager = CLLocationManager()
    @Published var lastLocation: CLLocation?
    
    override init() {
        super.init()
        locationManager.delegate = self
        locationManager.desiredAccuracy = kCLLocationAccuracyBest
        locationManager.requestWhenInUseAuthorization()
        locationManager.startUpdatingLocation()
    }
}

extension LocationManager {
    public func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
        guard let location = locations.first else {
            return
        }
        
        lastLocation = location
        locationManager.stopUpdatingLocation()
    }
}
``` 

There's one more step we need to take for asking the user to get his/her location permission, and it's by adding a key in ```info.plist```. Click on the project file, your target, select Info tap, and then add this key with a meaningful description: 

```
Privacy - Location Usage Description
``` 


We need a model with a view model. Simply create a new file by hitting ⌘+N on your keyboard, name it ```Weather```. And then,  type ``` import WeatherKit ``` at the top of the file.
 

For the model, we call it Weather. Here are the properties that we'll be going to have inside it:
- ```temperature```: current weather temperature.
- ```condition```: current weather condition text.
- ```symbolName```: an SFSymbol icon based on the weather condition.
- ```humidity```: current weather temperature humidity rate.
- ```isDaylight```: knowing whether it's daylight or not.


```
struct Weather {
    let temperature: Double
    let condition: String
    let symbolName: String
    let humidity: Double
    let isDaylight: Bool
    
    static func empty() -> Weather {
        Weather(temperature: 0,
                condition: "",
                symbolName: "",
                humidity: 0,
                isDaylight: false)
    }
}
``` 

Now, time to create the view model. Below the model, we're creating a class called ```WeatherViewModel``` and conforming it to ```ObservableObject```.

```
class WeatherViewModel: ObservableObject {
}
``` 
We mainly use the ``` WeatherService```  that comes with ``` WeatherKit``` , so let's make an instance of it inside the class.
```
class WeatherViewModel: ObservableObject {
  let service = WeatherService()
}
``` 

We also need another instance for the ``` LocationManager```  class we created before, and a published property for saving the weather data we're fetching to use it on the view. Our class should look like this:

```
class WeatherViewModel: ObservableObject {

    let service = WeatherService()
    let locationManager = LocationManager()
    
    @Published var currentWeather: Weather = .empty()
}
``` 

Now, below the instances and the property, we're creating an asynchronous method for appending the fetched data that ```WeatherService``` does for us automatically:

```
func getWeather() async {
        do {
            guard let currentLocation = locationManager.lastLocation else {
                return
            }
            
            let weather = try await service.weather(for: currentLocation)
            
            self.currentWeather = Weather(temperature: weather.currentWeather.temperature.value,
                                          condition: weather.currentWeather.condition.rawValue,
                                          symbolName: weather.currentWeather.symbolName,
                                          humidity: weather.currentWeather.humidity,
                                          isDaylight: weather.currentWeather.isDaylight)
        } catch {
            assertionFailure(error.localizedDescription)
        }
    }
``` 
The ```currentWeather``` class, instanced in ```WeatherService```, provides some other properties, such as ```date```, ```dewPoint```, ```pressureTrend``` and more. We're only covering a few of them here. Refer to the [documentation](https://developer.apple.com/documentation/weatherkit/currentweather) for more.

Speaking about ```WeatherService```, it provides other instances, such as  ```daily```, ```hourly```, ```minute``` and others. The mentioned instances are to fetch daily weather data for the next 10 days, hourly and minute for a specific time. We're only using ```currentWeather``` of it. Read the [documentation](https://developer.apple.com/documentation/weatherkit/weatherservice) for more.


Our data model is now fully ready. Let's jump to the main view's file, ```ContentView```. We're showing the weather condition with an SF Symbol icon, a temperature label, and an SF Symbol for the humidity with a label for its rate.

```
struct ContentView: View {
    
    @ObservedObject var weatherViewModel: WeatherViewModel
    
    var body: some View {
        ScrollView(.vertical, showsIndicators: false) {
            
            
            VStack {
                WeatherView(weather: weatherViewModel.currentWeather,
                            dailyWeather: weatherViewModel.dailyWeather)
            }
            .frame(maxWidth: .infinity)
        }
        .ignoresSafeArea(.all)
        .background(LinearGradient(colors: weatherViewModel.currentWeather.isDaylight
                                   ? [.blue.opacity(0.7), .blue]
                                   : [.black.opacity(0.7), .black],
                                   startPoint: .top, endPoint: .bottom))
        .onAppear {
            Task {
                await weatherViewModel.getWeather()
            }
        }
    }
}

struct WeatherView: View {
    let weather: Weather
    
    var body: some View {
        VStack {
            
            Spacer()
            
            VStack(spacing: 30) {
                Image(systemName: weather.symbolName)
                    .resizable()
                    .scaledToFit()
                    .foregroundColor(.white)
                    .frame(width: 80, height: 80)
                
                Text(String(format: "%.0f", weather.temperature) + "°C")
                    .foregroundColor(.white)
                    .font(.largeTitle)
                
                Text(weather.condition.uppercased())
                    .foregroundColor(.white)
                    .font(.body)
                
                HStack {
                    HStack(spacing: 10) {
                        Image(systemName: "humidity.fill")
                            .resizable()
                            .scaledToFit()
                            .foregroundColor(.white)
                            .frame(width: 20, height: 20)
                        
                        Text(String(format: "%.0f", weather.humidity) + "%")
                            .foregroundColor(.white)
                            .font(.body)
                    }
                }
            }
            .padding(.top, 80)
            
            Spacer()
        }
        .padding(.top, 50)
    }
}

extension Double {
    func formattedValue(style: NumberFormatter.Style = .decimal) -> String {
        let formatter = NumberFormatter()
        formatter.numberStyle = style
        
        return formatter.string(from: NSNumber(value: self)) ?? ""
    }
}
``` 
Final result: 🪄

![Simulator Screen Shot - iPhone 13 Pro - 2022-07-23 at 01.54.02.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1658530513615/cpYWDvkmQ.png)

Finally, I want thank the Apple's Weather team for building such handy framework, especially [Novall Swift](https://twitter.com/NovallSwift) since she is the only person I know there!
