CLGeocoder error EXC_BAD_INSTRUCTION - ios

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
}
}
}

Related

Cannot pass function of type '([CLPlacemark]?, (any Error)?) async throws -> Void' to parameter expecting synchronous function type

I have written this code:
In the first step, I read an Excel file that contains the addresses of the buildings as a string.
LABE
HAUS
Gemeindename
Hasenwinkel
14
Braunschweig
The Excel file is similar to the table above.
I want to read the addresses inside a loop and get the coordinates of the addresses (Lat, Long) using CLGeocoder. Using the snapshotImage function, I give the coordinates as input to the function, and if there is picture, I want to save that picture of the target address with its address name.
But I get the following errors:
1.Cannot pass function of type '([CLPlacemark]?, (any Error)?) async throws -> Void' to parameter expecting synchronous function type
2. Invalid conversion from throwing function of type '([CLPlacemark]?, (any Error)?) async throws -> Void' to non-throwing function type '([CLPlacemark]?, (any Error)?) -> Void'
let columnTypes: [String: CSVType] = ["LABE": .string,
"HAUS": .string,
"Gemeindename": .string]
let path = Bundle.main.url(forResource: "Adresse", withExtension: "csv")!
let options = CSVReadingOptions(
hasHeaderRow: true,
ignoresEmptyLines: true,
delimiter: ";"
)
let result = try DataFrame(contentsOfCSVFile: path,rows: 0..<50, types: columnTypes,options: options)
enum LookaroundError: Error {
case unableToCreateScene
}
func snapshotImage(for coordinate: CLLocationCoordinate2D) async throws -> UIImage {
guard let scene = try await MKLookAroundSceneRequest(coordinate: coordinate).scene else {
throw LookaroundError.unableToCreateScene
}
let options = MKLookAroundSnapshotter.Options()
options.size = CGSize(width: 1000, height: 500)
return try await MKLookAroundSnapshotter(scene: scene, options: options).snapshot.image
}
var address: String = ""
for row in result.rows {
if row["HAUS"] == nil {
address = "\(row["LABE"] ?? <#default value#>) \(row["Gemeindename"] ?? <#default value#>)"
} else {
address = "\(row["LABE"] ?? <#default value#>) \(row["HAUS"] ?? <#default value#>) \(row["Gemeindename"] ?? <#default value#>)"
}
print(address)
let geocoder = CLGeocoder()
geocoder.geocodeAddressString(address){ (placemarks, error) in
guard
let placemarks = placemarks,
let location: CLLocationCoordinate2D = placemarks.first?.location?.coordinate
else{
return
}
var image = try await snapshotImage(for: location)
var url = try FileManager.default
.url(for: .applicationSupportDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
.appending(component: "\(address).png")
try image.pngData()?.write(to: url)
}
}
Ideally, I want to read the addresses in this loop and save a picture of them using their coordinates.I don't have much experience in Swift programming. Does anyone have an idea how to solve the code problem?
The error tells you exactly what's wrong, but it may not be obvious why it is wrong.
This line
var image = try await snapshotImage(for: location)
makes the closure you pass to geocoder.geocodeAddressString asynchronous. That function will not accept async closures. It's not declared to. It's the same as if you passed a throwing closure to a non throwing parameter.
Since you don't provide the definition for geocoder.geocodeAddressString it is difficult to suggest a fix. Perhaps just build an array of locations in the loop and then get the snapshot images afterwards.
change
geocoder.geocodeAddressString(address){ (placemarks, error) in
guard
let placemarks = placemarks,
let location: CLLocationCoordinate2D = placemarks.first?.location?.coordinate
else{
return
}
to
guard let location = try await geocoder.geocodeAddressString(address).first?.location?.coordinate else { return }
or, more correct, mark your function signature with throws and
do {
let location = try await geocoder.geocodeAddressString(address).first?.location?.coordinate
} catch {
throw(error)
}

Function runs after block of code even though it is called before

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.

Initializer For Conditional Binding Must Have Optional Type, not CLPlacemark

Noob programmer here.
I'm trying to become a good programmer with the help of the internet. So, I subscribed to Rob Percivals iOS class. I'm having trouble with this bit of my code. Must've been a recent update that's making it fail on my system although it is the same code as his.
CLGeocoder().reverseGeocodeLocation(userLocation) { (placemarks, error) in
if (error != nil) {
print(error)
} else {
if let p = CLPlacemark(placemark: placemarks![0] as! CLPlacemark) {
print(p)
}
}
}
}
Please try to put it in baby english if possible guys. Thanks!
placemarks is of type [CLPlacemark]?. Therefore you should try something like the following:
if let placemarks = placemarks {
if let p0 = placemarks.first {
print (p0)
}
}
Don't use !. Use if let instead.
The error message just says that the initializer CLPlacemark(placemark:) does never return a nil value so reduce the code and even remove the type casting, because according to the signature [CLPlacemark]?is returned. Optional binding is not needed because if the error is nil the array is guaranteed to exist.
...
} else {
if !placemarks!.isEmpty {
let p = CLPlacemark(placemark: placemarks![0])
print(p)
}
}
I'd recommend taking it a step further with a guard statement, and being a little more verbose with the variable name p... Guard is a good way to exit from the current flow without cluttering your code with if statements, especially since the 'if let' optional unwrapping syntax came around.
CLGeocoder().reverseGeocodeLocation(userLocation) { (placemarks, error) in
guard error == nil else {
print(error)
return
}
if let placemark = placemarks?.first {
let currentPlacemark = CLPlacemark(placemark: placemark)
print(currentPlacemark)
}
}

CLGeoCoder doesn't append placemarks in right order

In my viewDidLoad I have this method which turns a location from Parse into a city and state. The only problem is, is that it appends in the wrong order most of the time. Also the .location I have on the end of the location variable is a custom method that turns a PFGeoPoint into a CLLocation.
for post in posts {
let location = ((post["locations"] as! NSMutableArray)[0] as! PFGeoPoint).location
CLGeocoder().reverseGeocodeLocation(location()) { (placemarks, error) -> Void in
if error != nil {
print(error)
} else {
let p = CLPlacemark(placemark: placemarks![0])
let locationString = "\(p.locality!), \(p.administrativeArea!)"
self.locationStrings.append(locationString)
}
}
}
I believe this is all happening due to the CLGeoCoder completion handler takes a while to fully execute. I think this could be solved by doing all of this without the completion hander, but I'm not sure how to do that. Thank you!
Reverse geocoding needs to be done asynchronously to prevent lag. You can create an object to hold your post location data, or your post data in general, with a location string property. Instantiate the post object and add them to an array then iterate over them to retrieve the location string and set it on the object. For example:
for postObject in postObjects {
CLGeocoder().reverseGeocodeLocation(postObject.location) { (placemarks, error) -> Void in
if error != nil {
print(error)
} else {
let p = CLPlacemark(placemark: placemarks![0])
postObject.locationString = "\(p.locality!), \(p.administrativeArea!)"
}
}
}

How can I access a variable outside block? [duplicate]

This question already has an answer here:
AlamoFire GET api request not working as expected
(1 answer)
Closed 8 years ago.
Following code doesn't work properly.
func convertToStreet(location:CLLocationCoordinate2D) -> CLPlacemark {
var tempLocation = CLLocation(latitude: location.latitude, longitude: location.longitude)
var temPlacemark:CLPlacemark?
CLGeocoder().reverseGeocodeLocation(tempLocation, completionHandler: {(placemarks, error) in
temPlacemark = (placemarks[0] as CLPlacemark)
println(temPlacemark!.thoroughfare)
})
return temPlacemark!
}
Println inside the completion handler works properly, but the value of temPlacemark is nil at the end of code. Why does that happen? I thank you very much in advance.
It's because that completionHandler is called asynchronously. For that to work you should have a callback block in your custom function to return the value once you get it from the CLGeocoder.
Something like this:
func convertToStreet(coordinate: CLLocationCoordinate2D, completionHandler: (placemark: CLPlacemark!, error: NSError!) -> Void) {
let tempLocation = CLLocation(latitude: coordinate.latitude, longitude: coordinate.longitude)
CLGeocoder().reverseGeocodeLocation(tempLocation) { placemarks, error in
completionHandler(placemark: placemarks?.first as CLPlacemark?, error: error)
}
}
You'd then call it like so:
convertToStreet(location.coordinate) { placemark, error in
if placemark != nil {
// use `placemark` here
println(placemark.thoroughfare)
} else {
println(error)
}
}
// but don't use `placemark` here
The problem of your code is that the block code :
temPlacemark = (placemarks[0] as CLPlacemark)
println(temPlacemark!.thoroughfare)
will be executed later.
Meaning that the current return statement will always return an uninitialized value.
If you want to initialize this var in your block you should make it a property of your object.

Resources