Predicting weather and potentially helping with agriculture

Hello,
I have been learning swift for about a month now and I followed a few tutorials but I am still a beginner.
I was learning how to make a weather app in hopes of it being used to help farmers with what to do with unpredictable weather etc.
So far I created a simple weather app and a few small lines of code for a type of prediction. However, I keep getting errors and i'm not sure why and I don't know how to make it more efficient and maybe I should use data or floods or heat.
I added all my codes below but you obviously don't have to read it because I'm pretty sure it was working fine until i added my touches on the last file called WeatherView (so probably need to take a look at that first) and it got errors

Below are all the files and codes inside:

WeatherManager


import Foundation
import CoreLocation

class WeatherManager {
    func getCurrentWeather(latitude: CLLocationDegrees, longitude: CLLocationDegrees) async throws -> ResponseBody {
        guard let url = URL(string: "abc") }


        let urlRequest = URLRequest(url: url)
        
        let (data, response) = try await URLSession.shared.data(for: urlRequest)
        
        guard (response as? HTTPURLResponse)?.statusCode == 200 else { fatalError("Error while fetching data") }
        
        let decodedData = try JSONDecoder().decode(ResponseBody.self, from: data)
        
        return decodedData
    }
}

// Model of the response body we get from calling the OpenWeather API
struct ResponseBody: Decodable {
    var coord: CoordinatesResponse
    var weather: [WeatherResponse]
    var main: MainResponse
    var name: String
    var wind: WindResponse

    struct CoordinatesResponse: Decodable {
        var lon: Double
        var lat: Double
    }

    struct WeatherResponse: Decodable {
        var id: Double
        var main: String
        var description: String
        var icon: String
    }

    struct MainResponse: Decodable {
        var temp: Double
        var feels_like: Double
        var temp_min: Double
        var temp_max: Double
        var pressure: Double
        var humidity: Double
    }
    
    struct WindResponse: Decodable {
        var speed: Double
        var deg: Double
    }
}

extension ResponseBody.MainResponse {
    var feelsLike: Double { return feels_like }
    var tempMin: Double { return temp_min }
    var tempMax: Double { return temp_max }
}

weatherdata.json


{
    "coord": {
        "lon": -74.006,
        "lat": 40.7143
    },
    "weather": [
        {
            "id": 804,
            "main": "Clouds",
            "description": "overcast clouds",
            "icon": "04d"
        }
    ],
    "base": "stations",
    "main": {
        "temp": 3.42,
        "feels_like": 0.51,
        "temp_min": 1.36,
        "temp_max": 5.28,
        "pressure": 1015,
        "humidity": 73
    },
    "visibility": 10000,
    "wind": {
        "speed": 3.13,
        "deg": 274,
        "gust": 4.92
    },
    "clouds": {
        "all": 90
    },
    "dt": 1638298034,
    "sys": {
        "type": 2,
        "id": 2039034,
        "country": "US",
        "sunrise": 1638273608,
        "sunset": 1638307792
    },
    "timezone": -18000,
    "id": 5128581,
    "name": "New York",
    "cod": 200
}

Welcome View:

import CoreLocationUI

struct Welcome_View: View {
    @EnvironmentObject var locationManager: LocationManager
    
    var body: some View {
        VStack {
            VStack(spacing: 20) {
                Text("Welcome to GROW!")
                    .bold()
                    .font(.title)
                Text("This App requires you to share your location")
                    .padding()
            }
            .multilineTextAlignment(.center)
            .padding()
            
            
            LocationButton(.shareCurrentLocation) {
                locationManager.requestLocation()
            }
            .cornerRadius(30)
            .foregroundColor(.white)
            .frame(maxWidth: .infinity)
            
        }
        .frame(maxWidth: .infinity, maxHeight: .infinity)
    }
}

struct Welcome_View_Previews: PreviewProvider {
    static var previews: some View {
        Welcome_View()
            .environmentObject(LocationManager())
    }
}

Loading View:

import SwiftUI

struct LoadingView: View {
    var body: some View {
        ProgressView()
            .progressViewStyle(CircularProgressViewStyle(tint:.white))
            .frame(maxWidth: .infinity, maxHeight: .infinity)
            .background(Color(hue: 0.373, saturation: 0.368, brightness: 0.373))
    }
}

#Preview {
    LoadingView()
}

Model Data:



import Foundation


var previewWeather: ResponseBody = load("weatherData.json")

func load<T: Decodable>(_ filename: String) -> T {
    let data: Data

    guard let file = Bundle.main.url(forResource: filename, withExtension: nil)
        else {
            fatalError("Couldn't find \(filename) in main bundle.")
    }

    do {
        data = try Data(contentsOf: file)
    } catch {
        fatalError("Couldn't load \(filename) from main bundle:\n\(error)")
    }

    do {
        let decoder = JSONDecoder()
        return try decoder.decode(T.self, from: data)
    } catch {
        fatalError("Couldn't parse \(filename) as \(T.self):\n\(error)")
    }
}

WeatherRow:


//

import SwiftUI

struct WeatherRow: View {
    var logo: String
    var name: String
    var value: String
    
    var body: some View {
        HStack(spacing: 20) {
            Image(systemName: logo)
                .font(.title2)
                .frame(width: 20, height: 20)
                .padding()
                .background(Color(hue: 1.0, saturation: 0.0, brightness: 0.888))
                .cornerRadius(50)

            
            VStack(alignment: .leading, spacing: 8) {
                Text(name)
                    .font(.caption)
                
                Text(value)
                    .bold()
                    .font(.title)
            }
        }
    }
}

struct WeatherRow_Previews: PreviewProvider {
    static var previews: some View {
        WeatherRow(logo: "thermometer", name: "Feels like", value: "8°")
    }
}

Extensions:

//


import Foundation
import SwiftUI

extension Double {
    func roundDouble() -> String {
        return String(format: "%.0f", self)
    }
}


extension View {
    func cornerRadius(_ radius: CGFloat, corners: UIRectCorner) -> some View {
        clipShape(RoundedCorner(radius: radius, corners: corners) )
    }
}

struct RoundedCorner: Shape {
    var radius: CGFloat = .infinity
    var corners: UIRectCorner = .allCorners

    func path(in rect: CGRect) -> Path {
        let path = UIBezierPath(roundedRect: rect, byRoundingCorners: corners, cornerRadii: CGSize(width: radius, height: radius))
        return Path(path.cgPath)
    }
}

ContentView:

import SwiftUI

struct ContentView: View {
    
    @StateObject var locationManager = LocationManager()
    var weatherManager = WeatherManager()
    @State var weather: ResponseBody?
    
    var body: some View {
        VStack {
            if let location = locationManager.location {
                if let weather = weather {
                    WeatherView(weather: weather)
                } else {
                    LoadingView()
                        .task {
                            do {
                                weather = try await weatherManager.getCurrentWeather(latitude: location.latitude, longitude: location.longitude)
                            } catch {
                                print("Error getting weather: \(error)")
                            }
                        }
                }
            } else {
                if locationManager.isLoading {
                    LoadingView()
                } else {
                    Welcome_View()
                        .environmentObject(locationManager)
                }
            }
        }
        .background(Color(hue: 0.373, saturation: 0.368, brightness: 0.373)) .preferredColorScheme(.dark)
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

Locationmanager:

import Foundation
import CoreLocation

class LocationManager: NSObject, ObservableObject, CLLocationManagerDelegate {
    let manager = CLLocationManager()
    
    @Published var location: CLLocationCoordinate2D?
    @Published var isLoading = false
    
    override init() {
        super.init()
        manager.delegate = self
    }
    
    func requestLocation() {
        isLoading = true
        manager.requestLocation()
    }
    
    func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
        location = locations.first?.coordinate
        isLoading = false
    }
    
    func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
        print("Error getting location:", error)
        isLoading = false
    }
}

This is where I edited---> WeatherView:

import SwiftUI

struct WeatherResponseBody {
    var name: String
    var main: Main
    var weather: [Weather]
    var wind: Wind
}

struct Main {
    var tempMin: Double
    var tempMax: Double
    var feelsLike: Double
    var humidity: Double
}

struct Weather {
    var main: String
}

struct Wind {
    var speed: Double

}

struct WeatherPrediction {

    var predictedCondition: String {
        if Weather.main.tempMin < 10 && Weather.main.humidity < 30 {
            return "High Drought Risk"
        } else if weather.main.tempMax > 30 && Weather.main.humidity < 50 {
            return "Heat Stress Potential"
        } else if Weather.main.humidity > 80 {
            return "Fungal Disease Risk"
        } else {
            return "Optimal Growing Conditions"
        }
    }
}


// WeatherView
struct WeatherView: View {
    var prediction: WeatherPrediction

    var body: some View {
        ZStack(alignment: .leading) {
            VStack {
                VStack(alignment: .leading, spacing: 5) {
                    Text(Weather.name)
                        .bold()
                        .font(.title)
                    
                    Text("Today, \(Date().formatted(.dateTime.month().day().hour().minute()))")
                        .fontWeight(.light)
                }
                .frame(maxWidth: .infinity, alignment: .leading)
                
                Spacer()
                
                VStack {
                    HStack {
                        VStack(spacing: 20) {
                            Image(systemName: "cloud")
                                .font(.system(size: 40))
                            
                            Text("\(weather.weather[0].main)")
                        }
                        .frame(width: 150, alignment: .leading)
                        
                        Spacer()
                        
                        Text("\(weather.main.feelsLike.roundDouble())°")
                            .font(.system(size: 100))
                            .fontWeight(.bold)
                            .padding()
                    }
                    
                    Spacer()
                        .frame(height: 80)
                    
                    AsyncImage(url: URL(string: "https://cdn.pixabay.com/photo/2020/01/24/21/33/city-4791269_960_720.png")) { image in
                        image
                            .resizable()
                            .aspectRatio(contentMode: .fit)
                            .frame(width: 350)
                    } placeholder: {
                        ProgressView()
                    }
                    
                    Spacer()
                }
                .frame(maxWidth: .infinity, alignment: .trailing)
            }
            .padding()
            .frame(maxWidth: .infinity, alignment: .leading)
            
            VStack {
                Spacer()
                VStack(alignment: .leading, spacing: 20) {
                    Text("Weather now")
                        .bold()
                        .padding(.bottom)
                    
                    HStack {
                        WeatherRow(logo: "thermometer", name: "Min temp", value: "\(weather.main.tempMin.roundDouble())°")
                        Spacer()
                        WeatherRow(logo: "thermometer", name: "Max temp", value: "\(weather.main.tempMax.roundDouble())°")
                    }
                    
                    HStack {
                        WeatherRow(logo: "wind", name: "Wind speed", value: "\(weather.wind.speed.roundDouble()) m/s")
                        Spacer()
                        WeatherRow(logo: "humidity", name: "Humidity", value: "\(weather.main.humidity.roundDouble())%")
                        Spacer()
                    }
                    
                    Text("Your crops may experience \(prediction.predictedCondition)")
                        .italic()
                        .font(.body)
                        .frame(maxWidth: .infinity, alignment: .leading)
                }
                .frame(maxWidth: .infinity, alignment: .leading)
                .padding()
                .padding(.bottom, 20)
                .foregroundColor(Color(hue: 0.373, saturation: 0.368, brightness: 0.373))
                .background(.white)
                .cornerRadius(20, corners: [.topLeft, .topRight])
            }
        }
        .edgesIgnoringSafeArea(.bottom)
        .background(Color(hue: 0.373, saturation: 0.368, brightness: 0.373))
        .preferredColorScheme(.dark)
    }
}

struct WeatherView_Previews: PreviewProvider {
    static var previews: some View {
        // Ensure previewWeather is defined and available
        let prediction = WeatherPrediction(weather: previewWeather)
        WeatherView(weather: previewWeather, prediction: prediction)
    }
}

Where do you get weather data? Do you use WeatherKit?

1 Like

Yes I also imported corelocation