I am having issues with the iOS simulator and specifically the custom location setting for the iPhone. When I run the app the first time the simulator is opened it finds the location of the user without issues, however if I then change the custom location, and run the app again it gives the same location as the first time, despite having changed the custom location. If instead I set the Debug> Location > none in the simulator, and change the location in Product > Schemes > Edit Schemes in xCode itself, I have no issues. However every time I change the location this way I have to first set the location to none in the simulator. Is it a problem with my code, or just a quirk of the simulator that I wouldn't find with a real iPhone?
import UIKit
import CoreLocation
import MapKit
var userLocationCity : String!
var userLocationDate : String!
var safeUsername : String!
class TinderViewController: UIViewController, CLLocationManagerDelegate {
override func viewDidLoad() {
super.viewDidLoad()
PFGeoPoint.geoPointForCurrentLocationInBackground { (geopoint: PFGeoPoint!, error: NSError!) -> Void in
if error == nil {
println(geopoint)
var longitude :CLLocationDegrees = geopoint.longitude
var latitude :CLLocationDegrees = geopoint.latitude
var location = CLLocation(latitude: latitude, longitude: longitude) //changed!!!
println(location)
var formatter: NSDateFormatter = NSDateFormatter()
formatter.dateFormat = "dd-MM-yyyy"
let stringDate: String = formatter.stringFromDate(NSDate())
userLocationDate = stringDate
println(userLocationDate)
CLGeocoder().reverseGeocodeLocation(location, completionHandler: {(placemarks, error) -> Void in
if error != nil {
println("Reverse geocoder failed with error" + error.localizedDescription)
return
}
if placemarks.count > 0 {
println(userLocationCity)
let pm = placemarks[0] as CLPlacemark
println(pm.locality)
println(userLocationCity)
userLocationCity = pm.locality
println(userLocationCity)
user["location"] = userLocationCity
user.save()
let string1 = PFUser.currentUser().objectId
let string2 = "ID_"
safeUsername = string2 + string1
var locate = PFObject(className: safeUsername)
locate.setObject(userLocationCity, forKey: "location")
locate.setObject(userLocationDate, forKey: "date")
locate.saveInBackgroundWithBlock {
(success: Bool!, error: NSError!) -> Void in
if success == true {
println("Score created with ID: \(locate.objectId)")
} else {
println(error)
}
}
}
else {
println("Problem with the data received from geocoder")
}
})
// user["location"] = geopoint
// user.save()
}
}
}
Yes, it sounds like the issue is that you are using two different methods to simulate location. You should choose either to simulate location via schemes or via the debug menu in XCode, but not through both. It sounds like you're doing both, and the setting in the debug menu is overriding the setting in your scheme.
I would strongly advise you, however, to test any location based code on an actual device. Most of the problems that you will find with location services will not appear on the simulator; you really need to deal with the actual peculiarities of real-world GPS hardware.
Related
I'm working on a watchOS App as my first Swift/iOS project ever. I want to fetch the latest body weight sample and use it for some calculation. The result is presented to the user. As soon as a new sample is added, I want to update my UI as well. It works in a completely fresh simulator installation. As soon as I add a sample in the iOS simulator, the app updates its UI in the watchOS simulator. However, it doesn't work on my real device or after resetting the watchOS simulator. And I just don't know why. The HKAnchoredObjectQuery just returns 0 samples but I definitely have some samples stored in health. I can even see them under Settings > Health on my watch. I can't imagine this is related to my code, but here it is:
class WeightProvider: ObservableObject {
private static let weightSampleType = HKSampleType.quantityType(forIdentifier: .bodyMass)!
private static let healthStore: HKHealthStore = .init()
private var previousAnchor: HKQueryAnchor?
private var runningQuery: HKAnchoredObjectQuery?
#Published var bodyWeight: Measurement<UnitMass>?
func getBodyWeight(longRunning: Bool = false) {
let query = HKAnchoredObjectQuery(type: Self.weightSampleType, predicate: nil, anchor: previousAnchor, limit: longRunning ? HKObjectQueryNoLimit : 1, resultsHandler: processQueryResult)
if longRunning {
query.updateHandler = processQueryResult
runningQuery = query
}
Self.healthStore.execute(query)
}
func stopLongRunningQuery() {
if let runningQuery = runningQuery {
Self.healthStore.stop(runningQuery)
self.runningQuery = nil
}
}
private func processQueryResult(_: HKAnchoredObjectQuery, samples: [HKSample]?, _: [HKDeletedObject]?, newAnchor: HKQueryAnchor?, error: Error?) {
guard let samples = samples as? [HKQuantitySample], error == nil else {
fatalError(error?.localizedDescription ?? "Failed to cast [HKSample] to [HKQuantitySample]")
}
previousAnchor = newAnchor
guard let sample = samples.last else {
return
}
DispatchQueue.main.async {
if Locale.current.usesMetricSystem {
let weight = sample.quantity.doubleValue(for: .gramUnit(with: .kilo))
self.bodyWeight = .init(value: weight, unit: UnitMass.kilograms)
} else {
let weight = sample.quantity.doubleValue(for: .pound())
self.bodyWeight = .init(value: weight, unit: UnitMass.pounds)
}
}
}
}
// MARK: - HealthKit Authorization
extension WeightProvider {
private static let typesToRead: Set<HKObjectType> = [
weightSampleType,
]
func authorize(completion: #escaping (Bool, Error?) -> Swift.Void) {
Self.healthStore.requestAuthorization(toShare: nil, read: Self.typesToRead) { success, error in
completion(success, error)
}
}
}
In my Views onAppear I call this function:
private func authorizeHealthKit() {
guard firstRun else {
return
}
firstRun = false
weightProvider.authorize { success, error in
guard success, error == nil else {
return
}
weightProvider.getBodyWeight(longRunning: true)
}
}
HealthKit is properly authorized as I can see in the Settings of my Watch. Any ideas? Any tips for my code in general?
Wow, after all this time I found the issue: The line previousAnchor = newAnchor needs to be after the guard statement. That's it.
Swift version: 4.1
Hello I am a bit more than beginner in swift. Working in an "order application by user locations". Which I control user "country" and "city" name by reversegeocodelocation function before user give order. And write those values in firebase realtime database childs.
my data structure is like
-TR
-Ankara
-userID
-Order(consist of user lat, user long, user mail, userOrder)
It is okay I did that users can order and cancel his/her orders. But also I want to check if users close their phone and return the app, the app should check the database and if there is order given by current user uID it must change the button label, buttonToCancelState = true, and image of our mascot.
This is how I get user coord for order and "countrycode" and "city" for data structure name.
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
let location = locations [0]
if let coord = manager.location?.coordinate {
userLocation = coord
}
let geoCoder = CLGeocoder()
geoCoder.reverseGeocodeLocation(location) {(placemark, error) in
if error != nil {
print("there is an error")
} else {
var placeMark: CLPlacemark?
placeMark = placemark?[0]
// City
if let city = placeMark?.locality {
self.userCity = city as String
}
// Country
if let country = placeMark?.isoCountryCode {
self.userCountry = country as String
}
}
}
}
And I use these "country" and "city" in "order button" like example;
orderHasBeenCalled = true
buttonLabelText.text = "CANCEL/EDIT"
imgView.image = UIImage(named: "...")
let orderRequestDictionary: [String:Any] = ["..."]
databaseREF.child(userCountry).child(userCity).child(userID!).setValue(orderRequestDictionary)
it works flawlessly user can send order, delete it even when user logout it deleted too, (the whole codes did not included)
now the problem is I want to check if the users have an order when the viewDidLoad() loads for this I am using
if let userID = FirebaseAuth.Auth.auth().currentUser?.uid {
databaseRef.child(userCountry).child(userCity).queryOrdered(byChild: userID!).observe(.childAdded, with: { (snapshot) in
self.OrderHasBeenCalled = true
self.buttonLabelText.text = "CANCEL/EDIT"
self.imgView.image = UIImage(named: "...")
databaseRef.child(self.userCountry).child(self.userCity).removeAllObservers()
})
}
Now the problem is as I read in internet reversegeocode is asynchronous or something like that and as seems it is not ready when the viewDidLoad() load, "code for check if there is order" crash the app because it finds no value to search the names in childs.
Terminating app due to uncaught exception 'InvalidPathValidation', reason: '(child:) Must be a non-empty string and not contain '.' '#' '$' '[' or ']''
To make use of userCountry and userCity in orderbutton I define them before viewDidLoad()
var userCountry = String()
var userCity = String()
I have tried many ways but didn't really figure it out that how can I get reversegeocode completion in viewdidload(). I tried viewDidAppear() too btw but it gives userCountry() and userCity() nil too.
I hope my question is clear and easly understandable. Will be very appreciated if answers will be in that way. Did a lot of researh in the internet some I try, some I did not understand or did not know how can I even try. The last place that my hope shine is stack overflow. Thanks by now for all the people whose Kindly responds my question.
I would change a little the approach. Once working with async functions you must avoid sync request values.
There are several ways to make nested calls from async functions, from your code I arrived at this approach, adapt to your need and it should work.
/////////attention the scope (must be above class declaration)
typealias CompletionGetAddress = (_ userCity : String?, _ userCountry: String?, _ success: Bool) -> Void
var userLocation = CLLocationCoordinate2D()
var locationManager = CLLocationManager()
//
class viewController: ... {
func viewDidLoad() {
yourLocationManager.requestLocation()
// you should implement some code to ensure your userLocation from your locationManager is not nil and a valid location
if let userID = FirebaseAuth.Auth.auth().currentUser?.uid {
if self.userCity != "" {
databaseRef.child(userCountry).child(userCity).queryOrdered(byChild: userID!).observe(.childAdded, with: { (snapshot) in
self.OrderHasBeenCalled = true
self.buttonLabelText.text = "CANCEL/EDIT"
self.imgView.image = UIImage(named: "...")
databaseRef.child(self.userCountry).child(self.userCity).removeAllObservers()
})
} else {
getAddress { (city, country, success) in
if success {
self.userCity = city
self.userCountry = country
databaseRef.child(userCountry).child(userCity).queryOrdered(byChild: userID!).observe(.childAdded, with: { (snapshot) in
self.OrderHasBeenCalled = true
self.buttonLabelText.text = "CANCEL/EDIT"
self.imgView.image = UIImage(named: "...")
databaseRef.child(self.userCountry).child(self.userCity).removeAllObservers()
})
}
}
}
}
}
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
let location = locations [0]
if let coord = manager.location?.coordinate {
userLocation = coord /// attention here
}
}
func getAddress(completion: #escaping CompletionGetAddress) {
let geoCoder = CLGeocoder()
geoCoder.reverseGeocodeLocation(userLocation) {(placemark, error) in
if error != nil {
print("there is an error")
completion(nil, nil, false)
} else {
var city: String = ""
var country: String = ""
var placeMark: CLPlacemark?
placeMark = placemark?[0]
// City
if let c = placeMark?.locality {
city = c
}
// Country
if let c = placeMark?.isoCountryCode {
country = c
}
completion(city, country, true)
}
}
}
I have a location coordinate in the form of a CLLocationCoordinate2D. How do I get its equivalent GMSPlace object using the Google Maps SDK?
This seems like it should be a very simple task, but I couldn't find anything in Google's documentation or on Stack Overflow.
I am working on a similar issue, and I haven't found the exact solution but these alternatives may work depending on your situation. If you are okay with having a GMSAddress instead of a GMSPlace, you may use a GMSGeocoder with a call to reverseGeocodeCoordinate as seen in option two below.
Two options if you're trying to get the user's current location:
Use Google Maps current location to get a GMSPlace. This is pretty simple and solves your problem if you are okay with only resorting to actual places. The problem with this is that I couldn't figure out how to get all addresses (as opposed to businesses). You can see the documentation here.
In viewDidLoad:
let placesClient = GMSPlacesClient()
When you want to get the current place:
placesClient?.currentPlaceWithCallback({ (placeLikelihoods, error) -> Void in
if error != nil {
// Handle error in some way.
}
if let placeLikelihood = placeLikelihoods?.likelihoods.first {
let place = placeLikelihood.place
// Do what you want with the returned GMSPlace.
}
})
Use OneShotLocationManager to get the CLLocationCoordinate2D and turn it into a GMSAddress. You will have to replace the _didComplete function with the code below to return a GMSAddress instead of a CLLocationCoordinate2D.
private func _didComplete(location: CLLocation?, error: NSError?) {
locationManager?.stopUpdatingLocation()
if let location = location {
GMSGeocoder().reverseGeocodeCoordinate(location.coordinate, completionHandler: {
[unowned self] (response, error) -> Void in
if error != nil || response == nil || response!.firstResult() == nil {
self.didComplete?(location: nil,
error: NSError(domain: self.classForCoder.description(),
code: LocationManagerErrors.InvalidLocation.rawValue,
userInfo: nil))
} else {
self.didComplete?(location: response!.firstResult(), error: error)
}
})
} else {
self.didComplete?(location: nil, error: error)
}
locationManager?.delegate = nil
locationManager = nil
}
Someone posted on here a convenient wrapper to extract fields from the GMSAddressComponents that you may find useful when dealing with this API. This makes it easy because then when you want to access the city all you have to do is place.addressComponents?.city as an example.
extension CollectionType where Generator.Element == GMSAddressComponent {
var streetAddress: String? {
return "\(valueForKey("street_number")) \(valueForKey(kGMSPlaceTypeRoute))"
}
var city: String? {
return valueForKey(kGMSPlaceTypeLocality)
}
var state: String? {
return valueForKey(kGMSPlaceTypeAdministrativeAreaLevel1)
}
var zipCode: String? {
return valueForKey(kGMSPlaceTypePostalCode)
}
var country: String? {
return valueForKey(kGMSPlaceTypeCountry)
}
func valueForKey(key: String) -> String? {
return filter { $0.type == key }.first?.name
}
}
If there is anything else that could be linking to the issue please let me know so I can try and provide more details. I am bout a week in and I can't seem to find the issue. It will probably be something that will cause me to want to chuck the computer at the wall but I can't seem to figure it out :(
The issue seems to have isolated down to the coordinate variables, however the issue could still be associated with how the classes are constructed but I am not able to identify how they would be.
I am able to print Current Location Coordinates to the screen prior to the request should be made but I get the following error within the JSON when calling the method that will be sending the api request
"status" : "INVALID_REQUEST",
Google documentation claims that:
"INVALID_REQUEST generally indicates that a required query parameter (location or radius) is missing."
What I am experiencing is:
With hard coded coordinates I am able to conduct a search and return JSON successfully.
I am able to successfully acquire Current Location Coordinates.
However; I am not able to interpolate Current Location Coordinates into the Search Query.
This is a good chunk of code that I believe is all that will be needed to grasp the issue. I appreciate any suggestions that could lead to solving the issue:
// all classes have their own swift file
class ViewController: UIViewController, CLLocationManagerDelegate
{
let currentLocation = CurrentLocation()
let downloadData = DownLoadData()
override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutablePointer<Void>)
{
if !didFindMyLocation
{
let myLocation: CLLocation = change![NSKeyValueChangeNewKey] as! CLLocation
currentLocation.coordinate = myLocation.coordinate
currentLocation.latitude = myLocation.coordinate.latitude
currentLocation.longitude = myLocation.coordinate.longitude
didFindMyLocation = true
print(currentLocation.coordinates) // prints coordinates correctly
downLoadData.downloadAllLocations({ () -> () in //request I am attempting to make that outputs,
//"status" : "INVALID_REQUEST",
})
}
}
class CurrentLocation: NSObject, CLLocationManagerDelegate
{
override init()
{
super.init()
}
var locationManager = CLLocationManager()
//THE CULPRITS!
var latitude : CLLocationDegrees!
var longitude : CLLocationDegrees!
}
// typealias in a swift file of its own.
import foundation
typealias DownLoadComplete = () -> ()
class DownloadData: NSObject
{
let currentLocation = CurrentLocation()
override init()
{
super.init()
}
func downloadAllLocations(completed: DownloadComplete)
{
//hardcoded coordinates output correct JSON Response. So Why Cant I Interpolate the coordinates from my current location???
let URL_Search = "https://maps.googleapis.com/maps/api/place/nearbysearch/json?"
let urlString = "\(URL_Search)location=\(currentLocation.latitude),\(currentLocation.longitude)&radius=\(searchRadius)&types=\(searchType)&key=\(API_ServerKey)"
let url = NSURL(string: urlString)!
print(url) // outputs nil,nil in the currentLocation.latitude and currentLocation.longitude spots
Alamofire.request(.GET,url).responseJSON { (response) -> Void in
switch response.result
{
case .Success:
if let value = response.result.value
{
let json = JSON(value)
print(json) // If hardcoded coordinates are inserted then I the json is outputted. If current location is attempted then I receive an INVALID_REQUEST error.
if let results = json["results"].array
{
for result in results {
if let places = result["place"].string
{
self.places = place
}
}
}
}
case .Failure(let error):
print(error)
}
completed()
}
}
}
Testing the output by a print(currentLocation.coordinates) before i send the request (downLoadData.downLoadAllLocation[which has a print(url) being called]) the output to the debug console is:
CLLocationCoordinate2D(latitude: 37.33233141, longitude: -122.0312186)
https://maps.googleapis.com/maps/api/place/nearbysearch/json?location=nil,nil
...etc
The issue here is that the coordinates interpolated into the search query are appearing as nil, nil? Ive tried manipulating code around. But no luck... It seems so odd that they get printed but then in the search they appear as nil. How can that be??
The DownloadData class refers to a currentLocation variable (presumably an instance of CurrentLocation, but this variable isn't defined anywhere in your code sample. I assume it's a member variable of ViewController, in which case the question is: How does DownloadData know about it?
You may need to pass currentLocation to downloadData.downloadAllLocations() as a parameter.
I'm using CLGeocoder reverseGeocodeLocation. I get the crash after running for about 5-10 minutes (with no noticeable pattern) and get random crashes. Here's my code:
if CLLocationManager.authorizationStatus() == .AuthorizedWhenInUse {
let currentLatCoord = manager.location?.coordinate.latitude
let currentLongCoord = manager.location?.coordinate.longitude
CLGeocoder().reverseGeocodeLocation(CLLocation(latitude: currentLatCoord!, longitude: currentLongCoord!)) { (placemarks, error) -> Void in
if error != nil {
print(error)
return
}
let placeArray = placemarks as [CLPlacemark]!
var placeMark: CLPlacemark
placeMark = placeArray![0]
self.locationLabel.text = String(placeMark.addressDictionary?["Thoroughfare"]!)
}
}
And also, just to help, here's a picture of the line and error:
I think you need some optional binding:
if let thoroughfare = placeMark.addressDictionary?["Thoroughfare"] as? String {
self.locationLabel.text = thoroughfare
}
I'm guessing either there might not be a "Thoroughfare" key in the address dictionary, and you're providing a nil value to the designated initializer for String.
Is there a chance that the view being updated in your code snippet is not on the screen (disposed) when the CLGeocoder has finished its reverse geocoding? If you have your outlet defined as an implicitly unwrapped optional:
#IBOutlet var locationLabel : UILabel!
I'm wondering if it has already been set to nil, but due to the bang (!) the compiler isn't making you check.
But, of course, if your view is still on the screen when you crash, this probably isn't the issue.
You provided us a code sample:
let currentLatCoord = manager.location?.coordinate.latitude
let currentLongCoord = manager.location?.coordinate.longitude
CLGeocoder().reverseGeocodeLocation(CLLocation(latitude: currentLatCoord!, longitude: currentLongCoord!)) { (placemarks, error) -> Void in
if error != nil {
print(error)
return
}
let placeArray = placemarks as [CLPlacemark]!
var placeMark: CLPlacemark
placeMark = placeArray![0]
self.locationLabel.text = String(placeMark.addressDictionary?["Thoroughfare"]!)
}
You can more gracefully handle nil values if you use the if let construct:
CLGeocoder().reverseGeocodeLocation(manager.location!) { placemarks, error in
guard error == nil else {
print(error)
return
}
if let placemark = placemarks?.first {
self.locationLabel.text = placemark.thoroughfare
}
}
And, of course, if you're calling this repeatedly, I wouldn't re-instantiate a new CLGeocoder every time, but hopefully this illustrates the pattern.
But as you can see, you can avoid extracting the latitude and longitude from the location property to only then create a new CLLocation object by simply using manager.location directly. Likewise, you can use the thoroughfare property, which saves you from needing to cast the addressDictionary value.
The key observation, which Craig mentioned above, is to scrupulously avoid using the ! forced unwrapping operator unless you are positive that the variable can never be nil. Likewise, don't use [0] syntax unless you know for a fact that there is at least one item in the array (which is why I use first, which is an optional for which I can easily test).
Frankly, I'd even make sure that the location was valid (not nil and with a non-negative horizontalAccuracy, as a negative value indicates that the coordinates are not valid):
if let location = manager.location where location.horizontalAccuracy >= 0 {
CLGeocoder().reverseGeocodeLocation(location) { placemarks, error in
guard error == nil else {
print(error)
return
}
if let placemark = placemarks?.first {
self.locationLabel.text = placemark.thoroughfare
}
}
}