I'am trying to query an array of PFGeoPoints stored on the Parse backend. I have the User table, with data assigned to it such as "location", "name".
everything is being sent to Parse upon posting from my app and is properly stored in the backend. I am having issues retrieving all location from Parse and storing them into an MKAnnotation on the map.
Find below my code
import UIKit
import Parse
import CoreLocation
import MapKit
class mapViewController: UIViewController, MKMapViewDelegate, CLLocationManagerDelegate {
#IBOutlet var mapUsers: MKMapView!
var MapViewLocationManager:CLLocationManager! = CLLocationManager()
var currentLoc: PFGeoPoint! = PFGeoPoint()
override func viewDidLoad() {
super.viewDidLoad()
// ask user for their position in the map
PFGeoPoint.geoPointForCurrentLocationInBackground {
(geoPoint: PFGeoPoint?, error: NSError?) -> Void in
if let geoPoint = geoPoint {
PFUser.currentUser()? ["location"] = geoPoint
PFUser.currentUser()?.save()
}
}
mapUsers.showsUserLocation = true
mapUsers.delegate = self
MapViewLocationManager.delegate = self
MapViewLocationManager.startUpdatingLocation()
mapUsers.setUserTrackingMode(MKUserTrackingMode.Follow, animated: false)
}
override func viewDidAppear(animated: Bool) {
let annotationQuery = PFQuery(className: "User")
currentLoc = PFGeoPoint(location: MapViewLocationManager.location)
annotationQuery.whereKey("Location", nearGeoPoint: currentLoc, withinMiles: 10)
annotationQuery.findObjectsInBackgroundWithBlock {
(PFUser, error) -> Void in
if error == nil {
// The find succeeded.
print("Successful query for annotations")
let myUsers = PFUser as! [PFObject]
for users in myUsers {
let point = users["Location"] as! PFGeoPoint
let annotation = MKPointAnnotation()
annotation.coordinate = CLLocationCoordinate2DMake(point.latitude, point.longitude)
self.mapUsers.addAnnotation(annotation)
}
} else {
// Log details of the failure
print("Error: \(error)")
}
}
}
Instead of putting the call in your viewDidAppear() method - as previous commenters said, your location may be nil, returning no results - I would use a tracker of some sort and put it in your didUpdateLocations() MKMapView delegate method. Here, I use GCD's dispatch_once() so that when my location is found for the first time with a reasonable accuracy, my code is executed (here you will put your call to Parse).
Declare GCD's tracker variable
var foundLocation: dispatch_once_t = 0
Now use something like this in your location manager's delegate method didUpdateLocations()
if userLocation?.location?.horizontalAccuracy < 2001 {
dispatch_once(&foundLocation, {
// Put your call to Parse here
})
}
PS. You should also consider doing any updates to UI on the main thread. Parse fetches that data on a background thread and although you might never see a problem, it's both safer and good habit to do any UI changes on main thread. You can do this with the following:
dispatch_async(dispatch_get_main_queue(), {
// Put any UI update code here. For example your pin adding
}
Related
I have data stored in a dynamoDB table on Amazon. I'm able to query that table. Within the dynamoDbObjectMapper.query statement I can print the result. With the result I want to create GMSMakers (pins on a Google Map). But when I call this the app crashes with a message stating "The API method must be called from the main thread". I just don't seem to get what I need to do.
So my question is if somebody can provide an example that can help me understand what's going on and guide me towards the solution. Passing the result of query "upwards" to the main thread so I can call the Google SDK functions.
I would appreciate it very much.....
Here is my code (please be gentle....). Result : the GMSMarker call generates this runtime exception: 'GMSThreadException', reason: 'The API method must be called from the main thread'
import UIKit
import GoogleMaps
import AWSCore
import AWSMobileClient
import AWSDynamoDB
class DiscoverViewController: UIViewController, CLLocationManagerDelegate, GMSMapViewDelegate {
#IBOutlet weak var mapView: GMSMapView!
var currentPositionMarker: GMSMarker!
var locationManager = CLLocationManager()
var output: AWSDynamoDBPaginatedOutput!
var paginatedOutput: AWSDynamoDBPaginatedOutput?
override func viewDidLoad() {
super.viewDidLoad()
//Hide the mapview until the currentlocation is found. unhide will be done from the event handler.
mapView.isHidden = true
mapView.delegate = self
self.locationManager.requestAlwaysAuthorization()
// For use in foreground
self.locationManager.requestWhenInUseAuthorization()
// Request current location: once.
if CLLocationManager.locationServicesEnabled() {
locationManager = CLLocationManager()
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.requestAlwaysAuthorization()
locationManager.requestLocation() //One time request
}
//first create query expression
let queryExpression = AWSDynamoDBQueryExpression()
queryExpression.keyConditionExpression = "#attribute1 = :value1"
queryExpression.expressionAttributeNames = [ "#attribute1": "storyUserId" ]
queryExpression.expressionAttributeValues = [ ":value1": "roger"]
let dynamoDbObjectMapper = AWSDynamoDBObjectMapper.default()
dynamoDbObjectMapper.query(Stories.self, expression: queryExpression) { (output: AWSDynamoDBPaginatedOutput?, error: Error?) in
if error != nil {
print("The request failed. Error: \(String(describing: error))")
}
if output != nil {
print(output!.items)
for stories in output!.items {
let story = stories as? Stories
print("Pin read \(story!._storyUserId!)")
print("Pin read \(story!._storyName!)")
print("Pin read \(story!._storyDateTime!)")
print("Pin read \(story!._storyAudioFilename!)")
print("Pin read \(story!._storyLongitude!)")
print("Pin read \(story!._storyLatitude!)")
print("Creating pin")
let positionMarker = GMSMarker()
positionMarker.position.latitude = story!._storyLatitude as! Double
positionMarker.position.longitude = story!._storyLongitude as! Double
positionMarker.title = story!._storyName
positionMarker.snippet = story!._storyAudioFilename
positionMarker.iconView = currentPositionMarkerView
positionMarker.map = self.mapView
self.showPins(result: output!)
} else {
print("No data read from the table")
}
}
// Do any additional setup after loading the view, typically from a nib.
}
I'm using Firestore together with Swift.
I have a singleton data class UserManager. I call this from my different ViewControllers to get data to populate my tableviews. I want the tableviews to automatically update when the collections are updated so I need to use a SnapshotListener. Everything works fine but I'm not sure how to detach from the listener when the Viewcontroller is closed.
In the singleton class I have methods like this below. The method gives a list of users and will be called from several different places around my app.
I also want to give back a reference to the listener so that I can detach from it when the Viewcontroller is closed. But I can't get it working. The below solution gives compiler error.
I've been trying to look at the reference, for example here
https://firebase.google.com/docs/firestore/query-data/listen but I need to get it working when the data is loaded in a singleton class instead of directly in the Viewcontroller. What is the way to go here?
In UserManager:
func allUsers(completion:#escaping ([User], ListenerRegistration?)->Void) {
let listener = db.collection("users").addSnapshotListener { querySnapshot, error in
if let documents = querySnapshot?.documents {
var users = [User]()
for document in documents {
let user = User(snapshot: document)
users.append(user)
}
completion(users, listener)
}
}
}
In ViewController:
override func viewDidLoad() {
super.viewDidLoad()
UserManager.shared.allUsers(completion: { (users, listener) in
self.users = users
self.listener = listener
self.tableView.reloadData()
})
}
deinit {
self.listener.remove()
}
I guess the compiler error that you see is referring to the fact that you are using listener into it's own defining context.
Try this for a change:
In UserManager:
func allUsers(completion:#escaping ([User])->Void) -> ListenerRegistration? {
return db.collection("users").addSnapshotListener { querySnapshot, error in
if let documents = querySnapshot?.documents {
var users = [User]()
for document in documents {
let user = User(snapshot: document)
users.append(user)
}
completion(users)
}
}
}
In ViewController:
override func viewDidLoad() {
super.viewDidLoad()
self.listener = UserManager.shared.allUsers(completion: { (users) in
self.users = users
self.tableView.reloadData()
})
}
deinit {
self.listener.remove()
}
I think that getDocument instead of addSnapshotListener is what you are looking for.
Using this method the listener is automatically detached at the end of the request...
It will be something similar to
func allUsers(completion:#escaping ([User])->Void) {
db.collection("users").getDocument { querySnapshot, error in
if let documents = querySnapshot?.documents {
var users = [User]()
for document in documents {
let user = User(snapshot: document)
users.append(user)
}
completion(users)
}
} }
EnvironmentXcode 8Swift 3
Problem Statement
I want to be able to determine if a user taps on a MKPointAnnotation and then extract information (like title and subtitle) from that annotation for use within my app.
I imagine this is not terribly difficult, but I'm a bit lost in terms of what I need to do / what various classes / objects / methods / etc. I need to use to do this.So I'm looking for pointers / guidance - code is welcome, but at this point the pointers / guidance would be a significant step forward for me.
Code SnippetsAbridged version of the code thus far (trying to limit it to just the relevant pieces)
class NewLocationViewController: UIViewController, CLLocationManagerDelegate, UITextFieldDelegate {
//... various #IBOutlet's for text fields, buttons, etc. ...
#IBOutlet weak var map: MKMapView!
var coords: CLLocationCoordinate2D?
var locationManager: CLLocationManager = CLLocationManager()
var myLocation: CLLocation!
var annotation: MKPointAnnotation!
var annotationList: [MKPointAnnotation] = []
var matchingItems: [MKMapItem] = [MKMapItem]()
override func viewDidLoad() {
super.viewDidLoad()
//... text field delegates, and other initilizations ...
locationManager.requestWhenInUseAuthorization()
if CLLocationManager.locationServicesEnabled() {
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.delegate = self
}
myLocation = nil
//... other initializations...
}
// Search for things that match what my app is looking for ("<search string>")
func performSearch() {
annotationList.removeAll() // clear list
matchingItems.removeAll() // clear list
var closest = MKMapItem()
var distance = 10000.0
let request = MKLocalSearchRequest()
let span = MKCoordinateSpan(latitudeDelta: 0.001, longitudeDelta: 0.001)
request.naturalLanguageQuery = "<search string>"
request.region = MKCoordinateRegionMake(myLocation.coordinate, span)
let search = MKLocalSearch(request: request)
if search.isSearching {
search.cancel()
}
search.start(completionHandler: {
(_ response, _ error) in
if error != nil {
self.showAlert(msg: "Error occurred in search\nERROR: \(error?.localizedDescription)")
}
else if response!.mapItems.count == 0 {
self.showAlert(msg: "No matches found")
}
else {
for item in response!.mapItems {
// Track the closest placemark to our current [specified] location
let (distanceBetween, prettyDistance) = self.getDistance(loc1: self.myLocation, loc2: item.placemark.location!)
let addrObj = self.getAddress(placemark: item.placemark)
//... some code omitted ...
// Add markers for all the matches found
self.matchingItems.append(item as MKMapItem)
let annotation = MKPointAnnotation()
annotation.coordinate = item.placemark.coordinate
annotation.title = item.name
annotation.subtitle = "\(addrObj.address!) (\(prettyDistance))"
self.map.addAnnotation(annotation)
self.annotationList.append(annotation)
}
//... some code omitted ...
}
})
}
//... code for getDistance(), getAddress() omitted for brevity - they work as designed ...
//... other code omitted as not being relevant to the topic at hand
}
I imagine that I will need to override touchesEnded and possibly touchesBegan and maybe touchesMoved in order to detect the tap.
What I cannot figure out is how to compare a touch's location (represented as X/Y coordinates on the screen) to an MKPointAnnotation's or MKMapItem's location (which is represented as latitude/longitude coordinates on a map)
So - that's kind of where I'm currently stuck. I searched various terms on the web but wasn't able to find anything that [simplisticly] answerwed my question - and in Swift code format (there were a number of postings that looked like they might help, but the code presented wasn't in Swift and I don't do the translation that easily).
UPDATE (19:48 ET)
I found this article: How do I implement the UITapGestureRecognizer into my application and tried to follow it, but ...
I modified the code a bit (added UIGestureRecognizerDelegate):
class NewLocationViewController: UIViewController, CLLocationManagerDelegate, UITextFieldDelegate, UIGestureRecognizerDelegate {
override func viewDidLoad() {
super.viewDidLoad()
//...other code...
let tapHandler = UITapGestureRecognizer(target: self, action: Selector(("handleTap:"))) //<<<== See notes below
tapHandler.numberOfTapsRequired = 1
tapHandler.numberOfTouchesRequired = 1
tapHandler.delegate = self
print("A")//#=#
map.addGestureRecognizer(tapHandler)
map.isUserInteractionEnabled = true
print("B")//#=#
}
func handleTap(tap: UITapGestureRecognizer) {
print("ARRIVED")//#=#
let here = tap.location(in: map)
print("I AM HERE: \(here)")//#=#
}
//...
}
With regard to the declaration / definition of tapHandler, I tried the following:
let tapHandler = UITapGestureRecognizer(target: self, action: "handleTap:")
let tapHandler = UITapGestureRecognizer(target: self, action: Selector("handleTap:"))
let tapHandler = UITapGestureRecognizer(target: self, action: Selector(("handleTap:"))) // supresses warning
The first two caused a warning to show up in Xcode, the last simply supresses the warning:
[W] No method declared with Objective-C selector 'handleTap:'
When I run my app and tap on a pin - I get the following in my log:
A
B
libc++abi.dylib: terminating with uncaught exception of type NSException
Which would seem (to me) to indicate that the general setup in viewDidLoad is okay, but as soon as it tries to handle the tap, it dies without ever getting to my handleTap function - and thus the warning (shown above) would seem to be far more serious.
So, I'm not sure if I can count this as making progress, but I'm trying...
Thanks to this MKAnnotationView and tap detection I was able to find a solution. My code changes from those originally posted:
class NewLocationViewController: UIViewController, CLLocationManagerDelegate, UITextFieldDelegate, UIGestureRecognizerDelegate {
override func viewDidLoad() {
super.viewDidLoad()
//...other code...
let tapHandler = UITapGestureRecognizer() //<<<== No parameters
tapHandler.numberOfTapsRequired = 1
tapHandler.numberOfTouchesRequired = 1
tapHandler.delegate = self
map.addGestureRecognizer(tapHandler)
map.isUserInteractionEnabled = true
}
// Not sure who calls this and requires the Bool response, but it seems to work...
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
return self.handleTap(touch: touch).count > 0
}
// Major Changes
private func handleTap(touch: UITouch) -> [MKAnnotationView] {
var tappedAnnotations: [MKAnnotationView] = []
for annotation in self.map.annotations {
if let annotationView: MKAnnotationView = self.map.view(for: annotation) {
let annotationPoint = touch.location(in: annotationView)
if annotationView.bounds.contains(annotationPoint) {
self.name.text = annotationView.annotation?.title!
let addr = AddrInfo(composite: ((annotationView.annotation?.subtitle)!)!)
self.address.text = addr.street!
self.city.text = addr.city!
self.state.text = addr.state!
self.zipcode.text = addr.zip!
tappedAnnotations.append(annotationView)
break
}
}
}
return tappedAnnotations
}
//...
}
The AddrInfo piece is my own little subclass that, among other things, takes a string like "1000 Main St., Pittsburgh, PA 15212, United States" and breaks it into the individual pieces so that they can be accessed, well, individually (as indicated in the code above).
There might be an easier, or better, way to achieve what I was looking for - but the above does achieve it, and so I consider it to be the answer for my issue.
When I receive in my Bluetooth class (new) values from my device, then I call a delegate. So the Bluetooth class is running in the background.
My protocol is simple:
protocol RefreshPositionInDrive {
func changingValue(latitude: Double, longitude: Double)
}
In my UIViewController I initialize a map. When I worked at the beginning without delegates this code works fine.
func initializeMapResolution() {
let regionRadius: CLLocationDistance = 1000
let initialLocation = CLLocation(latitude: 50.910349, longitude: 8.066895)
let coordinateRegion = MKCoordinateRegionMakeWithDistance(initialLocation.coordinate,
regionRadius * 1.5, regionRadius * 1.5)
MapDrive.setRegion(coordinateRegion, animated: true)
MapDrive.delegate = self
}
My method from my protocol:
func changingValue(latitude: Double,longitude: Double) {
print("THE NEW COORDINATES \(latitude) \(longitude)")
if self.MapDrive == nil {
print("Is nil")
} else {
updateTheMap()
}
}
Output:
THE NEW COORDINATES 25.012x 16.992
Is nil
But I don't understand that. I initialize my map first. After that the changingValue is called. How can be the MapDrive nil?
I tested the code without delegates, just with some fix coordinates in my UIViewController and the annotation appears.
(I'm working the first time with delegates.)
EDIT
I was indistinctly: My MapDrive:
#IBOutlet weak var MapDrive: MKMapView!
So I can't instantiate like you mean or?
You'll want to reference a MapDrive instance to the UIViewController, your MapDrive is probably released when your function ends.
class UIViewController {
var mapDrive = MapDrive()
func initializeMapResolution() {
// Instantiate map drive, assign it to self.mapDrive
//then assign the delegate of the property on self.
self.mapDrive.delegate = self
}
}
I am new to Swift and I am trying to find the current user location, and then query all nearby users and then load them into a UITableView in my storyboard. However, when I build my code, no data shows in my UITableView (Parse is my backend). I tried to research the problem and found this way of using PFQueryTableViewController:
PFQueryTableViewController in swift not loading data
However, when I do this
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
always returns an NSException error. How can I fix that problem or fix the below code to return my parse data into my table view?
import UIKit
import Parse
import ParseUI
import CoreLocation
class MasterTableViewController: PFQueryTableViewController {
var usersLocation: PFGeoPoint? {
didSet {
// This will reload the tableview when you set the users location.
// Handy if you want to keep updating it.
if (tableView != nil) {
tableView.reloadData()
}
}
}
override func viewDidLoad() {
super.viewDidLoad()
PFGeoPoint.geoPointForCurrentLocationInBackground { point, error in
if error == nil {
self.usersLocation = point
}
}
}
override func queryForTable() -> PFQuery {
var query = PFQuery(className: "User")
if let location = usersLocation {
query.whereKey("location", nearGeoPoint: location)
}
query.limit = 10
return query
}
In your queryForTable method it seems you're trying to query the User class.
From Parse docs, to query for users there is a special way of doing it:
example:
var query = PFUser.query()
query.whereKey("gender", equalTo:"female")
var girls = query.findObjects()
https://www.parse.com/docs/ios/guide#users-querying
Hopefully that helps