Lance Addison
Lance Addison

Reputation: 31

How would I convert a city name to CLLocation

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

Answers (1)

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

Related Questions