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)
}
}