Cant Update CLLocation() from user Location SwiftUi - ios

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.

Related

Implementing WeatherKit to widget - SwiftUI

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.

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()
}
}
'''
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)
}
}

Get API Coordinates and Show city in SwiftUI

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
}
}

App crashes due to The operation couldn't be completed. (KCLErrorDomain error 1.)

I have a problem with an app.
I wrote it last week and it worked on both the simulator and my iPhone.
Now I wanted to make a few small changes today, but every time I try to simulate them, I get the error:
In the simulator:
"The operation couldn't be completed. (KCLErrorDomain error 1.)"
On my iPhone:
"QUARANTINED DUE TO HIGH LOGGING VOLUME"
although I haven't changed anything in the code and always ask the user for permission whether the app is allowed to use the location.
Whenever I start the app, my location is shown in the window in which I am asked for permission and if I then click on "Only use once", I no longer see my location and the app does not react to gestures etc.
Here is my code, it would be great if someone had an idea what the problem is, since I've googled a lot and tried, but somehow nothing seems to work.
p.s.: sorry for no commentary :X
MapViewModel:
import SwiftUI
import MapKit
import CoreLocation
var region1 = CLCircularRegion(center:CLLocationCoordinate2D(latitude: 52.503117, longitude: 13.348901), radius: 20, identifier: "region1")
var region2 = CLCircularRegion(center:CLLocationCoordinate2D(latitude: 52.504829, longitude: 13.336798), radius: 20, identifier: "region2")
var region3 = CLCircularRegion(center:CLLocationCoordinate2D(latitude: 52.504733, longitude: 13.341834), radius: 20, identifier: "region3")
var region4 = CLCircularRegion(center:CLLocationCoordinate2D(latitude: 52.503312, longitude: 13.347718), radius: 20, identifier: "region4")
var region5 = CLCircularRegion(center:CLLocationCoordinate2D(latitude: 52.505699, longitude: 13.352198), radius: 20, identifier: "region5")
var region6 = CLCircularRegion(center:CLLocationCoordinate2D(latitude: 52.513517, longitude: 13.350253), radius: 20, identifier: "region6")
class MapViewModel : NSObject,ObservableObject, CLLocationManagerDelegate, MKMapViewDelegate{
#Published var mapView = MKMapView()
#Published var region : MKCoordinateRegion!
#Published var permissionDenied = false
#Published var alerts = Array(repeating: false, count: 7)
#Published var mapType : MKMapType = .standard
#Published var searchTxt = ""
#Published var places : [Place] = []
#Published var currentPin: MKPointAnnotation = MKPointAnnotation()
#Published var currentmap = CGPoint()
#Published var UserDidZoom: Bool = false;
private let manager = CLLocationManager()
var Waypoints = [region1, region2, region3, region4, region5, region6];
var Pins: [MKPointAnnotation] = []
#Published var RegionIndex: Int = 6
private var mapChangedFromUserInteraction = false
func mapViewRegionDidChangeFromUserInteraction() -> Bool {
let view = mapView.subviews[0]
if let gestureRecognizers = view.gestureRecognizers {
for recognizer in gestureRecognizers {
if( recognizer.state == UIGestureRecognizer.State.began || recognizer.state == UIGestureRecognizer.State.ended ) {
return true
}
}
}
return false
}
func mapView(_ mapView: MKMapView, regionWillChangeAnimated animated: Bool) {
mapChangedFromUserInteraction = mapViewRegionDidChangeFromUserInteraction()
}
func mapView(_ mapView: MKMapView, regionDidChangeAnimated animated: Bool) {
if (mapChangedFromUserInteraction) {
UserDidZoom = true
mapView.userTrackingMode = .none
}
}
func updateMapType(){
if mapType == .standard{
mapType = .hybrid
mapView.mapType = mapType
}else{
mapType = .standard
mapView.mapType = mapType
}
}
func focusLocation() {
let rangeInMeters: Double = 500
let coordinateRegion = MKCoordinateRegion.init(center: region.center, latitudinalMeters: rangeInMeters,longitudinalMeters: rangeInMeters)
mapView.setRegion(coordinateRegion, animated: true)
}
func searchQuery(){
places.removeAll()
let request = MKLocalSearch.Request()
request.naturalLanguageQuery = searchTxt
MKLocalSearch(request: request).start { (response, _) in
guard let result = response else{return}
self.places = result.mapItems.compactMap({ (item) -> Place? in
return Place(placemark: item.placemark)
})
}
}
func selectPlace(place: Place){
searchTxt = ""
guard let coordinate = place.placemark.location?.coordinate else{return}
let pointAnnotation = MKPointAnnotation()
pointAnnotation.coordinate = coordinate
pointAnnotation.title = place.placemark.name ?? "No Name"
mapView.removeAnnotations(mapView.annotations)
mapView.addAnnotation(pointAnnotation)
let coordinateRegion = MKCoordinateRegion(center: coordinate, latitudinalMeters: 200, longitudinalMeters: 200)
mapView.setRegion(coordinateRegion, animated: true)
mapView.setVisibleMapRect(mapView.visibleMapRect, animated: true)
}
func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {
switch manager.authorizationStatus{
case .denied:
permissionDenied.toggle()
case .notDetermined:
manager.requestWhenInUseAuthorization()
case .authorizedWhenInUse:
manager.requestLocation()
default:
()
}
}
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
print(error.localizedDescription)
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
guard let location = locations.last else{return}
self.region = MKCoordinateRegion(center: location.coordinate, latitudinalMeters: 200, longitudinalMeters: 200)
if(!UserDidZoom){
mapView.setRegion(region, animated: true)
}
self.isWayPoint()
}
func initPositionService(){
let startPointRegion = MKCoordinateRegion(center: CLLocationCoordinate2D(latitude: 52.506016, longitude: 13.33206), latitudinalMeters: 200, longitudinalMeters: 200)
mapView.setRegion(startPointRegion, animated: true)
manager.desiredAccuracy = kCLLocationAccuracyBest
manager.requestAlwaysAuthorization()
manager.requestWhenInUseAuthorization()
manager.delegate = self
manager.startUpdatingLocation()
mapView.delegate = self
}
func placeAnnotations(){
let toDoLocation = ["Friseur: Haare schneiden " , "Dönerbude: zu Mittag essen" , "Bank: Geld abheben" , "Supermarkt: einkaufen" , "Blumeneladen: Blumen kaufen" , "Lisa's Wohnung: Lisa Blumen überreichen"]
for i in 0...5 {
let point = MKPointAnnotation()
point.coordinate = Waypoints[i].center
point.title = toDoLocation[i]
Pins.append(point)
}
mapView.addAnnotations(Pins)
}
func locationManager(_ manager: CLLocationManager, didEnterRegion region: CLRegion) {
if(region.identifier == "region1"){
self.RegionIndex = 0
}else if(region.identifier == "region2"){
self.RegionIndex = 1
}else if(region.identifier == "region3"){
self.RegionIndex = 2
}else if(region.identifier == "region4"){
self.RegionIndex = 3
}else if(region.identifier == "region5"){
self.RegionIndex = 4
}else if(region.identifier == "region6"){
self.RegionIndex = 5
}
}
func isWayPoint() {
while(self.Waypoints.count > 0){
for i in self.Waypoints {
self.manager.startMonitoring(for: i)
}
}
}
}
MapView
import SwiftUI
import MapKit
import Combine
struct MapView: UIViewRepresentable {
#EnvironmentObject var mapData: MapViewModel
func makeCoordinator() -> Coordinator {
return MapView.Coordinator()
}
func makeUIView(context: Context) -> MKMapView {
let view = mapData.mapView
view.showsUserLocation = true
view.delegate = context.coordinator
return view
}
func updateUIView(_ uiView: MKMapView, context: Context) {
}
class Coordinator: NSObject, MKMapViewDelegate{
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
if annotation.isKind(of: MKUserLocation.self){return nil}
else{
let pinAnnotation = MKPinAnnotationView(annotation: annotation, reuseIdentifier: "PIN_VIEW")
pinAnnotation.tintColor = .blue
pinAnnotation.animatesDrop = true
pinAnnotation.canShowCallout = true
return pinAnnotation
}
}
}
}
Place
import SwiftUI
import MapKit
struct Place: Identifiable{
var id = UUID().uuidString
var placemark: CLPlacemark
}
ContentView
import MapKit
import SwiftUI
import CoreLocation
import AVFoundation
struct ContentView: View {
#StateObject var mapData = MapViewModel()
#State var locationManager = CLLocationManager()
let systemSoundID: SystemSoundID = 1015
var body: some View {
ZStack{
MapView()
.environmentObject(mapData)
.ignoresSafeArea(.all, edges: .all)
VStack{
VStack(spacing : 0){
HStack{
Image(systemName: "magnifyingglass")
.foregroundColor(.gray)
TextField("Search", text: $mapData.searchTxt)
.colorScheme(.light)
}
.padding(.vertical, 10)
.padding(.horizontal)
.background(Color.white)
if !mapData.places.isEmpty && mapData.searchTxt != ""{
ScrollView{
VStack(spacing:10){
ForEach(mapData.places){place in
Text(place.placemark.name ?? "")
.foregroundColor(.black)
.frame(maxWidth: .infinity, alignment: .leading)
.padding(.leading)
.onTapGesture{
mapData.selectPlace(place: place)
}
Divider()
}
}
.padding(.top)
}
.background(Color.white)
}
}
.padding()
Spacer()
VStack{
Button(action:{ mapData.focusLocation(); mapData.UserDidZoom = false; mapData.mapView.userTrackingMode = .follow },label:{
Image(systemName: "location.fill")
.font(.title2)
.padding(10)
.background(Color.primary)
.clipShape(Circle())
})
.opacity(mapData.UserDidZoom ? 1 : 0)
Button(action: mapData.updateMapType ,label:{
Image(systemName: mapData.mapType == .standard ? "network" : "map")
.font(.title2)
.padding(10)
.background(Color.primary)
.clipShape(Circle())
})
}
.frame(maxWidth: .infinity, alignment: .trailing)
.padding(.horizontal, 10)
}
}
.onAppear(perform: {
mapData.initPositionService()
mapData.placeAnnotations()
})
.onChange(of: mapData.RegionIndex) { regionIndex in
var done = Array(repeating: false, count: 7)
if( regionIndex <= 5 ){
if(done[regionIndex] == false){
mapData.currentPin = mapData.Pins[regionIndex]
mapData.alerts[regionIndex] = true
AudioServicesPlayAlertSound(systemSoundID)
mapData.mapView.removeAnnotation(mapData.currentPin)
done[regionIndex] = true
}
}
}
.alert(isPresented: $mapData.alerts[mapData.RegionIndex], content: {
Alert(title: Text("Ziel erreicht"), message: Text("Sie haben \(mapData.currentPin.title!) erledigt"), dismissButton: .default(Text("Ok"), action: {mapData.alerts[mapData.RegionIndex] = false} ))
})
.onChange(of: mapData.searchTxt, perform: { value in
let delay = 0.3
DispatchQueue.main.asyncAfter(deadline: .now() + delay){
if value == mapData.searchTxt{
self.mapData.searchQuery()
}
}
})
}
}

SwiftUI with Core Location as ObservableObject crashes

I am trying to use Core Location to get the CLRegionState to update elements in a SwiftUI app. I am using XCode 11 beta 6 and have iOS 13 beta 7 on my device.
There are two problems that I can see:
The app crashes and the error Thread 1: EXC_BAD_ACCESS appears on line 147 (...ScrollView {... )
The CLRegionState is never called or does not update.
I am basing this off of Paul Hudson's tutorial on SwiftUI Beacon Detector (which I have not been able to make work either), and modifying it to use CLRegionState instead of beacon proximity .
Here is the code:
import SwiftUI
import CoreLocation
import Combine
class MYLocationManager: NSObject, ObservableObject, CLLocationManagerDelegate {
var locationManager: CLLocationManager?
var willChange = PassthroughSubject<Void, Never>()
var lastRegionState = CLRegionState.unknown
override init() {
super.init()
locationManager = CLLocationManager()
locationManager?.delegate = self
locationManager?.requestWhenInUseAuthorization()
}
func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
checkLocationAuthorization()
}
func update(state: CLRegionState) {
lastRegionState = state
willChange.send(())
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
guard let location = locations.last else { return }
print("Your location is \(location)")
update(state: .unknown)
}
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
print(error)
}
func startScanning() {
// temporary coordinates
var workCoordinates: CLLocationCoordinate2D {
return CLLocationCoordinate2D(
latitude: 43.486525,
longitude: -11.912542)
}
var homeCoordinates = CLLocationCoordinate2D(
latitude: 43.499541,
longitude: -11.875079)
let workRegion: CLCircularRegion = CLCircularRegion(center: workCoordinates, radius: 100, identifier: "Work")
let homeRegion: CLCircularRegion = CLCircularRegion(center: homeCoordinates, radius: 100, identifier: "Home")
locationManager!.startMonitoring(for: workRegion)
locationManager!.startMonitoring(for: homeRegion)
locationManager!.requestState(for: workRegion)
locationManager!.requestState(for: homeRegion)
}
func locationManager(_ manager: CLLocationManager, didDetermineState state: CLRegionState, for region: CLRegion) {
switch state {
case .inside:
switch region.identifier {
case "Work":
print("You are at work")
case "Home":
print("You are at home")
default:
print("unknown")
}
default:
break
}
}
func locationManager(_ manager: CLLocationManager, didEnterRegion region: CLRegion) {
switch region.identifier {
case "Work":
print("Work**********")
//self.taskTypeSegCtrl.selectedSegmentIndex = 0
case "Home":
print("Home*********8")
//self.taskTypeSegCtrl.selectedSegmentIndex = 1
default:
break
}
}
func checkLocationAuthorization() {
switch CLLocationManager.authorizationStatus() {
case .authorizedWhenInUse:
startScanning()
break
case .authorizedAlways:
startScanning()
break
case .denied:
// show an alert instructing them howto turn on permissions
break
case .notDetermined:
print("Location authorization is not determined.")
locationManager!.requestAlwaysAuthorization()
break
case .restricted:
break
#unknown default:
fatalError()
}
}
}
struct ContentView: View {
#Environment(\.managedObjectContext) var managedObjectContext
#FetchRequest(entity: Task.entity(),
sortDescriptors: [NSSortDescriptor(
keyPath: \Task.name, ascending: true)])
var tasks: FetchedResults<Task>
var locationManager = CLLocationManager()
#ObservedObject var location: MYLocationManager = MYLocationManager()
#State private var taskName = ""
#State private var taskType = 0
#State private var selectedTask = ""
#State private var numberOfTaps = 0
#State private var regionState = CLRegionState.unknown
var body: some View {
ScrollView {
VStack {
TextField("Enter a task name", text: $taskName)
.textFieldStyle(RoundedBorderTextFieldStyle())
Picker(selection: $taskType, label: Text("Task type")) {
Text("Work").tag(1)
Text("Home").tag(2)
}.pickerStyle(SegmentedPickerStyle())
Text(selectedTask)
Button(action: {
let task = Task(context: self.managedObjectContext)
task.name = self.taskName
task.type = Int16(self.taskType)
do {
try self.managedObjectContext.save()
} catch {
// handle the Core Data error
}
self.taskName = ""
}) {
Text("Save Task")
}.padding()
Button(action: {
if self.numberOfTaps < self.tasks.count {
let task = self.tasks[self.numberOfTaps].name
self.selectedTask = task ?? "No task..."
self.numberOfTaps = self.numberOfTaps + 1
} else {
self.selectedTask = "No more tasks! Have a wonderful day."
}
}) {
Text("Next Task")
}
List {
ForEach(tasks, id: \.self) { task in
VStack(alignment: .leading, spacing: 6) {
Text(task.name ?? "Unknown")
.font(.headline)
Text("Task type \(task.type)")
.font(.caption)
}
}.onDelete(perform: removeTask)
}
} .frame(width: 300, height: 400, alignment: .top)
.padding()
.border(Color.black)
if regionState == .inside {
Text("inside")
} else if regionState == .outside {
Text("outside")
} else {
Text("unknown")
}
Spacer()
}
}
func removeTask(at offsets: IndexSet) {
for index in offsets {
let task = tasks[index]
managedObjectContext.delete(task)
do {
try managedObjectContext.save()
} catch {
// handle the Core Data error
}
}
}
func showTask(at offsets: IndexSet) {
for index in offsets {
let task = tasks[index]
selectedTask = task.name ?? "No task..."
}
}
}
#if DEBUG
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
#endif
After implementing the changes made by Fabian, here is the content of the console log:
Granted: true
2019-08-22 14:30:07.051062-0600 AppName[4452:2089841] locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus)
2019-08-22 14:30:07.052803-0600 New1Thing[4452:2089841] startScanning
2019-08-22 14:30:07.054319-0600 New1Thing[4452:2089841] Current location: <+**.49945068,-***.87504490> +/- 65.00m (speed -1.00 mps / course -1.00) # 8/22/19, 2:30:07 PM **** Daylight Time
Here is a complete working example. There were several problems I fixed.
ObservableObject does now work with objectWillChange instead of willChange.
It should now update on every status change.
The updating part was not complete before (my opinion)
import SwiftUI
import CoreLocation
import Combine
import CoreData
import os
class MYLocationManager: NSObject, ObservableObject {
var locationManager: CLLocationManager?
var objectWillChange = PassthroughSubject<Void, Never>()
#Published var lastRegionState = CLRegionState.unknown {
willSet {
objectWillChange.send()
}
}
#Published var currentRegion: Region = .nowhereKnown {
willSet {
objectWillChange.send()
}
}
override init() {
super.init()
locationManager = CLLocationManager()
locationManager!.delegate = self
locationManager!.requestWhenInUseAuthorization()
}
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
fatalError("error: \(error.localizedDescription)")
}
enum Region: String {
case work = "Work"
case home = "Home"
case nowhereKnown = "Nowhere Known"
}
func startScanning() {
os_log("startScanning")
// temporary coordinates
var workCoordinates: CLLocationCoordinate2D {
return CLLocationCoordinate2D(
latitude: 43.486525,
longitude: -11.912542)
}
var homeCoordinates = CLLocationCoordinate2D(
latitude: 43.499541,
longitude: -11.875079)
if let currentLocation = locationManager?.location {
os_log("Current location: %#", currentLocation.description)
homeCoordinates = currentLocation.coordinate
} else {
os_log("Current location: failed")
}
let workRegion: CLCircularRegion = CLCircularRegion(center: workCoordinates, radius: 100, identifier: Region.work.rawValue)
let homeRegion: CLCircularRegion = CLCircularRegion(center: homeCoordinates, radius: 100, identifier: Region.home.rawValue)
locationManager!.startMonitoring(for: workRegion)
locationManager!.startMonitoring(for: homeRegion)
locationManager!.requestState(for: workRegion)
locationManager!.requestState(for: homeRegion)
}
}
// MARK: Authorization
extension MYLocationManager {
func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
os_log("locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus)")
checkLocationAuthorization()
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
os_log("locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation])")
guard let location = locations.last else { return }
print("Your location is \(location)")
update(state: .unknown)
}
func checkLocationAuthorization() {
switch CLLocationManager.authorizationStatus() {
case .authorizedWhenInUse:
startScanning()
break
case .authorizedAlways:
startScanning()
break
case .denied:
// show an alert instructing them howto turn on permissions
break
case .notDetermined:
print("Location authorization is not determined.")
locationManager!.requestAlwaysAuthorization()
break
case .restricted:
break
#unknown default:
fatalError()
}
}
}
// MARK: UI Updates
extension MYLocationManager: CLLocationManagerDelegate {
func updateCurrentRegion(region: CLRegion) {
guard let region = Region(rawValue: region.identifier) else {
currentRegion = .nowhereKnown
return
}
currentRegion = region
}
func update(state: CLRegionState) {
lastRegionState = state
}
func locationManager(_ manager: CLLocationManager, didDetermineState state: CLRegionState, for region: CLRegion) {
self.lastRegionState = state
updateCurrentRegion(region: region)
}
func locationManager(_ manager: CLLocationManager, didEnterRegion region: CLRegion) {
updateCurrentRegion(region: region)
}
func locationManager(_ manager: CLLocationManager, didExitRegion region: CLRegion) {
updateCurrentRegion(region: region)
}
}
struct CoreLocationView: View {
private static func makeContainer() -> NSPersistentContainer {
let store = NSPersistentContainer(name: "CoreLocationView")
store.loadPersistentStores { (desc, err) in
if let err = err {
fatalError("core data error: \(err)")
}
}
return store
}
let container: NSPersistentContainer
init() {
self.container = CoreLocationView.makeContainer()
}
var body: some View {
CoreLocationView_NeedsEnv().environment(\.managedObjectContext, container.viewContext)
}
}
struct CoreLocationView_NeedsEnv: View {
#Environment(\.managedObjectContext) var managedObjectContext
#FetchRequest(entity: Task.entity(),
sortDescriptors: [NSSortDescriptor(
keyPath: \Task.name, ascending: true)])
var tasks: FetchedResults<Task>
var locationManager = CLLocationManager()
#ObservedObject var location: MYLocationManager = MYLocationManager()
#State private var taskName = ""
#State private var taskType = 0
#State private var selectedTask = ""
#State private var numberOfTaps = 0
//#State private var regionState = CLRegionState.unknown
var body: some View {
ScrollView {
VStack {
TextField("Enter a task name", text: $taskName)
.textFieldStyle(RoundedBorderTextFieldStyle())
Picker(selection: $taskType, label: Text("Task type")) {
Text("Work").tag(1)
Text("Home").tag(2)
}.pickerStyle(SegmentedPickerStyle())
Text(selectedTask)
Button(action: {
let task = Task(context: self.managedObjectContext)
task.name = self.taskName
task.type = Int16(self.taskType)
do {
try self.managedObjectContext.save()
} catch {
// handle the Core Data error
}
self.taskName = ""
}) {
Text("Save Task")
}.padding()
Button(action: {
if self.numberOfTaps < self.tasks.count {
let task = self.tasks[self.numberOfTaps].name
self.selectedTask = task ?? "No task..."
self.numberOfTaps = self.numberOfTaps + 1
} else {
self.selectedTask = "No more tasks! Have a wonderful day."
}
}) {
Text("Next Task")
}
List {
ForEach(tasks, id: \.self) {
task in
VStack(alignment: .leading, spacing: 6) {
Text(task.name ?? "Unknown")
.font(.headline)
Text("Task type \(task.type)")
.font(.caption)
}
}.onDelete(perform: removeTask)
}
} .frame(width: 300, height: 400, alignment: .top)
.padding()
.border(Color.black)
if location.lastRegionState == .inside {
Text("inside")
} else if location.lastRegionState == .outside {
Text("outside")
} else {
Text("unknown")
}
Text("Where am I: \(location.currentRegion.rawValue)")
Spacer()
}
}
func removeTask(at offsets: IndexSet) {
for index in offsets {
let task = tasks[index]
managedObjectContext.delete(task)
do {
try managedObjectContext.save()
} catch {
// handle the Core Data error
}
}
}
func showTask(at offsets: IndexSet) {
for index in offsets {
let task = tasks[index]
selectedTask = task.name ?? "No task..."
}
}
}
First off I want to thank Fabian and graycampbell for their help.
Secondly, as for as I can tell #ObservableObject still does not work in iOS 13 beta 8 using XCode 11 beta 6.
Here is what worked for me:
1. I changed
#ObservedObject var location: MYLocationManager = MYLocationManager()
to:
#EnvironmentObject var location: MYLocationManager
2. In the SceneDelegate I added:
let myLocationManager = MYLocationManager()
and:
window.rootViewController = UIHostingController(rootView: CoreLocationView_NeedsEnv()
.environmentObject(myLocationManager)
No more crash!!
P.S. I am using Fabian's updated code. Thanks again!

Resources