Reputation: 31
I am using WeatherKit and am trying to convert a city name to a CLLocation so I can pass it to the weatherService but I am not sure how to do this. I know there is a geocodeAddressString but I wasn't able to get it to work and I don't think it would return a CLLocation. Along with that I am also having trouble with getting location data from multiple locations. I am confused on what type of array I would make meaning would I make an array of Weather objects of an array of just city names and then convert them to coordinates when I need to get the weather data. My code that I have so far is below. Any help with these would be great.
WeatherKitTestApp
'''
import SwiftUI
import WeatherKit
@main
struct WeatherKitTestApp: App {
let weatherService = WeatherService.shared
@StateObject private var locationManager = LocationManager()
var body: some Scene {
WindowGroup {
ContentView(weatherService: weatherService, locationManager: locationManager)
}
}
}
'''
ContentView
'''
import SwiftUI
import WeatherKit
struct ContentView: View {
let weatherService: WeatherService
@ObservedObject var locationManager: LocationManager
@State private var weather: Weather?
@State var searchLocation: String = ""
var body: some View {
NavigationView {
if locationManager.weatherLocations.isEmpty {
Text("Add a location")
} else {
TabView {
ForEach(locationManager.weatherLocations, id: \.self) { location in
VStack {
HStack {
TextField("Weather Location", text: $searchLocation)
.padding(.horizontal)
.frame(width: 200)
.background {
RoundedRectangle(cornerRadius: 5)
.foregroundColor(Color(UIColor.lightGray))
}
Button(action: {
locationManager.weatherLocations.append(searchLocation)
searchLocation = ""
print(locationManager.weatherLocations)
}) {
Image(systemName: "plus")
}
}
.padding(.horizontal)
if let weather {
VStack {
Text("\(location)")
Text("\(weather.currentWeather.date.formatted())")
Text(weather.currentWeather.temperature.formatted())
}
}
}
.task(id: locationManager.currentLocation) {
do {
if let location = locationManager.currentLocation {
self.weather = try await weatherService.weather(for: location)
}
} catch {
print(error)
}
}
}
}
.tabViewStyle(.page)
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView(weatherService: WeatherService(), locationManager: LocationManager())
}
}
'''
LocationManager
'''
import Foundation
import CoreLocation
class LocationManager: NSObject, ObservableObject {
@Published var currentLocation: CLLocation? {
didSet {
if let currentLocation {
resolveLocationName(with: currentLocation) { locationName in
self.weatherLocations.append(locationName!)
}
}
}
}
@Published var city: String = ""
private let locationManager = CLLocationManager()
@Published var weatherLocations: [String] = []
override init() {
super.init()
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.distanceFilter = kCLDistanceFilterNone
locationManager.requestAlwaysAuthorization()
locationManager.startUpdatingLocation()
locationManager.delegate = self
}
public func resolveLocationName(with location: CLLocation, completion: @escaping ((String?) -> Void)) {
let geocoder = CLGeocoder()
geocoder.reverseGeocodeLocation(location) { (placemarks, error) in
guard let place = placemarks?.first, error == nil else {
completion(nil)
return
}
var name = ""
if let locality = place.locality {
name += locality
}
if let adminRegion = place.administrativeArea {
name += ", \(adminRegion)"
}
completion(name)
}
}
func getCoordinate(addressString: String, completionHandler: @escaping(CLLocationCoordinate2D, NSError?) -> Void) {
let geocoder = CLGeocoder()
geocoder.geocodeAddressString(addressString) { (placemarks, error) in
if error == nil {
if let placemark = placemarks?[0] {
let location = placemark.location!
completionHandler(location.coordinate, nil)
return
}
}
completionHandler(kCLLocationCoordinate2DInvalid, error as NSError?)
}
}
// Another attempt at trying to convert city name to CLLocation
/*
private func getLocation() {
CLGeocoder().geocodeAddressString(city, completionHandler: {(placemarks, error) in
if error != nil {
print("Error: ", error!)
}
if let placemark = placemarks?.first {
let location = placemark.location!.coordinate
return
}
})
}
*/
func appendLocation(location: String) {
weatherLocations.append(location)
}
}
extension LocationManager: CLLocationManagerDelegate {
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
guard let location = locations.last, currentLocation == nil else { return }
DispatchQueue.main.async {
self.currentLocation = location
}
manager.stopUpdatingLocation()
}
}
'''
Upvotes: 0
Views: 454
Reputation: 36119
to convert an address, eg a city name (Tokyo) to a CLLocation try this example code, works for me:
import SwiftUI
import CoreLocation
struct ContentView: View {
@State var location: CLLocation?
var body: some View {
Text("Tokyo \(location?.coordinate.latitude ?? 0) , \(location?.coordinate.longitude ?? 0)")
.onAppear {
getLocation("Tokyo") { loc in
location = loc
}
}
}
func getLocation(_ adres: String, completion: @escaping (CLLocation?) -> Void) {
CLGeocoder().geocodeAddressString(adres) { placemarks, error in
if error != nil {
print("error: \(error as Optional)")
} else {
if let placemark = placemarks?.first,
let coord = placemark.location?.coordinate {
return completion(CLLocation(latitude: coord.latitude, longitude: coord.longitude))
}
}
return completion(nil)
}
}
Upvotes: 1