1

I am using Swift, CoreLocation, and Parse in my app. I have a couple questions that I hope someone can answer please:

1) When exactly does the queryForTable method get called?

2) Why is the user's current location (CLLocationCoordinate2D) not available from the get go when the app loads (currLocation seems to be nil at first here: if let queryLoc = currLocation)?

See comments in part of my code below.

var currLocation: PFGeoPoint?

override func viewDidLoad() {
    super.viewDidLoad()

    queryForPosts()
}

func queryForPosts() {

    PFGeoPoint.geoPointForCurrentLocationInBackground {
        (geoPoint: PFGeoPoint?, error: NSError?) -> Void in
        if error == nil {
            // do something with the new geoPoint
            self.currLocation = geoPoint

            // this will call queryForTable
            self.loadObjects()
        }
    }

}

override func queryForTable() -> PFQuery {

    let query = PFQuery(className: ParseHelper.ParsePostClass)

    // Need to fix: the query is being run before the location manager returns a valid location (currLocation)...
    if let queryLoc = self.currLocation {

        print("SUCESS: Successfully queried user's location.")

        query.whereKey("location", nearGeoPoint: queryLoc, withinMiles: 3)

        query.limit = 200;

        if (self.objects?.count == 0) {

            query.cachePolicy = PFCachePolicy.CacheThenNetwork

        }

        query.addDescendingOrder("createdAt")

        return query

    } else {
        // This else block is being executed first every time the view loads. Only after the user performs a pull to refresh on the table view, the if block gets executed and the correct objects display. 
        print("FAILURE: Could not query user's location.")

        /* Decide on how the application should react if there is no location available */

    }

    return PFQuery()

}
dnadri
  • 173
  • 1
  • 2
  • 11

2 Answers2

3

The location is not readily available because didUpdateLocations is an asynchronous process.

From Apple's documentation on startUpdatingLocation:

This method returns immediately. Calling this method causes the location manager to obtain an initial location fix (which may take several seconds) and notify your delegate by calling its locationManager:didUpdateLocations: method

As gnasher729 mentioned, the location services of phones use power-intensive systems and should be disabled once you're done. You've already achieved this using locationManager.stopUpdatingLocation().

With regards to your question about when queryForTable gets called in a PFQueryTableViewController, all we have to do is look at the open-sourced ParseUI Github for it. Here we find that queryForTable is called as part of loadObjects, which in turn is called in viewDidLoad.

What you should use is geoPointForCurrentLocationInBackground (Included in the Parse framework) which makes it easy to get the user's location. As stated in the documentation, this function takes care of all the location services for you.

  • An internal CLLocationManager starts listening for location updates (via startsUpdatingLocation).
  • Once a location is received, the location manager stops listening for location updates (via stopsUpdatingLocation) and a PFGeoPoint is created from the new location. If the location manager errors out, it still stops listening for updates, and returns an NSError instead.
  • Your block is called with the PFGeoPoint.

Use this in a separate function and call it in viewDidLoad:

PFGeoPoint.geoPointForCurrentLocationInBackground {
  (geoPoint: PFGeoPoint?, error: NSError?) -> Void in
  if error == nil {
    // do something with the new geoPoint

    // this will call queryForTable
    self.loadObjects()
  }
}
Russell
  • 3,099
  • 2
  • 14
  • 18
  • thanks. I tried the code you suggested. Unpredictably, it sometimes queries correctly and sometimes does not. 1) Am I supposed to do something with the new `geoPoint`, if so what? 2) Do I need to remove any CLLocationManager code (including `func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation])` since the `geoPointForCurrentLocationInBackground` takes care of all the location services for you, as you mentioned? – dnadri Jan 15 '16 at 19:23
  • You won't need any of the CLLocationManager code, feel free to remove it or comment it out. The new geopoint should be stored as the current location or wherever you like so it can be used in the query. In most typical use cases, `queryForTable` should return nil if the location is nil. To achieve that see the following https://stackoverflow.com/questions/33312128/queryfortable-cannot-return-nil-in-swift – Russell Jan 15 '16 at 20:32
  • the same problem still persists even after removing the CLLocationManager code and trying the new approach. For some unknown reason, in the `queryForTable` method, the `if self.currLocation != nil` block does not get executed - the `else` block does. Only after I pull to refresh does the `if` block execute the query and its constraints. – dnadri Jan 15 '16 at 21:00
  • pull to refresh just calls `loadObjects()`, as seen in the github link. The `else` block will get called the first time the view loads while the location is being fetched asynchronously. `loadObjects()` just needs to be called again in the completion handler of `geoPointForCurrentLocationInBackground`. Try adding in your updated code if you're stilling having issues and I can look at it – Russell Jan 15 '16 at 21:04
  • If the location is nil and the queryForTable returns nil, then no objects will be queried and the table will be empty. Do you know why the location still does not become available when the view loads? – dnadri Jan 15 '16 at 21:05
  • Exactly right. The table will be empty until the location has been fetched. As said above and referenced from Apple, "... to obtain an initial location fix ***(which may take several seconds)***". Once the view loads, the location will be fetched asynchronously which takes some time, then once fetched the query will be updated and executed. Only once the query returns will the table be populated as expected. – Russell Jan 15 '16 at 21:08
  • Because of this delay, location updates are typically handled in the appDelegate so they may occur behind the scenes on startup. You can then store the location information locally and pass it around or put it inside of Parse class. – Russell Jan 15 '16 at 21:10
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/100820/discussion-between-dnadri-and-russell). – dnadri Jan 16 '16 at 03:03
1

To get the user's location, the device has to turn on some rather power consuming systems. The GPS for example takes lots of powers. Your users would be very unhappy if GPS was turned on permanently.

gnasher729
  • 51,477
  • 5
  • 75
  • 98