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()
}
}
'''
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)
}
}
Related
So I have a widget for a weather app (the app currently doesn't use WeatherKit but I'd eventually like to move it over), but I want to implement a widget using WeatherKit beforehand. I made a practice app using WeatherKit and got all the components I'd want to use in the widget to work correctly.
Here's the entirety of the main app that works perfectly:
import SwiftUI
import CoreLocation
import WeatherKit
var userCity: String = ""
class LocationManager: NSObject, ObservableObject {
#Published var currentLocation: CLLocation?
#Published var userCity: String = ""
private let locationManager = CLLocationManager()
override init() {
super.init()
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.distanceFilter = kCLDistanceFilterNone
locationManager.requestAlwaysAuthorization()
locationManager.startUpdatingLocation()
locationManager.delegate = self
}
}
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
}
CLGeocoder().reverseGeocodeLocation(location, completionHandler: {(placemarks, error) -> Void in
print(location)
guard error == nil else {
print("Reverse geocoder failed with error" + error!.localizedDescription)
return
}
guard placemarks!.count > 0 else {
print("Problem with the data received from geocoder")
return
}
let pm = placemarks![0].locality
print(pm!)
let userLocation = (pm!)
print(userLocation)
DispatchQueue.main.async {
self.userCity = userLocation
}
})
}
}
struct ContentView: View {
let weatherService = WeatherService.shared
#StateObject private var locationManager = LocationManager()
#State private var weather: Weather?
var body: some View {
VStack {
if let weather{
let celsiusWeather = weather.currentWeather.temperature.converted(to: .celsius)
let celsiusFormatted = String(format: "%.0f", celsiusWeather.value)
let celsiusFinal = "\(celsiusFormatted)°C"
let fahrenheitWeather = weather.currentWeather.temperature.converted(to: .fahrenheit)
let fahrenheitFormatted = String(format: "%.0f", fahrenheitWeather.value)
let fahrenheitFinal = "\(fahrenheitFormatted)°F"
VStack{
Image(systemName: weather.currentWeather.symbolName)
.resizable()
.symbolRenderingMode(.palette)
.scaledToFit()
.frame(width: 80, height: 80)
Text(locationManager.userCity)
.font(.largeTitle)
Text(fahrenheitFinal)
Text(celsiusFinal)
}
}
}
.padding()
.task(id: locationManager.currentLocation) {
do{
if let location = locationManager.currentLocation{
self.weather = try await weatherService.weather(for: location)
print(weather!)
}
}catch{
print(error)
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
Basically, I've just been trying to move this code over to the widget, unsure if that would even work properly (clearly something doesn't).
And here is what I've been trying to do with the widget, but it comes up blank, so something is coming up nil and can't retrieve the weather data. But Xcode doesn't give me any errors.
import WidgetKit
import SwiftUI
import WeatherKit
import CoreLocation
var userCity: String?
class LocationManager: NSObject, ObservableObject {
#Published var currentLocation: CLLocation?
#Published var userCity: String = ""
private let locationManager = CLLocationManager()
override init() {
super.init()
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.distanceFilter = kCLDistanceFilterNone
locationManager.requestAlwaysAuthorization()
locationManager.startUpdatingLocation()
locationManager.delegate = self
}
}
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
}
CLGeocoder().reverseGeocodeLocation(location, completionHandler: {(placemarks, error) -> Void in
print(location)
guard error == nil else {
print("Reverse geocoder failed with error" + error!.localizedDescription)
return
}
guard placemarks!.count > 0 else {
print("Problem with the data received from geocoder")
return
}
let pm = placemarks![0].locality
print(pm!)
let userLocation = (pm!)
print(userLocation)
DispatchQueue.main.async {
self.userCity = userLocation
}
})
}
}
struct Provider: TimelineProvider {
func placeholder(in context: Context) -> SimpleEntry {
SimpleEntry(date: Date(), city: "", temp: "0") // placeholder humidity
}
func getSnapshot(in context: Context, completion: #escaping (SimpleEntry) -> ()) {
let entry = SimpleEntry(date: Date(), city: "", temp: "0") // placeholder humidity
completion(entry)
}
func getTimeline(in context: Context, completion: #escaping (Timeline<Entry>) -> ()) {
let locationManager = LocationManager()
#State var currentWeather: Weather?
let nextUpdate = Date().addingTimeInterval(1800)
Task {
do{
if let location = locationManager.currentLocation{
let currentWeather = try await WeatherService.shared.weather(for: location)
print(currentWeather)
}
}catch{
print(error)
}
}
let entry = SimpleEntry(date: .now, city: userCity!, temp: (currentWeather?.currentWeather.temperature.description)!)
let timeline = Timeline(entries: [entry], policy: .after(nextUpdate))
completion(timeline)
}
}
struct SimpleEntry: TimelineEntry {
let date: Date
let city: String
let temp: String
}
struct WidgetWeatherTestWidgetEntryView : View {
var entry: Provider.Entry
var body: some View {
VStack{
Text(entry.city)
Text(entry.temp)
}
}
}
struct ShitsdagWidget: Widget {
let kind: String = "WidgetWeatherTest"
var body: some WidgetConfiguration {
StaticConfiguration(kind: kind, provider: Provider()) { entry in
WidgetWeatherTestWidgetEntryView(entry: entry)
}
.configurationDisplayName("Widget Weather Test")
.description("Testing weatherkit in a Widget")
.supportedFamilies([.systemSmall])
}
}
Because widgets are set up slightly differently than just SwiftUI with the timeline and everything, I might just have things in the wrong spot to be triggered in the right order. Any help would be great.
I call an API and get a response with a longitude and latitude. Now, I'm trying to show and update the city name. I made a function ("place") to convert the longitude and latitude to a city name. Unfortunately is doesn't work. Please, who could give me a help on this one? Thanks in advance.
Below I will show the code. I filled the struct "APIcoord" with lat and lon example coordinates.
public final class WeatherService: NSObject {
private let locationManager = CLLocationManager()
private var completionHandler: ((Weather) -> Void)?
public override init() {
super.init()
locationManager.delegate = self
}
public func loadWeatherData(_ completionhandler: #escaping((Weather) -> Void)) {
self.completionHandler = completionhandler
locationManager.requestWhenInUseAuthorization()
locationManager.startUpdatingLocation()
}
private func makeDataRequest(forCoordinates coorodinates: CLLocationCoordinate2D) {
}
}
extension WeatherService: CLLocationManagerDelegate {
public func locationManager(
_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]
) {
guard let location = locations.first else {return}
makeDataRequest(forCoordinates: location.coordinate)
}
public func locationManager(
_ manager: CLLocationManager, didFailWithError error: Error
) {
print("Something went wrong: \(error.localizedDescription)")
}
}
struct APIResponse: Decodable {
let coord: APIcoord
}
struct APIcoord: Decodable{
let lon: Double = -122.031
let lat: Double = 37.33
}
public struct Weather {
let longtitude: Double
let latitude: Double
init(response: APIResponse) {
longtitude = response.coord.lon
latitude = response.coord.lat
}
}
public class WeaterViewModel: ObservableObject {
#Published var longtitude: Double = 0.0
#Published var latitude: Double = 0.0
public let weatherService: WeatherService
public init(weatherService: WeatherService) {
self.weatherService = weatherService
}
public func refresh() {
weatherService.loadWeatherData { weather in
DispatchQueue.main.sync {
self.longtitude = weather.longtitude
self.latitude = weather.latitude
}
}
}
}
struct ContentView: View {
#ObservedObject var viewModel: WeaterViewModel
#State var placeFound: String?
var body: some View {
NavigationView{
ZStack{
VStack {
Text(placeFound ?? "No Place Found")
.onAppear(){
place(APIlatitude: viewModel.latitude, APIlongitude: viewModel.longtitude){(city) in
placeFound = city
print("Place: \(String(describing: city))" )
}
}
}
.onAppear{
viewModel.refresh()
}
}
.navigationBarHidden(true)
}
}
func place(APIlatitude : Double, APIlongitude : Double, completion: #escaping (String?) -> Void) {
let geoCoder = CLGeocoder()
let location = CLLocation(latitude: APIlatitude, longitude: APIlongitude)
geoCoder.reverseGeocodeLocation(location, completionHandler: { (placemarks, _) -> Void in
placemarks?.forEach { (placemark) in
if let city = placemark.locality {
print(city)
completion(city)
}
else{
print("Can't find place")
}
}
})
}
}
You could pair GLGeocoder's async API with SwiftUI's task like this:
struct ContentView: View {
#StateObject var locator = Locator()
var body: some View {
if let location = locator.location {
ContentView2(location: location)
}
else {
Text("Hello, world!")
.padding()
}
}
}
struct ContentView2Config {
let geocoder = CLGeocoder()
var text = Self.noCityText
static let noCityText = "No City"
mutating func reverseGeocode(location: Location) async {
text = Self.noCityText
let location = CLLocation(latitude: location.lat, longitude: location.lon)
if let city = try? await geocoder.reverseGeocodeLocation(location)
.first
.flatMap({ placemark in
placemark.locality
})
{
text = city
}
else {
text = Self.noCityText
}
}
}
struct ContentView2: View {
let location: Location
#State var config = ContentView2Config()
var body: some View {
Text(config.text)
.task(id: location.id) {
await config.reverseGeocode(location: location)
}
}
}
And in case it helps, here is the location manager:
struct Location: Identifiable {
let id = UUID()
let lat: Double
let lon: Double
}
class Locator: NSObject, ObservableObject, CLLocationManagerDelegate {
#Published var location: Location?
let locationManager = CLLocationManager()
override init() {
super.init()
locationManager.delegate = self
locationManager.startUpdatingLocation()
locationManager.requestWhenInUseAuthorization()
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
guard let coordinate = locations.last?.coordinate else {
location = nil
return
}
location = Location(lat: coordinate.latitude, lon: coordinate.longitude)
}
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
location = nil
}
}
Hello I’m really new to iOS and SwiftUi so don't hate me if my formatting is wrong haha. I’m having trouble converting the users location coordinates to a US address. I have gotten most of the code done but for some reason on line 109 I create the #Published var location and set it to some random location and then I try to update it on line l72 but for some reason it just uses the first location I pass in on line 109. Id love for this to update and use the users current location after they grant permission. Thanks you for any suggestions you have :)
//
// ContentView.swift
// Parked
//
// Created by Luke Jamison on 11/2/21.
//
import SwiftUI
import MapKit
import CoreLocation
import CoreLocationUI
struct ContentView: View {
static let sharedV = ContentView()
#StateObject private var viewModel = ContentViewModel()
#State var addressData: String = ""
#State private var showingImagePicker = false
#State private var image: Image?
#State private var inputImage: UIImage?
//#State var geo: Any = locationManager.location
var body: some View {
NavigationView {
VStack {
Map(coordinateRegion: $viewModel.region, showsUserLocation: true)
.onAppear {
viewModel.checkIFLocationServicesisEnabled()
setData()
//viewModel.runSetDatafunc()
}.frame(maxWidth: .infinity, maxHeight: .infinity).cornerRadius(20).padding()
Form {
Section(header: Text("Add Location Details")) {
Text(addressData)
Menu {
Button(action: {
self.showingImagePicker = true
}) {
Label("Take Picture", systemImage: "camera.shutter.button")
}
image?.resizable()
.frame(width: 250, height: 250)
.clipShape(Circle())
.overlay(Circle().stroke(Color.white, lineWidth: 4))
.shadow(radius: 10)
} label: {
Label("Add Image", systemImage: "photo.on.rectangle.angled")
}
}
if image != nil {
Section(header: Text("Image Taken")) {
ZStack {
Rectangle().fill(Color.secondary)
if image != nil {
image?.resizable().scaledToFit()
} else {
}
}
Button(action: {
image = nil
}) {
Label("Remove Picture", systemImage: "trash.fill")
}
}
} else {
}
}.sheet(isPresented: $showingImagePicker, onDismiss: loadImage) {
ImagePicker(image: self.$inputImage)
}.navigationBarTitle("Parked", displayMode: .automatic)
}
}.navigationViewStyle(.stack)
}
func loadImage() {
guard let inputImage = inputImage else { return }
image = Image(uiImage: inputImage)
}
func setData() {
ContentViewModel.shared.resolveLoactionName(with: viewModel.location) { [self] locationName in
self.addressData = locationName
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
final class ContentViewModel: NSObject, ObservableObject, CLLocationManagerDelegate {
static let shared = ContentViewModel()
#Published var region = MKCoordinateRegion(center: CLLocationCoordinate2D(latitude: 33.4, longitude: -117.4), span:
MKCoordinateSpan(latitudeDelta: 0.5, longitudeDelta: 0.5))
#Published var address = ""
#Published var location = CLLocation.init(latitude: 33, longitude: -117)
var locationManager: CLLocationManager?
func checkIFLocationServicesisEnabled() {
if CLLocationManager.locationServicesEnabled() {
locationManager = CLLocationManager()
locationManager!.delegate = self
//locationManager?.desiredAccuracy = kCLLocationAccuracyBest
} else {
print("this is off turn in the settings...")
}
}
public func resolveLoactionName(with location: CLLocation, completion: #escaping ((String) -> Void)) {
let geocoder = CLGeocoder()
geocoder.reverseGeocodeLocation(location, preferredLocale: .current) { placemarks, error in
guard let place = placemarks?.first, error == nil else {
completion("")
return
}
print(place)
var name = ""
if let locality = place.subThoroughfare {
name += locality
}
if let street = place.thoroughfare {
name += " \(street)"
}
if let city = place.locality {
name += " \(city)"
}
if let adminRegion = place.administrativeArea {
name += ", \(adminRegion)"
}
if let zipCode = place.postalCode {
name += " \(zipCode)"
}
completion(name)
}
}
private func checkLocationAuth() {
guard let locationManager = locationManager else { return }
switch locationManager.authorizationStatus {
case .notDetermined:
locationManager.requestWhenInUseAuthorization()
case .restricted:
print("location is restricted")
case .denied:
print("location is denied in settings for app")
case .authorizedAlways, .authorizedWhenInUse:
region = MKCoordinateRegion(center: locationManager.location!.coordinate, span: MKCoordinateSpan(latitudeDelta: 0.5, longitudeDelta: 0.5))
ContentView.sharedV.setData()
self.location = CLLocation(latitude: region.self.center.latitude, longitude: region.self.center.longitude)
print(location)
//runSetDatafunc()
//address = "\(locationManager.location!.coordinate.latitude), \(locationManager.location!.coordinate.longitude)"
/*geocode(latitude: region.center.latitude, longitude: region.center.longitude) { placemark, error in
let placemark = placemark?.first
//print(placemark?.description)
//self.address = "\(placemark?.thoroughfare), \(placemark?.locality), \(placemark?.administrativeArea)"
}*/
#unknown default:
break
}
}
public func runSetDatafunc() {
}
func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {
checkLocationAuth()
}
}
}
Unfortunately, due to the transient nature of SwiftUI views, you definitely won't have luck making a singleton out of a View. But, you don't need it because you're already publishing your region and location, which your View will update in reaction to.
Here's a simplified/refactored version of your code:
struct ContentView: View {
#StateObject private var viewModel = ContentViewModel()
var body: some View {
VStack {
Text(viewModel.address)
Map(coordinateRegion: $viewModel.region, showsUserLocation: true)
.onAppear {
viewModel.checkIfLocationServicesisEnabled()
}
}
}
}
final class ContentViewModel: NSObject, ObservableObject, CLLocationManagerDelegate {
static let shared = ContentViewModel()
#Published var region = MKCoordinateRegion(center: CLLocationCoordinate2D(latitude: 33.4, longitude: -117.4), span:
MKCoordinateSpan(latitudeDelta: 0.5, longitudeDelta: 0.5))
#Published var address = ""
#Published var location = CLLocation.init(latitude: 33, longitude: -117)
private var lastResolved : CLLocation = .init()
var locationManager: CLLocationManager?
func checkIfLocationServicesisEnabled() {
if CLLocationManager.locationServicesEnabled() {
locationManager = CLLocationManager()
locationManager?.startUpdatingLocation()
locationManager?.delegate = self
//locationManager?.desiredAccuracy = kCLLocationAccuracyBest
} else {
print("this is off turn in the settings...")
}
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
guard let last = locations.last else { return }
if last.distance(from: lastResolved) > 200 {
resolveLocationName(with: last) { address in
self.address = address
self.lastResolved = last
}
}
self.location = last
}
public func resolveLocationName(with location: CLLocation, completion: #escaping ((String) -> Void)) {
let geocoder = CLGeocoder()
geocoder.reverseGeocodeLocation(location, preferredLocale: .current) { placemarks, error in
guard let place = placemarks?.first, error == nil else {
completion("")
return
}
print(place)
var name = ""
if let locality = place.subThoroughfare {
name += locality
}
if let street = place.thoroughfare {
name += " \(street)"
}
if let city = place.locality {
name += " \(city)"
}
if let adminRegion = place.administrativeArea {
name += ", \(adminRegion)"
}
if let zipCode = place.postalCode {
name += " \(zipCode)"
}
completion(name)
}
}
private func checkLocationAuth() {
guard let locationManager = locationManager else { return }
switch locationManager.authorizationStatus {
case .notDetermined:
locationManager.requestWhenInUseAuthorization()
case .restricted:
print("location is restricted")
case .denied:
print("location is denied in settings for app")
case .authorizedAlways, .authorizedWhenInUse:
region = MKCoordinateRegion(center: locationManager.location!.coordinate, span: MKCoordinateSpan(latitudeDelta: 0.5, longitudeDelta: 0.5))
self.location = CLLocation(latitude: region.self.center.latitude, longitude: region.self.center.longitude)
print(location)
#unknown default:
break
}
}
func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {
checkLocationAuth()
}
}
Note that I'm calling startUpdatingLocation() and implementing didUpdateLocations -- these will allow you to get/update the user's location.
In the simulator, you can use the Features->Location menu to simulate different spots. You'll see it resolve different spots and show the address at the top of the screen.
I am learning to develop apps for iOS from scratch. And I chose SwiftUI to make an app that gets the location of the user, get with geocode the city where he is and with that information obtained, show a list of items that belong to that city from a API.
So, I learned on one hand how to get the location and on the other hand how to display a list. My problem now is that when you run .onAppear(perform: loadData) to display my list, the "city" result is still empty. Evidently the value of city is obtained after I try to display the list of the city.
Both the algorithm I have to get the location and the one I have to show the list work separately.
So my code is:
import SwiftUI
struct Response: Codable {
var cinemas: [Cinema]
}
struct Cinema: Codable {
var _id: String
var cinemaName: String
var cinemaCategory: String
}
struct HomeScreenView: View {
#State var results = [Cinema]()
#ObservedObject var lm = LocationManager()
var latitude: String {
return("\(lm.location?.latitude ?? 0)") }
var longitude: String { return("\(lm.location?.longitude ?? 0)") }
var placemark: String { return("\(lm.placemark?.description ?? "XXX")") }
var status: String { return("\(lm.status)") }
var city: String {
return("\(lm.placemark?.locality ?? "empty")")
}
var body: some View {
VStack {
List(results, id: \._id) { item in
VStack(alignment: .leading) {
Text(item.cinemaName)
.font(.headline)
Text(item.cinemaCategory)
}
}.onAppear(perform: loadData)
}
}
func loadData() {
guard let url = URL(string: "https://mycinemasapi.com/cinemasbycity/\(self.city)") else {
print("Invalid URL")
return
}
let request = URLRequest(url: url)
URLSession.shared.dataTask(with: request) { data, response, error in
if let data = data {
if let decodedResponse = try? JSONDecoder().decode(Response.self, from: data) {
// we have good data – go back to the main thread
DispatchQueue.main.async {
// update our UI
self.results = decodedResponse.cinemas
}
// everything is good, so we can exit
return
}
}
// if we're still here it means there was a problem
print("Fetch failed: \(error?.localizedDescription ?? "Unknown error")")
}.resume()
}
}
UPDATE:
LocationManager class
import Foundation
import CoreLocation
import Combine
class LocationManager: NSObject, ObservableObject {
private let locationManager = CLLocationManager()
private let geocoder = CLGeocoder()
let objectWillChange = PassthroughSubject<Void, Never>()
#Published var status: CLAuthorizationStatus? {
willSet { objectWillChange.send() }
}
#Published var location: CLLocation? {
willSet { objectWillChange.send() }
}
override init() {
super.init()
self.locationManager.delegate = self
self.locationManager.desiredAccuracy = kCLLocationAccuracyBest
self.locationManager.requestWhenInUseAuthorization()
self.locationManager.startUpdatingLocation()
}
#Published var placemark: CLPlacemark? {
willSet { objectWillChange.send() }
}
private func geocode() {
guard let location = self.location else { return }
geocoder.reverseGeocodeLocation(location, completionHandler: { (places, error) in
if error == nil {
self.placemark = places?[0]
} else {
self.placemark = nil
}
})
}
}
extension LocationManager: CLLocationManagerDelegate {
func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
self.status = status
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
guard let location = locations.last else { return }
self.location = location
self.geocode()
}
}
As by your code just do load data on placemark received, like
List(results, id: \._id) { item in
VStack(alignment: .leading) {
Text(item.cinemaName)
.font(.headline)
Text(item.cinemaCategory)
}
}.onReceive(lm.$placemark) {
if $0 != nil {
self.loadData()
}
}
I'm a beginner, have been working on a weather project, trying to get the lat and long from LocationManager in WeatherViewModel class and pass it the API so i can get the user location based data. but can't get the lattitude and longitude in the WeatherViewModel. the simulator shows a random temperature and city as globe.(SwiftUI and Xcode 11.6)
here's the LocationManager class
import Foundation
import CoreLocation
import Combine
class LocationManager: NSObject, CLLocationManagerDelegate, ObservableObject {
private let manager: CLLocationManager
#Published var lastKnownLocation: CLLocation?
func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
if status == .denied{
print("denied")
}
else{
print("athorized")
manager.requestLocation()
}
}
func start() {
manager.requestWhenInUseAuthorization()
manager.startUpdatingLocation()
}
init(manager: CLLocationManager = CLLocationManager()) {
self.manager = manager
super.init()
}
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
print(error.localizedDescription)
}
func startUpdating() {
self.manager.delegate = self
self.manager.requestWhenInUseAuthorization()
self.manager.startUpdatingLocation()
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
lastKnownLocation = locations.last
print("\((lastKnownLocation?.coordinate.latitude)!)")
print("\((lastKnownLocation?.coordinate.longitude)!)")
}
}
Here's the WeatherViewModel class
import SwiftUI
import Combine
import CoreLocation
class WeatherViewModel: ObservableObject {
#ObservedObject var location = LocationManager()
let client = OpenWeatherAPI()
var stateView: StateView = StateView.loading{
willSet{
objectWillChange.send() }
}
var currentWeather = CurrentWeather.emptyInit(){
willSet{
objectWillChange.send()
}
}
var todayWeather = ForecastWeather.emptyInit(){
willSet{
objectWillChange.send()
}
}
var hourlyWeathers: [ForecastWeather] = [] {
willSet{
objectWillChange.send()
}
}
var dailyWeathers: [ForecastWeather] = [] {
willSet{
objectWillChange.send()
}
}
var currentDescription = ""{
willSet{
objectWillChange.send()
}
}
private var stateCurrentWeather = StateView.loading
private var stateForecastWeather = StateView.loading
var latitude: String{
return "\(location.lastKnownLocation?.coordinate.latitude ?? 0.0)"
}
var longitude: String{
return "\(location.lastKnownLocation?.coordinate.longitude ?? 0.0)"
}
func printLatlon(){
print("WeatherViewModel")
print(latitude )
print(longitude )
}
init (){
getData()
printLatlon()
}
func retry(){
stateView = .loading
stateCurrentWeather = .loading
stateForecastWeather = .loading
}
private func getData() {
client.getCurrentWeather(at: latitude, at: longitude) { [weak self] currentWeather, error in
guard let ws = self else { return }
if let currentWeather = currentWeather {
ws.currentWeather = currentWeather
ws.todayWeather = currentWeather.getForecastWeather()
ws.currentDescription = currentWeather.description()
ws.stateCurrentWeather = .success
} else {
ws.stateCurrentWeather = .failed
}
ws.updateStateView()
}
client.getForecastWeather(at: latitude, at: longitude) { [weak self] forecastWeatherResponse, error in
guard let ws = self else { return }
if let forecastWeatherResponse = forecastWeatherResponse {
ws.hourlyWeathers = forecastWeatherResponse.list
ws.dailyWeathers = forecastWeatherResponse.dailyList
ws.stateForecastWeather = .success
} else {
ws.stateForecastWeather = .failed
}
ws.updateStateView()
}
}
private func updateStateView() {
if stateCurrentWeather == .success, stateForecastWeather == .success {
stateView = .success
}
if stateCurrentWeather == .failed, stateForecastWeather == .failed {
stateView = .failed
}
}
}
I'm passing the data in this
private func getData() {
client.getCurrentWeather(at: latitude, at: longitude) { }
client.getForecastWeather(at: latitude, at: longitude) { }
}
if i use this
var latitude: String{
return "\(location.lastKnownLocation?.coordinate.latitude ?? 0.0)"
}
var longitude: String{
return "\(location.lastKnownLocation?.coordinate.longitude ?? 0.0)"
}
Then i get this output in the simulator
the console shows this
the console shows that it didnt get the lat and lon
but if i give a string of lat and lon like this
var latitude = "36.778259"
var longitude = "-119.417931"
Then i get expected output in the simulator
console output
because I pass the string location and thats why showing my string values