why app crashes when I use localization and LocationButton in swiftui? I get this error:Thread 1: "Invalid parameter not satisfying: width && height" - swift

In my app I used locationButton and everything worked fine. Then I added Localizable string file and localized for English. Still, everything worked OK. Then I added localized String file for Hebrew, and it compiles, opens my simulator and shows everything in Hebrew But then I try to use the app and click something and it crashes and gives me this error:
Thread 1: "Invalid parameter not satisfying: width && height".
Can't find anything about this anywhere. Did anyone encountered this?
The String files:
English:
/*
Localizable.strings
*/
"location" = "Location:";
Hebrew:
"location" = "מיקום:";
This is my code:
import SwiftUI
import MapKit
import CoreLocationUI
struct ContentView: View {
#StateObject var vm = ViewModel()
var body: some View {
VStack {
Text("location")
LocationButton(.currentLocation) { vm.reqestUserLocationOnce() }
.foregroundColor(.white)
.symbolVariant(.fill)
.labelStyle(.iconOnly)
.clipShape(Capsule())
}
}
}
class ViewModel: NSObject, ObservableObject, CLLocationManagerDelegate {
private let locationManager = CLLocationManager()
override init() {
super.init()
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
}
func reqestUserLocationOnce() { locationManager.requestLocation() }
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
guard let currenLocation = locations.last else { return }
CLGeocoder().reverseGeocodeLocation(currenLocation) { placemarks, error in
guard error == nil else { return }
guard let placemark = placemarks?.first else { return }
guard placemark.location?.coordinate != nil else { return }
}
}
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) { print(error.localizedDescription) }
}

Related

Get users current location and upload it to firebase

I am trying to get the users current location in swifti and then upload it to firebase, but nothing seems to be uploaded
I tried adding print commands to the code to check whether I was getting the location but nothing was printed to the terminal, here is my code:
import MapKit
import FirebaseFirestore
struct Maps: View {
#State private var location = CLLocationCoordinate2D()
var body: some View {
Text("Hello World")
.onAppear {
let manager = LocationManager()
manager.getLocation { location in
self.location = location
print("Latitude: \(location.latitude), Longitude: \(location.longitude)")
}
}
}
}
class LocationManager: NSObject, CLLocationManagerDelegate {
let locationManager = CLLocationManager()
func getLocation(completion: #escaping (CLLocationCoordinate2D) -> ()) {
locationManager.delegate = self
locationManager.requestWhenInUseAuthorization()
locationManager.startUpdatingLocation()
self.completion = completion
}
private var completion: ((CLLocationCoordinate2D) -> ())?
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
guard let location = locations.last else { return }
let db = Firestore.firestore()
let locationRef = db.collection("locations").document()
locationRef.setData([
"latitude": location.coordinate.latitude,
"longitude": location.coordinate.longitude,
"identifier": UUID().uuidString
])
locationManager.stopUpdatingLocation()
completion?(location.coordinate)
}
}
struct Maps_Previews: PreviewProvider {
static var previews: some View {
Maps()
}
}
I added Privacy - Request when in use.. to info, so I don’t know what the problem is, I am not getting any errors
This code
.onAppear {
let manager = LocationManager()
// ...
}
creates and then immediately discards a LocationManager, which means that the object does not live long enough in memory to even receive one callback to the delegate method.
Move manager up to be a propery of Maps to let it stay in memory as long as the view itself.

Do something in another class when the locationManagerDidChangeAuthorization method called

In the following code, I have a LocationManager class which provides the city name of the current location via the #Published property wrapper lastSearchedCity.
Then I have a SearchManagerViewModel class that should be in charge of presenting the city name on SwiftUI views based on some conditions (not currently shown in the code below) via the #Published property wrapper cityName. It properly shows the city name when I call the searchAndSetCity() method from ContentView.swift inside an onAppear modifier.
My issue is that if the user turned Location Services off and turns it back On while he/she is in the ContentView.swift the Text view doesn't update, which is understandable since the searchAndSetCity() method would need to be called again.
How can I call the searchAndSetCity() method located inside the SearchManagerViewModel class every time the locationManagerDidChangeAuthorization(_ manager: CLLocationManager) method is called? I believed this method is called every time the authorization status changes.
LocationManager Class
final class LocationManager: NSObject, ObservableObject, CLLocationManagerDelegate {
private let locationManager = CLLocationManager()
#Published var lastSearchedCity = ""
var hasFoundOnePlacemark:Bool = false
func checkIfLocationServicesIsEnabled(){
DispatchQueue.global().async {
if CLLocationManager.locationServicesEnabled(){
self.locationManager.delegate = self
self.locationManager.desiredAccuracy = kCLLocationAccuracyBest/// kCLLocationAccuracyBest is the default
self.checkLocationAuthorization()
}else{
// show message: Services desabled!
}
}
}
private func checkLocationAuthorization(){
switch locationManager.authorizationStatus{
case .notDetermined:
locationManager.requestWhenInUseAuthorization()
case .restricted:
// show message
case .denied:
// show message
case .authorizedWhenInUse, .authorizedAlways:
/// app is authorized
locationManager.startUpdatingLocation()
default:
break
}
}
func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {
checkLocationAuthorization()
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
hasFoundOnePlacemark = false
CLGeocoder().reverseGeocodeLocation(manager.location!, completionHandler: {(placemarks, error)-> Void in
if error != nil {
self.locationManager.stopUpdatingLocation()
// show error message
}
if placemarks!.count > 0 {
if !self.hasFoundOnePlacemark{
self.hasFoundOnePlacemark = true
let placemark = placemarks![0]
self.lastSearchedCity = placemark.locality ?? ""
}
self.locationManager.stopUpdatingLocation()
}else{
// no places found
}
})
}
}
SearchManagerViewModel Class
class SearchManagerViewModel: ObservableObject{
#Published var cityName = "" // use this directly in SwifUI views
#ObservedObject private var locationManager = LocationManager()
// Call this directly fron onAppear in SwiftUI views
// This method is more complex than what is shown here. It handles other things like HTTP requests etc.
func searchAndSetCity(){
locationManager.checkIfLocationServicesIsEnabled()
self.cityName = locationManager.lastSearchedCity
}
}
ContentView.swift
struct ContentView: View {
#StateObject private var searchManager = SearchManagerViewModel()
var body: some View {
VStack {
Text(searchManager.cityName)
.font(.callout)
}
.onAppear{
searchManager.searchAndSetCity()
}
}
}

SwiftUI refresh the app when user change the location setting in device setting option

My requirement is when the location is disabled, In UI there is a feature "Enabled Location" when the user clicks Enabled location, it will go to setting screen. That is fine working. After changing the setting, and user comes back to app, it should refresh it. This is not working. Not refreshing the app again
My code :
struct MainView: View {
//MARK:- ObservedObject variable
#ObservedObject private var inStoreVM = InStoreViewModal()
#ObservedObject var locationManager = LocationManager()
//MARK: Location coordinate
var userLatitude: String {
return "\(locationManager.lastLocation?.coordinate.latitude ?? 0)"
}
var userLongitude: String {
return "\(locationManager.lastLocation?.coordinate.longitude ?? 0)"
}
var body: some View {
NavigationView {
ZStack {
TabView(selection: $selectedTab) {
HomeView()
.tabItem {
Image("home").renderingMode(.template)
Text("Home")
}.tag(0)
ExploreView(inStoreVM: inStoreVM)
.tabItem {
Image("search").renderingMode(.template)
Text("Explore")
}.tag(1)
.onAppear() {
print("----onAppear-----TAB")
}
.onReceive(NotificationCenter.default.publisher(for: UIApplication.willEnterForegroundNotification)) { _ in
print("----Moving back to the foreground!")
locationManager.enableMyAlwaysFeatures()
}
}}}
This is my location manager class
class LocationManager: NSObject, ObservableObject {
let objectWillChange = PassthroughSubject<Void, Never>()
private var locationManager = CLLocationManager()
func registerForLocationUpdates() {
self.locationManager.desiredAccuracy = kCLLocationAccuracyBest
self.locationManager.requestWhenInUseAuthorization()
if CLLocationManager.locationServicesEnabled() {
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyNearestTenMeters
locationManager.startUpdatingLocation()
}
}
override init() {
super.init()
self.locationManager.delegate = self
locationManager = CLLocationManager()
locationManager.delegate = self
locationManager.requestWhenInUseAuthorization()
self.locationManager.desiredAccuracy = kCLLocationAccuracyBest
self.locationManager.requestWhenInUseAuthorization()
if CLLocationManager.locationServicesEnabled(){
locationManager.startUpdatingLocation()
}
}
#Published var locationStatus: CLAuthorizationStatus? {
willSet {
objectWillChange.send()
}
}
#Published var lastLocation: CLLocation? {
willSet {
objectWillChange.send()
}
}
var statusString: String {
guard let status = locationStatus else {
return "unknown"
}
switch status {
case .notDetermined: return "notDetermined"
case .authorizedWhenInUse: return "authorizedWhenInUse"
case .authorizedAlways: return "authorizedAlways"
case .restricted: return "restricted"
case .denied: return "denied"
default: return "unknown"
}
}
}
extension LocationManager: CLLocationManagerDelegate {
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
print(locations)
guard let location = locations.first else { return }
lastLocation = location
self.lastLocation = location
print(#function, location)
print("location = \(location.coordinate.latitude) \(location.coordinate.longitude)")
}
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
print(error.localizedDescription)
}
func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
enableMyAlwaysFeatures()
self.locationStatus = status
print(#function, statusString)
}
func enableMyAlwaysFeatures() {
locationManager.startUpdatingLocation()
locationManager.delegate = self
}
}
Any idea, how can I refresh/reload the app, when the user comes from setting screen
I appreciate any advice or help.
As already mentioned, property observers didSet and willSet don't "work" on #Published, since you are simply observing changes to the Published<...> object defining the property wrapper, not the wrappedValue ... itself.
There are two possible approaches.
First
You can use Combine to subscribe to changes of your #Published properties.
Example:
private var cancellables = Set<AnyCancellable>()
init() {
$locationStatus
.sink { _ in objectWillChange.send() }
.store(in: &cancellables)
$lastLocation
.sink { _ in objectWillChange.send() }
.store(in: &cancellables)
}
This would be equivalent to willSet.
Don't forget to import Combine.
Second
You call objectWillChange.send() manually right after you change the properties.
For example in your locationManager(_ , didChangeAuthorization):
func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
enableMyAlwaysFeatures()
self.locationStatus = status
self.objectWillChange.send() // <- Here
print(#function, statusString)
}

CoreLocation enabled in simulator

didUpdateLocations is not firing
- CLLocationManagerDelegate was implemented correctly with the viewcontroller as a delegate
import Foundation
import UIKit
import CoreLocation
protocol LocationServiceDelegate {
func tracingLocation(currentLocation: CLLocation)
func tracingLocationDidFailWithError(error: NSError)
}
class LocationService: NSObject, CLLocationManagerDelegate {
static var sharedInstance = LocationService()
var locationManager: CLLocationManager?
var currentLocation: CLLocation?
var delegate: LocationServiceDelegate?
var paymentVC: PaymentViewController?
override init() {
super.init()
self.locationManager = CLLocationManager()
guard let locationManager = self.locationManager else {
return
}
if CLLocationManager.authorizationStatus() == .notDetermined {
locationManager.requestAlwaysAuthorization()
}
locationManager.distanceFilter = 200
}
func startUpdatingLocation() {
print("Starting Location Updates")
self.locationManager!.startUpdatingLocation()
}
func stopUpdatingLocation() {
print("Stop Location Updates")
self.locationManager?.stopUpdatingLocation()
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
guard let location = locations.last else {
return
}
self.currentLocation = location
updateLocation(currentLocation: location)
}
private func locationManager(manager: CLLocationManager, didFailWithError error: Error) {
updateLocationDidFailWithError(error: error as NSError)
}
private func updateLocation(currentLocation: CLLocation){
guard let delegate = self.delegate else {
return
}
delegate.tracingLocation(currentLocation: currentLocation)
}
private func updateLocationDidFailWithError(error: NSError) {
guard let delegate = self.delegate else {
return
}
delegate.tracingLocationDidFailWithError(error: error)
}
}
this is the extension of the viewcontroller where I implement to custom protocole for corelocation tracking
I call startUpdatingLocations() in viewDidLoad
extension PaymentViewController: LocationServiceDelegate,CLLocationManagerDelegate {
func tracingLocation(currentLocation: CLLocation) {
locationService.currentLocation = currentLocation
}
func tracingLocationDidFailWithError(error: NSError) {
print("Error message: \(error.localizedDescription)")
}
func startUpdatingLocations() {
locationService.locationManager?.delegate = self
locationService.delegate = self
locationService.startUpdatingLocation()
}
func stopUpdatingLocations() {
LocationService.sharedInstance.stopUpdatingLocation()
}
}
the corelocation tracking is not firing in the simulator. However this is enabled.
enter image description here
unfortunately, I have no way to test with a device right now
The only way I know of to simulate core location tracking on a simulator is by choosing one of the location options available in debug tab of the simulator.

Why is this Singleton Location Manager class returning a nil Location?

I am trying to use the below LocationSingleton Class from this blog in my project. I like the simplicity of its usage.
You start updating location by simply calling:
LocationSingleton.sharedInstance.startUpdatingLocation()
Get the last location by simply calling:
LocationSingleton.sharedInstance.lastLocation
My intention is to start location services, get the last location so that I can then fetch users from Firebase using the location returned.
The issue is that if I call lastLocation straight after startUpdatingLocation it returns nil.
After some debugging I've found the reason is because location services are slow to start on the device and therefore when lastLocation is called the devices hasn't acquired the location yet. I would like to execute the next command as soon as the lastLocation has been recorded. How can I achieve that?
I would like to understand how the Protocol is being used?
import UIKit
import CoreLocation
protocol LocationServiceDelegate {
func locationDidUpdateToLocation(currentLocation: CLLocation)
func locationUpdateDidFailWithError(error: NSError)
}
class LocationSingleton: NSObject,CLLocationManagerDelegate {
var locationManager: CLLocationManager?
var lastLocation: CLLocation?
var delegate: LocationServiceDelegate?
static let sharedInstance:LocationSingleton = {
let instance = LocationSingleton()
return instance
}()
override init() {
super.init()
self.locationManager = CLLocationManager()
guard let locationManagers=self.locationManager else {
return
}
if CLLocationManager.authorizationStatus() == .notDetermined {
//locationManagers.requestAlwaysAuthorization()
locationManagers.requestWhenInUseAuthorization()
}
if #available(iOS 9.0, *) {
// locationManagers.allowsBackgroundLocationUpdates = true
} else {
// Fallback on earlier versions
}
locationManagers.desiredAccuracy = kCLLocationAccuracyBest
locationManagers.pausesLocationUpdatesAutomatically = false
locationManagers.distanceFilter = 0.1
locationManagers.delegate = self
}
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
guard let location = locations.last else {
return
}
self.lastLocation = location
updateLocation(currentLocation: location)
}
#nonobjc func locationManager(manager: CLLocationManager!, didChangeAuthorizationStatus status: CLAuthorizationStatus) {
switch status {
case .notDetermined:
locationManager?.requestWhenInUseAuthorization()
break
case .authorizedWhenInUse:
locationManager?.startUpdatingLocation()
break
case .authorizedAlways:
locationManager?.startUpdatingLocation()
break
case .restricted:
// restricted by e.g. parental controls. User can't enable Location Services
break
case .denied:
// user denied your app access to Location Services, but can grant access from Settings.app
break
default:
break
}
}
// Private function
private func updateLocation(currentLocation: CLLocation){
guard let delegate = self.delegate else {
return
}
delegate.locationDidUpdateToLocation(currentLocation: currentLocation)
}
private func updateLocationDidFailWithError(error: NSError) {
guard let delegate = self.delegate else {
return
}
delegate.locationUpdateDidFailWithError(error: error)
}
func startUpdatingLocation() {
print("Starting Location Updates")
self.locationManager?.startUpdatingLocation()
// self.locationManager?.startMonitoringSignificantLocationChanges()
}
func stopUpdatingLocation() {
print("Stop Location Updates")
self.locationManager?.stopUpdatingLocation()
}
func startMonitoringSignificantLocationChanges() {
self.locationManager?.startMonitoringSignificantLocationChanges()
}
}
The location manager works asynchronously and provides delegate methods to get the result.
In your class adopt LocationServiceDelegate, implement the delegate methods and set the delegate for example in viewDidLoad
func locationDidUpdateToLocation(currentLocation: CLLocation)
{
print(LocationSingleton.sharedInstance.lastLocation)
}
func locationUpdateDidFailWithError(error: NSError)
{
print(error)
}
func viewDidLoad()
super viewDidLoad()
let locationSingleton = LocationSingleton.sharedInstance
locationSingleton.delegate = self
locationSingleton.startUpdatingLocation()
}
When a location is detected, one of the delegate methods is called