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()
}
}
}
CodePudding user response:
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.