I'm trying to go to another area after a location is input (zip, address, city, st). It works-SOMETIMES. After I move the map (zoom out or move around), I'm not able to search for an area using the location. I thought I should call startUpdatingLocation again, but that didn't seem to work. Any help would be greatly appreciated considering this is my first time working with maps. Code below:
let geocoder = CLGeocoder()
geocoder.geocodeAddressString(self.zipCode.text!.capitalized) {
placemarks, error in
if error == nil {
let placemark = placemarks?.first
let lat = placemark?.location?.coordinate.latitude
let lon = placemark?.location?.coordinate.longitude
let center = CLLocationCoordinate2D(latitude: lat ?? 0.0, longitude: lon ?? 0.0)
print("\(self.zipCode.text) lat \(lat) long \(lon)")
let mRegion = MKCoordinateRegion(center: center, span: MKCoordinateSpan(latitudeDelta: 0.2, longitudeDelta: 0.2))
self.mapView.setRegion(mRegion, animated: true)
}
else{
print("Error finding location \(self.zipCode.text!)")
I would need a little more context to give you an exact answer (like where in the code are you calling this, how often are you doing, what is the string dress value), but as far as I can see the problem could be:
lat or Lon could be nil and would default to 0.0 (?? operator)
all the code is running inside the closure, so it could be a scope problem
Also, taking a quick look at Apple documentation I found the following:
After initiating a forward-geocoding request, do not attempt to initiate another forward- or reverse-geocoding request. Geocoding requests are rate-limited for each app, so making too many requests in a short period of time may cause some of the requests to fail. When the maximum rate is exceeded, the geocoder passes an error object with the value CLError.Code.network to your completion handler.
So it could be an problem with the amount of the requests you are doing in a sort amount of time
I would say that is due to the number of requests made within a small time interval. I experimented with Apple's location framework for a long time and found that a single app can't send multiple geocoding request within short time. If you try so, you will get an error.
Related
I am attempting to include a function in a Predicate definition. Is this possible?
Let's say you have a Core Data entity of Places with attributes for
latitude and longitude.
I would like to add annotations to a mapview of those Places within a
specified distance from the user location. I can, of course, loop through the entire database and calculate the distance between each Place and the
user location but I will have about 35000 places and it would seem that
it would be more efficient to have a predicate in the
fetchedResultsController setup.
I tried the code below but I receive an error message of "Problem with
subpredicate BLOCKPREDICATE(0x2808237b0) with userInfo of (null)"
func myDistanceFilter(myLat : CLLocationDegrees, myLong : CLLocationDegrees, cdLat : CLLocationDegrees, cdLong : CLLocationDegrees) -> Bool {
let myLocation = CLLocation(latitude: myLat, longitude: myLong)
let cdLocation = CLLocation(latitude: cdLat, longitude: cdLong)
if myLocation.distance(from: cdLocation) < 5000.0 {
return true
} else {
return false
}
}//myDistancePredicate
And inside the fetchedResultsController:
let distancePredicate = NSPredicate {_,_ in self.myDistanceFilter(myLat: 37.774929, myLong: -122.419418, cdLat: 38.0, cdLong: -122.0)}
If it is possible to have a block/function inside a predicate how do you get a reference to an attribute for the entity object being evaluated?
Any guidance would be appreciated.
An additional observation for anyone else struggling with a similar issue.
Considering the suggestions of pbasdf and Jerry above, at least in my case, there is no reason why a region has to be round. I'll craft a name indicating a nearly rectangular area. I ran some tests with latitude and longitude values. These latitude and longitude values can be scaled to a rectangle enclosing a circular radius specified by the user. One degree of latitude is about 69 miles and one degree of longitude at the latitude of Chicago is about 51 miles. I used the following:
var myUserLatitude : CLLocationDegrees!
var myUserLongitude : CLLocationDegrees!
In the init file for the view:
guard let userLatitude = locationManager.location?.coordinate.latitude else {return}
guard let userLongitude = locationManager.location?.coordinate.longitude else {return}
myUserLatitude = userLatitude
myUserLongitude = userLongitude
Inside the fetchedResultsController variable creation:
let latitudeMinPredicate = NSPredicate(format: "latitude >= %lf", myUserLatitude - 1.0)
let latitudeMaxPredicate = NSPredicate(format: "latitude <= %lf", myUserLatitude + 1.0)
let longitudeMinPredicate = NSPredicate(format: "longitude >= %lf", myUserLongitude - 1.0)
let longitudeMaxPredicate = NSPredicate(format: "longitude <= %lf", myUserLongitude + 1.0)
var compoundPredicate = NSCompoundPredicate()
compoundPredicate = NSCompoundPredicate(andPredicateWithSubpredicates: [anotherUnrelatedPredicate,latitudeMinPredicate,latitudeMaxPredicate,longitudeMinPredicate, longitudeMaxPredicate])
fetchRequest.predicate = compoundPredicate
Obviously I will create another property to scale the 1.0 value per the user desired region. Initial tests seem to work and best of all I can't believe how fast it it. Literally, the tableView is populated by the time the viewController segues to the enclosing mapView.
Well, yes it is possible. NSPredicate does have +predicateWithBlock: and init(block:).
However if you scroll down on that page you see the bad news:
Core Data supports block-based predicates in the in-memory and atomic stores, but not in the SQLite-based store.
So, whether you use an in-memory store, or do the filtering in code, either way you need to bring these 35,000 items into memory, and performance of a brute force approach will be poor.
There is a point of complexity at which SQL is no longer appropriate – you get better performance with real code. I think your requirement is far beyond that point. This will be an interesting computer science project in optimization. You need to do some pre-computing, analagous to adding an index to your database. Consider adding a region attribute to your place entities, then write your predicate to fetch all places within the target location's region and all immediate neighbors. Then filter through those candidates in code. I'm sure this has been done by others – think of cells in a cell phone network – but Core Data is not going to give you this for free.
I posted this issue on GitHub, though it has been over a week and no response from the developers, so hoping to get an answer here.
Using the example code, plus adding a bit to show placemarks returned from ForwardGeocodeOptions, I came up with this testing code:
(Swift 3, Xcode 8)
func mapView(_ mapView: MGLMapView, regionDidChangeAnimated animated: Bool) {
geocodingDataTask?.cancel()
self.outputText.text = ""
// Variables.userLat and Variables.userLng are set through locationManager
let options = ReverseGeocodeOptions(coordinate: CLLocationCoordinate2D(latitude: Variables.userLat, longitude: Variables.userLng))
geocodingDataTask = geocoder.geocode(options) { [unowned self] (placemarks, attribution, error) in
if let error = error {
NSLog("%#", error)
} else if let placemarks = placemarks, !placemarks.isEmpty {
self.resultsLabel.text = placemarks[0].qualifiedName
let foptions = ForwardGeocodeOptions(query: self.inputText.text!)
// To refine the search, you can set various properties on the options object.
foptions.allowedISOCountryCodes = ["US"]
foptions.focalLocation = CLLocation(latitude: Variables.userLat, longitude: Variables.userLng)
let neLat = Variables.userLat + 1.0
let neLng = Variables.userLng + 1.0
foptions.allowedRegion?.northEast = CLLocationCoordinate2D(latitude: neLat, longitude: neLng)
let swLat = Variables.userLat - 1.0
let swLng = Variables.userLng - 1.0
foptions.allowedRegion?.southWest = CLLocationCoordinate2D(latitude: swLat, longitude: swLng)
foptions.allowedScopes = [.address, .pointOfInterest]
let _ = geocoder.geocode(foptions) { (placemarks, attribution, error) in
guard let placemark = placemarks?.first else {
return
}
let coordinate = placemark.location.coordinate
print("\(coordinate.latitude), \(coordinate.longitude)")
self.inputLat.text = coordinate.latitude.description
self.inputLng.text = coordinate.longitude.description
var string = ""
for mark in placemarks! {
if string != "" {
string += "\n"
}
string += mark.qualifiedName
}
self.outputText.text = string
}
} else {
self.resultsLabel.text = "No results"
}
}
}
That gives me a mini-app to test out the data that is returned when I change locations in the Xcode Simulator.
screenshot 2017-07-12 13 54 09
As you can see from this shot, I have centered the map in Jenks, OK (a small town just outside of Tulsa, OK - sort of a 'central US' location.)
When searching for a common place in that area ("Walmart" - which is based in nearby Arkansas, so there are plenty of them around), you can see that only 2 'local' Walmart's come back in the search.
Now, let's move to Bentonville, AR - the home of Walmart......
And, we get two top new results, but the others are the same (and much farther away than Tulsa, OK.....)
We found that if we add the town to the first of the search, the results are much better:
(similar results are true for every search we did - various cities around the US and with other 'common places' like Quiznos (similar results as Walmart when in their home town of Denver, CO...)
As you can see from my code, I tried using the allowedRegion?.northEast and southWest (as I understand it, those should set the search area to about 100 miles around the location, though I'm not sure I set that up right), though no difference was found from this setup (i.e., with/without I get the same results).
The only 'better' results were by putting in the town name along with the 'common' one (though, oddly, different results were returned if the town name was before or after the common one - I didn't check exactly, though I think they are 'best' (i.e., locations are all pretty near) from putting the town name after the common one)
What can I do to get better results without having to tell the user to enter the town name (not a very desirable plan! :)
Thank you in advance for tips - the lookup is a key part of the app (not the test stuff shown in the pictures! ) and users expect to pull up several 'nearby' common places (in this case, we would expect to see all 5 results within something like 20 miles - certainly no more than 100 miles away), so it is an important thing for us that this work much more reliably than we are seeing now.
I am trying to convert the location address to coordinates and open it inside the maps app, but I am getting this error when the function is called.
[Client] Geocode error: <private>. That is the only thing printed inside the console.
#IBAction func openinmaps(_ sender: AnyObject) {
var geocoder: CLGeocoder = CLGeocoder()
var location = "1 Infinite Loop"
geocoder.geocodeAddressString(location,completionHandler: {(placemarks: [CLPlacemark]?, error: NSError?) -> Void in
if (placemarks?.count > 0) {
var topResult: CLPlacemark = (placemarks?[0])!
var placemark: MKPlacemark = MKPlacemark(placemark: topResult)
let regionDistance:CLLocationDistance = 10000
let coordinates = CLLocationCoordinate2DMake(placemark.coordinate.latitude, placemark.coordinate.latitude)
let regionSpan = MKCoordinateRegionMakeWithDistance(coordinates, regionDistance, regionDistance)
let options = [
MKLaunchOptionsMapCenterKey: NSValue(mkCoordinate: regionSpan.center),
MKLaunchOptionsMapSpanKey: NSValue(mkCoordinateSpan: regionSpan.span)
]
let mapItem = MKMapItem(placemark: placemark)
mapItem.openInMaps(launchOptions: options)
}
})
}
My extension for CLLocationManagerDelegate was randomly crashing because of the same error: [Client] Geocode error: <private> in swift 3.0.
The thing is:
geocoder.reverseGeocodeLocation(startLocation, completionHandler: {
placemarks, error in
})
placemarks sometimes was returning nil...
So... I realised that I was playing with my HTTP proxy in order to test some request to the Google API from my device with the Charles Proxy. Removing the Manual HTTP proxy address on my iPhone did the trick and geocoder was working fine again.
I hope it helps.
PD: Nevermind...after 30 seconds or even less, app crashes with the same debug message: [Client] Geocode error:
I had the same issue, it seems there is a limit when geocoding by address and this is the error for this.
My workaround was to put sleep(1) after each geocode in my loop, find the location latitude and longitude and post them back into my database against each venue I was plotting.
This means it now doesn't need to use the geocoder to plot the annotations on the map as it just uses the lat and long to do so. This will work for fixed sets of data but for stuff on the fly you would need another way. Apparently sleep(2) will enable you to plot continuously through large volumes of data but ends up taking a while for everything to plot.
I've added a default lat and long of 0.0 into my database and upon launching the app it will scan through to find any new venues and will then geocode these and post the lat and long back. I had to do a couple of runs through originally to get ~700 venues lats and longs but I've had no trouble since.
I don't know about anyone else, but EventKit seems to have very little in terms resources and tutorials online for you to refer to for help.
I need to trigger an alarm when a user hits a radius of a set of coordinates, I wasn't sure of the best ay to do this, I was torn between local notifications, and EventKit reminders.
I decided to go for eventKit as I felt that I could do more with more the alarms and it was the most practical way to do it however, having not known much about EventKit i had some issues.
Anyway I've managed to get together and build a sample project which works and triggers an alert when the user leaves their current location, the only problem is, is that I almost want to do the complete opposite of that, I want to trigger an alert when the user enters a set of coordinates, I assume that most of the code is transferrable, however I seem to be stuck on one bit mainly.
// Creates an EKStructuredLocation Instance with a title of "Current Location"
let location = EKStructuredLocation(title: "Current Location")
// Uses the last location update extracted from the locations array to supply you're current location
location.geoLocation = locations.last as! CLLocation
// Location is added to a newly created alarm instance
let alarm = EKAlarm()
alarm.structuredLocation = location
// This alarm is triggered when the user moves away from the location proximity
alarm.proximity = EKAlarmProximityLeave
stationReminder.addAlarm(alarm)
I'm struggling to find how to set the location of the alarm to coordinates rather than users location.
I tried changing this
location.geoLocation = locations.last as! CLLocation
to
location.geoLocation = CLCircularRegion(circularRegionWithCenter: CLLocationCoordinate2D(latitude: 37.33233141, longitude: -122.03121860), radius: 50.0, identifier: "Location1")
but this doesn't work, i believe i am on the right track but i am throwing up this error: Cannot assign a value of type 'CLCircularRegion!' to a value of type 'CLLocation!'
I've tried loads of things with no resolve, does anybody have any experience with this and know how to help?
I also assume i'll have to change the following from this
alarm.proximity = EKAlarmProximityLeave
to this
alarm.proximity = EKAlarmProximityEnter
UPDATE
I've taken on board some comments below and tried a bunch of other things to get this to work, I feel like I am so close but somethings just missing. I cannot get this alarm to trigger. Excuse all the code comments, it's just so you can see some of the attempts i have made at fixing this.
can anyone see anything wrong with this code for the alarm?
func locationManager(manager: CLLocationManager!, didUpdateLocations locations: [AnyObject]!) {
// Stops location manager from sending further updates
locationManager.stopUpdatingLocation()
// Creates a new EKReminder which is named and initialised with text from the UITextField
let stationReminder = EKReminder(eventStore: appDelegate!.eventStore)
stationReminder.title = locationText.text
// Stores the previously created EKReminder in the default calendar
stationReminder.calendar = appDelegate!.eventStore!.defaultCalendarForNewReminders()
// Creates an EKStructuredLocation Instance with a title of "Current Location"
let location = EKStructuredLocation(title: "Destination: Bournemouth Station")
// Uses the last location update extracted from the locations array to supply you're current location
// location.geoLocation = locations.last as! CLLocation
// location.geoLocation = CLCircularRegion(circularRegionWithCenter: CLLocationCoordinate2D(latitude: 37.33233141, longitude: -122.03121860), radius: 50.0, identifier: "Location1")
// location.geoLocation = CLLocation(latitude: 50.742771, longitude: -1.895072)
location.radius = 50.0
location.geoLocation = CLLocation(latitude:50.742771, longitude:-1.895072)
// location.radius = 10.0 // metres
// Location is added to a newly created alarm instance
let alarm = EKAlarm()
alarm.structuredLocation = location
// This alarm is triggered when the user moves away from the location proximity
// alarm.proximity = EKAlarmProximityEnter
alarm.proximity = EKAlarmProximityEnter // "geofence": we alarm when *arriving*
// but this will have no effect until Reminders is granted Location access...
// and in iOS 8 it won't even ask for it until it is launched
// also, in iOS 8 the separate background usage pref is withdrawn;
// instead, auth of Reminders for "when in use" covers this...
// ...because it means "this app *or one of its features* is visible on screen"
stationReminder.addAlarm(alarm)
// Now we have a fully configured reminder which we save in the Event Store
var error: NSError?
appDelegate!.eventStore?.saveReminder(stationReminder,
commit: true, error: &error)
if error != nil {
println("Reminder failed with error \(error?.localizedDescription)")
}
}
Looks like EKEvent has an EKStructuredLocation, which you are using correctly. However, you need to be careful of the type of the geoLocation property. It should be a CLLocation, which is not the same as a CLCircularRegion.
Steps to fix:
check the docs for EKStructuredLocation https://developer.apple.com/library/mac/documentation/EventKit/Reference/EKStructuredLocationClassRef/index.html
set the location.geoLocation to a CLLocation that you create from latitude, longitude coordinates. (check the docs for CLLocation: https://developer.apple.com/library/mac/documentation/CoreLocation/Reference/CLLocation_Class/index.html#//apple_ref/swift/cl/CLLocation)
geoLocation = CLLocation(latitude: 37.33233141, longitude: -122.03121860)
set the geoLocation.radius separately location.radius = 50.0
at that point setting proximityEnter should work as you expected
I am working on an app for iOS 7 and I need to find the location of the user and then check to see what other users are also at that same location. I need it to update as soon as the user opens the app as well as update every so often and will display the users at the same location. I have looked at the available examples, but there doesn't seem to be enough on this using Parse. Can anyone give me any help on how to go about doing this, or if anyone knows of some examples similar to what I'm trying to do I would greatly appreciate it. Thanks in advance!
You need to break your problem down and tackle the various pieces -
Obtain the user's location when the app opens and periodically
The Location and Maps programming guide is a good starting point. You can use CLLocationManager to obtain your initial location and you can register for the significant location change event to get updates periodically, even when your app isn't running. Apple has example code plus there are plenty of other examples out there - Just search using "Core Location examples"
Store the user's location in your Parse database
There are examples and documentation on Parse.com showing how to do this. You can also provide a web service that allows your app to query the database for other users at the same location
Identify other users at the same location when your app isn't running
You can use Parse background jobs to trawl your database, match user locations and send push notifications to your users. Again there are examples on Parse.com showing how to set up background jobs and push notifications
this may help this will get your current location then save to parse, its just a matter of querying parse to pull the data down
func locationManager(manager: CLLocationManager!, didUpdateLocations
locations: [AnyObject]!) {
var userLocation : CLLocation = locations[0] as! CLLocation
var latitude = userLocation.coordinate.latitude
var longitude = userLocation.coordinate.longitude
var latDelta : CLLocationDegrees = 0.01
var lonDelta : CLLocationDegrees = 0.01
var span : MKCoordinateSpan = MKCoordinateSpanMake(latDelta,
lonDelta)
var location : CLLocationCoordinate2D = CLLocationCoordinate2DMake(latitude, longitude)
var region : MKCoordinateRegion = MKCoordinateRegionMake(location, span)
self.mapView.setRegion(region, animated: true)
let testObject = PFObject(className: "User")
let currentPoints = PFGeoPoint(latitude: latitude, longitude: longitude)
testObject.setValue(currentPoints, forKey: "currentLocation")
testObject.saveInBackgroundWithBlock { (success: Bool, error: NSError?) -> Void in
if (success) {
dispatch_async(dispatch_get_main_queue()) {
println("added to parse")
}
} else {
println(error)
}
}
}