I want to get the user’s current location using the LocationButton and then display some Weather Data. Watching some tutorials and stackoverflow, I only saw things relating to the LocationButton and getting permission. I currently have it set up to search a city by name to get the data but want something to automatically be displayed after getting permission to share their location. I have a separate LocationManager and have a separate method but am a bit lost on displaying the data to the UI. I’ve tried different ways I cant seem to put it all together.
Here is my LocationManager
import CoreLocation
import Foundation
class LocationManager: NSObject, ObservableObject, CLLocationManagerDelegate {
let locationManager = CLLocationManager()
@Published var location: CLLocationCoordinate2D?
override init() {
super.init()
locationManager.delegate = self
}
func requestLocation(){
locationManager.requestLocation()
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
location = locations.first?.coordinate
}
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
print("There is an error getting your location", error)
}
}
My ViewModel
import Foundation
import CoreLocation
import SwiftUI
class WeatherViewModel: ObservableObject {
@Published var weatherData: Weather?
@AppStorage("cityName") var cityName = ""
init(){
getWeatherData(cityName)
}
//Init method gets run when a new instance of WeatherModel is created
//MARK: - OpenWeatherMap API methods
func getWeatherData(_ cityName: String){
CLGeocoder().geocodeAddressString(cityName){(placemarks, error ) in
if let error = error {
print(error)
}
if let lat = placemarks?.first?.location?.coordinate.latitude,
let lon = placemarks?.first?.location?.coordinate.longitude {
let weatherUrlString = "https://api.openweathermap.org/data/2.5/onecall?lat=\(lat)&lon=\(lon)&exclude=minutely,alerts&units=imperial&appid=\(Constants.apiKey)"
let weatherUrl = URL(string: weatherUrlString)
guard weatherUrl != nil else {
return
}
let request = URLRequest(url: weatherUrl!)
//Create a URL session
let session = URLSession.shared
let dataTask = session.dataTask(with: request) { data, response, error in
guard error == nil else {
return
}
do{
let decoder = JSONDecoder()
let result = try decoder.decode(Weather.self, from: data!)
DispatchQueue.main.async {
self.weatherData = result
}
}catch {
print(error)
}
}
dataTask.resume()
}
}
}//func getWeatherData
func getCurrentWeather(latitude: CLLocationDegrees, longitude: CLLocationDegrees) {
let weatherStringUrl = "https://api.openweathermap.org/data/2.5/onecall?lat=\(latitude)&lon=\(longitude)&exclude=minutely,alerts&units=imperial&appid=\(Constants.apiKey)"
let getCurrentWeatherStringUrl = URL(string: weatherStringUrl)
guard getCurrentWeatherStringUrl != nil else { return }
let currentRequest = URLRequest(url: getCurrentWeatherStringUrl!)
let currentSession = URLSession.shared
let currentDataTask = currentSession.dataTask(with: currentRequest) { data, response, error in
guard error == nil else {
return
}
do {
let currentDecoder = JSONDecoder()
let currentResults = try currentDecoder.decode(Weather.self, from: data!)
DispatchQueue.main.async {
self.weatherData = currentResults
}
} catch {
print(error)
}
}
currentDataTask.resume()
}
}
Onboarding View to get the user’s current location
import CoreLocationUI
import SwiftUI
struct OnboardingView: View {
@EnvironmentObject var locationManager: LocationManager
var body: some View {
ZStack {
Color.gray
.edgesIgnoringSafeArea(.all
)
VStack(spacing: 10) {
Image(systemName: "cloud.sun.rain")
Text("Grab-N-Fetch Weather")
.font(.largeTitle)
.fontWeight(.medium)
Text("Find out what the weather is like in your area or search a location. Please make sure to select and allow location access.")
.font(.title2)
.padding(.bottom, 20)
LocationButton(.shareCurrentLocation) {
locationManager.requestLocation()
}
.foregroundColor(.white)
.cornerRadius(10)
.symbolVariant(.fill)
.onAppear {
HomeView()
}
}//Vstack
.multilineTextAlignment(.center)
.padding()
}//Zstack
}
}
HomeView.
import CoreLocation
import SwiftUI
struct HomeView: View {
//MARK: - Properties
@State private var cityName = ""
@State private var isEditing = false
@StateObject var locationManager = LocationManager()
@ObservedObject var model = WeatherViewModel()
@State private var isCelsius = false
@FocusState private var isFocused: Bool
var hourlyFirstTwelve: [Current] {
guard let hourly = model.weatherData?.hourly else {
return []
}
return Array(hourly.prefix(13))
}
var selectTemperatureUnit: some View {
Menu {
Picker("", selection: $isCelsius) {
Text("Fahrenheit °F").tag(false)
Text("Celsius °C").tag(true)
}
} label: {
Image(systemName: "list.dash")
}
}
var searchField: some View {
withAnimation {
Button {
isEditing = false
cityName = ""
} label: {
Text("Cancel")
}
.padding(.trailing, 10)
.transition(.move(edge: .trailing))
}
}
//MARK: - Methods
func convertTemp(_ temp: Double) -> Double {
if isCelsius == true {
return ((temp - 32) * (5/9))
} else {
return temp
}
}
var body: some View {
if let location = locationManager.location {
NavigationView {
ZStack {
Color.primaryColor
.ignoresSafeArea(.all)
VStack {
HStack {
TextField("Enter city", text: $cityName)
.textFieldStyle(RoundedBorderTextFieldStyle())
.onTapGesture {
isEditing = true
}
.focused($isFocused)
.submitLabel(.search)
.onSubmit {
model.getWeatherData(cityName)
}
if isEditing {
searchField
}
}//HStack
.padding()
VStack {
VStack(spacing: 4) {
HStack(spacing: 0) {
Text(String(format: "%.f°", convertTemp(model.weatherData?.current.temp ?? 0.0))
)
.modifier(TempTextModifier(size: 60))
//
Text(isCelsius ? "C" : "F")
.modifier(TempTextModifier(size: 50))
}
Text(model.weatherData?.current.weather.first?.description ?? "Weather Description")
.modifier(TempTextModifier(size: 22))
Text(String(format: "Feels like: %.f°", convertTemp(model.weatherData?.current.feels_like ?? 0.0)))
.modifier(TempTextModifier(size: 26))
}//VStack
.padding([.trailing, .bottom], 20.0)
ScrollView(.horizontal) {
HStack(spacing: 20) {
ForEach(hourlyFirstTwelve) {
hour in
VStack(spacing: 10) {
Text("\(Constants.dtConversion(hour.dt))")
HStack {
Image(systemName: "thermometer")
Text(String(format: "%.f°", convertTemp(hour.temp)))
}
}//VStack
.modifier(TileModifier())
}
}
}
}//VStack
.padding()
GeometryReader {
geo in
ScrollView {
VStack(spacing: 20) {
ForEach(model.weatherData?.daily ?? []) { d in
HStack(){
Text("\(Constants.dtDaily(d.dt))")
Spacer()
Text(String(format: "Low: %.f°", convertTemp(d.temp.min)))
Spacer()
Text(String(format: "High: %.f°", convertTemp(d.temp.max)))
}
}
}
.padding()
}
}//geometry reader
.background(Color.accentColor)
.cornerRadius(12)
.edgesIgnoringSafeArea(.bottom)
}//VStack
.navigationTitle("Grab-N-Fetch Weather")
.navigationBarTitleDisplayMode(.automatic)
.toolbar {
ToolbarItemGroup(placement: .navigationBarTrailing) {
selectTemperatureUnit
}
}
}//toolbar
}//NavigationView
} else {
OnboardingView()
.environmentObject(locationManager)
}
}
}