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
}
}
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.
I have the following class to return a list of NOAA weather observation stations. I am using it to learn how to deal with XML. However, I am getting a "Use of undeclared type 'wxObservationStations'" as an error at func returnWxStation() -> (wxObservationStations). I am using SWXMLHash to deserialize the XML, but I don't think that is my problem (though I am just learning, so it may be).
class WxObservationStations {
let wxObserStationsURL = URL(string: "http://w1.weather.gov/xml/current_obs/index.xml")
struct wxStation: XMLIndexerDeserializable {
let stationName: String
let stationState: String
let latitude: Double
let longitude: Double
static func deserialize(_ node: XMLIndexer) throws -> wxStation {
return try wxStation(
stationName: node["station_name"].value(),
stationState: node["state"].value(),
latitude: node["latitude"].value(),
longitude: node["longitude"].value()
)
}
}
public var wxObservationStations: [wxStation] = []
private func getStationNamesAndLocations(url: URL, completion:#escaping (XMLIndexer) -> ()) {
Alamofire.request(url).responseJSON { response in
// print(response) // To check XML data in debug window.
let wxStationList = SWXMLHash.parse(response.data!)
print(wxStationList)
completion(wxStationList)
}
}
//The error is here:
func returnWxStation() -> (wxObservationStations) {
getStationNamesAndLocations(url: wxObserStationsURL!, completion: { serverResponse in
do {
self.wxObservationStations = try serverResponse["wx_station_index"]["station"].value()
} catch {
}
})
return self.wxObservationStations
}
}
Any thoughts? The variable is declared in the class, and I would like to use it to send the data back to the requesting object. Thanks in advance.
The wxObservationStations is not a type, so it doesn't make sense to say
func returnWxStation() -> (wxObservationStations) { ... }
You're returning self.wxObservationStations, which is of type [wxStation]. So the method declaration should be
func returnWxStation() -> [wxStation] { ... }
By the way, your life will be much easier if you stick with Cocoa naming conventions, namely types should start with upper case letters. So rather than the wxStation type, I'd suggest WxStation.
Your following method will not achieve what you want:
func returnWxStation() -> [wxStation] {
getStationNamesAndLocations(url: wxObserStationsURL!, completion: { serverResponse in
do {
self.wxObservationStations = try serverResponse["wx_station_index"]["station"].value()
} catch {
}
})
return self.wxObservationStations
}
The method getStationNamesAndLocations runs asynchronously and your self.wxObservationStations will not be populated by the time that returnWxStation actually returns.
The entire purpose of the getStationNamesAndLocations method is to provide you a nice asynchronous method with completion handler. I would excise returnWxStation from your code entirely. Or do something like:
func returnWxStation(completionHandler: ([wxStation]?) -> Void) {
getStationNamesAndLocations(url: wxObserStationsURL!) { serverResponse in
do {
let stations = try serverResponse["wx_station_index"]["station"].value()
completionHandler(stations)
} catch {
completionHandler(nil)
}
}
}
And you'd use it like so:
returnWxStation() { stations in
guard let stations = stations else {
// handle error here
return
}
// use `stations` here
}
// but not here
Ok I don't get this. I have written some code for forward geocoding, I have an UITextField that you write name of a city in and after you press the enter button it is dismissed and at the same time the function is called to determine if the UITextField contains a valid input. If there is an error it is saved in a bool variable which value is changed in the function. I have print statements all over the place and from the console output I can see that the function ran after the if condition, but it is called before... what? Can somebody explain me what is going on? Code:
var locationError: Bool?
func textFieldShouldReturn(textField: UITextField) -> Bool {
self.view.endEditing(true)
forwardGeocoding(textField.text!)
print("forward geocoding ran 1st time")
print(locationError)
if locationError == true {
print("Error")
} else if locationError == false {
print("Success")
} else if locationError == nil {
print("No value for locationError")
}
return false
}
func forwardGeocoding(address: String) -> CLLocation? {
var userLocation: CLLocation?
CLGeocoder().geocodeAddressString(address, completionHandler: { (placemarks, error) in
if error != nil {
print("Geocoding error: \(error)")
self.locationError = true
return
}
if placemarks?.count > 0 {
print("Placemark found")
self.locationError = false
let placemark = placemarks?.first
let location = placemark?.location
let coordinate = location?.coordinate
print("Settings location: \(coordinate!.latitude), \(coordinate!.longitude)")
if let unwrappedCoordinate = coordinate {
let CLReadyLocation: CLLocation = CLLocation(latitude: unwrappedCoordinate.latitude, longitude: unwrappedCoordinate.longitude)
userLocation = CLReadyLocation
}
}
})
return userLocation
}
Console output:
forward geocoding ran 1st time
nil
No value for locationError
Placemark found
Settings location: 48.8567879, 2.3510768
Try multi threading it...
let queue = NSOperationQueue()
queue.addOperationWithBlock() {
NSOperationQueue.mainQueue().addOperationWithBlock() {
}
}
You need to add the completion handler as your function parameter:
func forwardGeocoding(address: String, completionHandler: (placemarks: String? or [Array of any type], error: NSError?) -> ()) -> CLLocation?
modify your if block:
if error != nil {
print("Geocoding error: \(error)")
self.locationError = true
completionHandler(nil, error)
return
}
then call it like
forwardGeocoding(textField.text!){(placemarks, error) in
//your code.
}
When you call geocodeAddressString, it is executed in a separate thread (#AppleDoc This method submits the specified location data to the geocoding server asynchronously and returns). So effectively you have 2 threads running in parallel. geocodeAddressString in thread 2 will take more time to execute since it makes a server call and the block will be executed when the call returns. During this time thread 1 will finish its execution and will print the log statements.
If you want to handle this issue, locationError if-else condition logic should be implemented in such a way that it should be triggered once your callback is executed.
So im a bit new to swift and object-c as well and was wondering if someone could help me out a bit.
I'm used to creating usually a utils file where I have functions I use often in programming.
In this case im trying to call a function from another swift file and return an array of data.
For example in my mainViewController.swift im calling the function:
var Data = fbGraphCall()
In the Utils.swift file I have a function that Im trying to get it to return an array of data collected.
func fbGraphCall() -> Array<String>{
var fbData: [String] = [""]
if (FBSDKAccessToken.currentAccessToken() != nil){
// get fb info
var userProfileRequestParams = [ "fields" : "id, name, email, about, age_range, address, gender, timezone"]
let userProfileRequest = FBSDKGraphRequest(graphPath: "me", parameters: userProfileRequestParams)
let graphConnection = FBSDKGraphRequestConnection()
graphConnection.addRequest(userProfileRequest, completionHandler: { (connection: FBSDKGraphRequestConnection!, result: AnyObject!, error: NSError!) -> Void in
if(error != nil) {
println(error)
} else {
// DEBUG
println(result)
let fbEmail = result.objectForKey("email") as! String
// DEBUG
println(fbEmail)
fbData.append("\(fbEmail)")
let fbID = result.objectForKey("id") as! String
if(fbEmail != "") {
PFUser.currentUser()?.username = fbEmail
PFUser.currentUser()?.saveEventually(nil)
}
println("Email: \(fbEmail)")
println("FBUserId: \(fbID)")
}
})
graphConnection.start()
}
println(fbData)
return fbData
}
I can confirm that im getting the fbEmail and fbID back from facebook with my debug statements but as I said im still new on how to return data back.
Ideally I usually want an array back if its more than one value or the ability to get back data like Data.fbEmail, Data.fbID or an array maybe like ["email" : "email#gmail.com", "id" : "1324134124zadfa"]
When I hit the return statement its blank.. so not sure why the constants are not keeping values or passing values into my fbData array.. I'm trying fbData.append(fbEmail) for example ..
any thoughts on what might be wrong?
The graphConnection.addRequest is an asynchronous function and you are trying to synchronously return the array of strings back. This won't work because the graphConnection.addRequest is done in the background to avoid blocking the main thread. So instead of returning the data directly make a completion handler. Your function would then become this:
func fbGraphCall(completion: ([String]) -> Void, errorHandler errorHandler: ((NSError) -> Void)?) {
if (FBSDKAccessToken.currentAccessToken() != nil) {
// get fb info
var userProfileRequestParams = [ "fields" : "id, name, email, about, age_range, address, gender, timezone"]
let userProfileRequest = FBSDKGraphRequest(graphPath: "me", parameters: userProfileRequestParams)
let graphConnection = FBSDKGraphRequestConnection()
graphConnection.addRequest(userProfileRequest, completionHandler: { (connection: FBSDKGraphRequestConnection!, result: AnyObject!, error: NSError!) -> Void in
if(error != nil) {
println(error)
errorHandler?(error!)
} else {
var fbData = [String]() // Notice how I removed the empty string you were putting in here.
// DEBUG
println(result)
let fbEmail = result.objectForKey("email") as! String
// DEBUG
println(fbEmail)
fbData.append("\(fbEmail)")
let fbID = result.objectForKey("id") as! String
if(fbEmail != "") {
PFUser.currentUser()?.username = fbEmail
PFUser.currentUser()?.saveEventually(nil)
}
println("Email: \(fbEmail)")
println("FBUserId: \(fbID)")
completion(fbData)
}
})
graphConnection.start()
}
}
I added the completion handler and the error handler blocks that get executed according to what's needed.
Now at the call site you can do something like this:
fbGraphCall( { println($0) // $0 refers to the array of Strings retrieved }, errorHandler: { println($0) // TODO: Error handling }) // Optionally you can pass `nil` for the error block too incase you don't want to do any error handling but this is not recommended.
Edit:
In order to use the variables you would do something like this at the call site
fbGraphCall( { array in
dispatch_async(dispatch_get_main_queue(), { // Get the main queue because UI updates must always happen on the main queue.
self.fbIDLabel.text = array.first // array is the array we received from the function so make sure you check the bounds and use the right index to get the right values.
self.fbEmailLabel.text = array.last
})
}, errorHandler: {
println($0)
// TODO: Error handling
})
I'm a beginner working with Parse and Swift. I need to update the object referred to in my viewDidLoad in another function within the same controller. How do I pass the currently loaded object's objectId without having to hardcode it like this:
query.getObjectInBackgroundWithId("8DkYgraEJq")
Here is my viewDidLoad function:
override func viewDidLoad() {
var query = PFQuery(className: "CheckedBaggage")
query.orderByAscending("createdAt")
query.whereKey("respondedTo", notEqualTo: true)
query.getFirstObjectInBackgroundWithBlock {
(CheckedBaggage: PFObject!, error: NSError!) -> Void in
if error != nil {
println("The getFirstObject request failed.")
} else {
// The find succeeded.
self.randomBaggageLabel.text = CheckedBaggage.objectForKey("message") as? NSString
CheckedBaggage.save()
println(CheckedBaggage.objectId)
let baggageId = CheckedBaggage.objectId
println("Successfully retrieved the object.")
}
}
I would like to try and pass the variable baggageId, which should be the object's ID as a string, as an argument to the getObjectInBackgroundWithId block in my carryIt function:
#IBAction func carryIt(sender: AnyObject!) {
println("CarryIt is being called")
var query = PFQuery(className: "CheckedBaggage")
query.getObjectInBackgroundWithId(baggageId) {
(CheckedBaggage: PFObject?, error: NSError?) -> Void in
if error != nil {
println(error)
} else if let CheckedBaggage = CheckedBaggage {
println("object hello!")
CheckedBaggage["respondedTo"] = true
CheckedBaggage["response"] = self.kindnessMessage.text
CheckedBaggage.save()
}
}
}
But I'm getting an "unresolved identifier" error. It updates my Parse database perfectly fine if I hardcode the object ID, but I can't do it this way. Here's a screenshot of the error:
Thank you so much for your help!
You have to initialize baggageId. To use it in multiple functions, it must be scoped at class level as the comment said. To set it after it has been declared, it must be a "var", not a constant "let".
var baggageId = ""
func viewDidload() {
var query = ...
query.get... {
baggageId = CheckedBaggege.objectId
}
}
func shipIt() {
var query = ...
query.getObjectWithId(baggageId) ...
}